From 1f132399dd5dfcc09a73be9ae7f65db18b364344 Mon Sep 17 00:00:00 2001 From: vz Date: Sat, 21 Jan 2017 16:52:05 +0800 Subject: [PATCH 001/120] Initial commit --- .gitignore | 24 ++ LICENSE | 674 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 1 + 3 files changed, 699 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9cecc1d --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a0c944 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# gohook \ No newline at end of file From 16089b2aa9f48dd17623c3c2bfc23511dc98b4d8 Mon Sep 17 00:00:00 2001 From: vCaesar Date: Mon, 6 Feb 2017 16:55:01 +0800 Subject: [PATCH 002/120] Initial commit --- README.md | 4 +- event/hook/darwin/event_c.h | 235 ++++ event/hook/darwin/hook_c.h | 1362 ++++++++++++++++++++ event/hook/darwin/input.h | 103 ++ event/hook/darwin/input_c.h | 542 ++++++++ event/hook/darwin/properties_c.h | 522 ++++++++ event/hook/iohook.h | 441 +++++++ event/hook/logger.h | 15 + event/hook/logger_c.h | 53 + event/hook/windows/event_c.h | 328 +++++ event/hook/windows/hook_c.h | 747 +++++++++++ event/hook/windows/input.h | 104 ++ event/hook/windows/input_c.h | 832 ++++++++++++ event/hook/windows/properties_c.h | 211 +++ event/hook/x11/event_c.h | 386 ++++++ event/hook/x11/hook_c.h | 1132 +++++++++++++++++ event/hook/x11/input.h | 86 ++ event/hook/x11/input_c.h | 1970 +++++++++++++++++++++++++++++ event/hook/x11/properties_c.h | 499 ++++++++ hook.go | 0 test/main.go | 14 + 21 files changed, 9585 insertions(+), 1 deletion(-) create mode 100644 event/hook/darwin/event_c.h create mode 100644 event/hook/darwin/hook_c.h create mode 100644 event/hook/darwin/input.h create mode 100644 event/hook/darwin/input_c.h create mode 100644 event/hook/darwin/properties_c.h create mode 100644 event/hook/iohook.h create mode 100644 event/hook/logger.h create mode 100644 event/hook/logger_c.h create mode 100644 event/hook/windows/event_c.h create mode 100644 event/hook/windows/hook_c.h create mode 100644 event/hook/windows/input.h create mode 100644 event/hook/windows/input_c.h create mode 100644 event/hook/windows/properties_c.h create mode 100644 event/hook/x11/event_c.h create mode 100644 event/hook/x11/hook_c.h create mode 100644 event/hook/x11/input.h create mode 100644 event/hook/x11/input_c.h create mode 100644 event/hook/x11/properties_c.h create mode 100644 hook.go create mode 100644 test/main.go diff --git a/README.md b/README.md index 7a0c944..764879e 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# gohook \ No newline at end of file +# gohook + +This is a work in progress. \ No newline at end of file diff --git a/event/hook/darwin/event_c.h b/event/hook/darwin/event_c.h new file mode 100644 index 0000000..c621b71 --- /dev/null +++ b/event/hook/darwin/event_c.h @@ -0,0 +1,235 @@ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include +#include +#include "../iohook.h" + +// #include "../logger_c.h" +#include "input.h" + +// TODO Possibly relocate to input helper. +static inline CGEventFlags get_key_event_mask(iohook_event * const event) { + CGEventFlags native_mask = 0x00; + + if (event->mask & (MASK_SHIFT)) { native_mask |= kCGEventFlagMaskShift; } + if (event->mask & (MASK_CTRL)) { native_mask |= kCGEventFlagMaskControl; } + if (event->mask & (MASK_META)) { native_mask |= kCGEventFlagMaskControl; } + if (event->mask & (MASK_ALT)) { native_mask |= kCGEventFlagMaskAlternate; } + + if (event->type == EVENT_KEY_PRESSED || event->type == EVENT_KEY_RELEASED || event->type == EVENT_KEY_TYPED) { + switch (event->data.keyboard.keycode) { + case VC_KP_0: + case VC_KP_1: + case VC_KP_2: + case VC_KP_3: + case VC_KP_4: + case VC_KP_5: + case VC_KP_6: + case VC_KP_7: + case VC_KP_8: + case VC_KP_9: + + case VC_NUM_LOCK: + case VC_KP_ENTER: + case VC_KP_MULTIPLY: + case VC_KP_ADD: + case VC_KP_SEPARATOR: + case VC_KP_SUBTRACT: + case VC_KP_DIVIDE: + case VC_KP_COMMA: + native_mask |= kCGEventFlagMaskNumericPad; + break; + } + } + + return native_mask; +} + +static inline void post_key_event(iohook_event * const event) { + bool is_pressed = event->type == EVENT_KEY_PRESSED; + + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef cg_event = CGEventCreateKeyboardEvent(src, + (CGKeyCode) scancode_to_keycode(event->data.keyboard.keycode), + is_pressed); + + CGEventSetFlags(cg_event, get_key_event_mask(event)); + CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works. + CFRelease(cg_event); + CFRelease(src); +} + +static inline void post_mouse_button_event(iohook_event * const event, bool is_pressed) { + CGMouseButton mouse_button; + CGEventType mouse_type; + if (event->data.mouse.button == MOUSE_BUTTON1) { + if (is_pressed) { + mouse_type = kCGEventLeftMouseDown; + } + else { + mouse_type = kCGEventLeftMouseUp; + } + mouse_button = kCGMouseButtonLeft; + } + else if (event->data.mouse.button == MOUSE_BUTTON2) { + if (is_pressed) { + mouse_type = kCGEventRightMouseDown; + } + else { + mouse_type = kCGEventRightMouseUp; + } + mouse_button = kCGMouseButtonRight; + } + else { + if (is_pressed) { + mouse_type = kCGEventOtherMouseDown; + } + else { + mouse_type = kCGEventOtherMouseUp; + } + mouse_button = event->data.mouse.button - 1; + } + + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef cg_event = CGEventCreateMouseEvent(src, + mouse_type, + CGPointMake( + (CGFloat) event->data.mouse.x, + (CGFloat) event->data.mouse.y + ), + mouse_button + ); + CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works. + CFRelease(cg_event); + CFRelease(src); +} + +static inline void post_mouse_wheel_event(iohook_event * const event) { + // FIXME Should I create a source event with the coords? + // It seems to automagically use the current location of the cursor. + // Two options: Query the mouse, move it to x/y, scroll, then move back + // OR disable x/y for scroll events on Windows & X11. + CGScrollEventUnit scroll_unit; + if (event->data.wheel.type == WHEEL_BLOCK_SCROLL) { + // Scrolling data is line-based. + scroll_unit = kCGScrollEventUnitLine; + } + else { + // Scrolling data is pixel-based. + scroll_unit = kCGScrollEventUnitPixel; + } + + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef cg_event = CGEventCreateScrollWheelEvent(src, + kCGScrollEventUnitLine, + // TODO Currently only support 1 wheel axis. + (CGWheelCount) 1, // 1 for Y-only, 2 for Y-X, 3 for Y-X-Z + event->data.wheel.amount * event->data.wheel.rotation); + + CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works. + CFRelease(cg_event); + CFRelease(src); +} + +static inline void post_mouse_motion_event(iohook_event * const event) { + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef cg_event; + if (event->mask >> 8 == 0x00) { + // No mouse flags. + cg_event = CGEventCreateMouseEvent(src, + kCGEventMouseMoved, + CGPointMake( + (CGFloat) event->data.mouse.x, + (CGFloat) event->data.mouse.y + ), + 0 + ); + } + else if (event->mask & MASK_BUTTON1) { + cg_event = CGEventCreateMouseEvent(src, + kCGEventLeftMouseDragged, + CGPointMake( + (CGFloat) event->data.mouse.x, + (CGFloat) event->data.mouse.y + ), + kCGMouseButtonLeft + ); + } + else if (event->mask & MASK_BUTTON2) { + cg_event = CGEventCreateMouseEvent(src, + kCGEventRightMouseDragged, + CGPointMake( + (CGFloat) event->data.mouse.x, + (CGFloat) event->data.mouse.y + ), + kCGMouseButtonRight + ); + } + else { + cg_event = CGEventCreateMouseEvent(src, + kCGEventOtherMouseDragged, + CGPointMake( + (CGFloat) event->data.mouse.x, + (CGFloat) event->data.mouse.y + ), + (event->mask >> 8) - 1 + ); + } + + // kCGSessionEventTap also works. + CGEventPost(kCGHIDEventTap, cg_event); + CFRelease(cg_event); + CFRelease(src); +} + +IOHOOK_API void hook_post_event(iohook_event * const event) { + switch (event->type) { + case EVENT_KEY_PRESSED: + case EVENT_KEY_RELEASED: + post_key_event(event); + break; + + + case EVENT_MOUSE_PRESSED: + post_mouse_button_event(event, true); + break; + + case EVENT_MOUSE_RELEASED: + post_mouse_button_event(event, false); + break; + + case EVENT_MOUSE_CLICKED: + post_mouse_button_event(event, true); + post_mouse_button_event(event, false); + break; + + case EVENT_MOUSE_WHEEL: + post_mouse_wheel_event(event); + break; + + + case EVENT_MOUSE_MOVED: + case EVENT_MOUSE_DRAGGED: + post_mouse_motion_event(event); + break; + + + case EVENT_KEY_TYPED: + // FIXME Ignoreing EVENT_KEY_TYPED events. + + case EVENT_HOOK_ENABLED: + case EVENT_HOOK_DISABLED: + // Ignore hook enabled / disabled events. + + default: + // Ignore any other garbage. + logger(LOG_LEVEL_WARN, "%s [%u]: Ignoring post event type %#X\n", + __FUNCTION__, __LINE__, event->type); + break; + } +} diff --git a/event/hook/darwin/hook_c.h b/event/hook/darwin/hook_c.h new file mode 100644 index 0000000..cb8b609 --- /dev/null +++ b/event/hook/darwin/hook_c.h @@ -0,0 +1,1362 @@ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifndef USE_WEAK_IMPORT + #include +#endif +#include +#ifdef USE_OBJC + #include + #include +#endif + +#include +#include +#include +#include "../iohook.h" +// #include "../logger_c.h" +#include "input.h" + +typedef struct _hook_info { + CFMachPortRef port; + CFRunLoopSourceRef source; + CFRunLoopObserverRef observer; +} hook_info; + +#ifdef USE_OBJC +static id auto_release_pool; +#endif + +// Event runloop reference. +CFRunLoopRef event_loop; + +// Flag to restart the event tap incase of timeout. +static Boolean restart_tap = false; + +// Modifiers for tracking key masks. +static uint16_t current_modifiers = 0x0000; + +// Required to transport messages between the main runloop and our thread for +// Unicode lookups. +#define KEY_BUFFER_SIZE 4 +typedef struct { + CGEventRef event; + UniChar buffer[KEY_BUFFER_SIZE]; + UniCharCount length; +} TISMessage; +TISMessage *tis_message; + +#ifdef USE_WEAK_IMPORT +// Required to dynamically check for AXIsProcessTrustedWithOptions availability. +extern void dispatch_get_main_queue() __attribute__((weak_import)); +extern void dispatch_sync_f(dispatch_queue_t queue, void *context, void (*function)(void *)) __attribute__((weak_import)); +#else +#if __MAC_OS_X_VERSION_MAX_ALLOWED <= 1050 +typedef void* dispatch_queue_t; +#endif +static dispatch_queue_t (*dispatch_get_main_queue_f)(); +static void (*dispatch_sync_f_f)(dispatch_queue_t, void *, void (*function)(void *)); +#endif + +#if ! defined(USE_CARBON_LEGACY) && defined(USE_COREFOUNDATION) +static CFRunLoopSourceRef src_msg_port; +static CFRunLoopObserverRef observer; + +static pthread_cond_t msg_port_cond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t msg_port_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +// Click count globals. +static unsigned short click_count = 0; +static CGEventTimestamp click_time = 0; +static unsigned short int click_button = MOUSE_NOBUTTON; +static bool mouse_dragged = false; + +// Structure for the current Unix epoch in milliseconds. +static struct timeval system_time; + +// Virtual event pointer. +static iohook_event event; + +// Event dispatch callback. +static dispatcher_t dispatcher = NULL; + +IOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n", + __FUNCTION__, __LINE__, dispatch_proc); + + dispatcher = dispatch_proc; +} + +// Send out an event if a dispatcher was set. +static inline void dispatch_event(iohook_event *const event) { + if (dispatcher != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n", + __FUNCTION__, __LINE__, event->type); + + dispatcher(event); + } + else { + logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n", + __FUNCTION__, __LINE__); + } +} + + +// Set the native modifier mask for future events. +static inline void set_modifier_mask(uint16_t mask) { + current_modifiers |= mask; +} + +// Unset the native modifier mask for future events. +static inline void unset_modifier_mask(uint16_t mask) { + current_modifiers ^= mask; +} + +// Get the current native modifier mask state. +static inline uint16_t get_modifiers() { + return current_modifiers; +} + +// Initialize the modifier mask to the current modifiers. +static void initialize_modifiers() { + current_modifiers = 0x0000; + + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_Shift)) { + set_modifier_mask(MASK_SHIFT_L); + } + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_RightShift)) { + set_modifier_mask(MASK_SHIFT_R); + } + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_Control)) { + set_modifier_mask(MASK_CTRL_L); + } + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_RightControl)) { + set_modifier_mask(MASK_CTRL_R); + } + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_Option)) { + set_modifier_mask(MASK_ALT_L); + } + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_RightOption)) { + set_modifier_mask(MASK_ALT_R); + } + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_Command)) { + set_modifier_mask(MASK_META_L); + } + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_RightCommand)) { + set_modifier_mask(MASK_META_R); + } + + if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_LBUTTON)) { + set_modifier_mask(MASK_BUTTON1); + } + if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_RBUTTON)) { + set_modifier_mask(MASK_BUTTON2); + } + if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_MBUTTON)) { + set_modifier_mask(MASK_BUTTON3); + } + if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_XBUTTON1)) { + set_modifier_mask(MASK_BUTTON4); + } + if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_XBUTTON2)) { + set_modifier_mask(MASK_BUTTON5); + } + + if (CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState) & kCGEventFlagMaskAlphaShift) { + set_modifier_mask(MASK_CAPS_LOCK); + } + // Best I can tell, OS X does not support Num or Scroll lock. + unset_modifier_mask(MASK_NUM_LOCK); + unset_modifier_mask(MASK_SCROLL_LOCK); +} + + +// Wrap keycode_to_unicode with some null checks. +static void keycode_to_lookup(void *info) { + TISMessage *data = (TISMessage *) info; + + if (data != NULL && data->event != NULL) { + // Preform Unicode lookup. + data->length = keycode_to_unicode(data->event, data->buffer, KEY_BUFFER_SIZE); + } +} + +#if ! defined(USE_CARBON_LEGACY) && defined(USE_COREFOUNDATION) +void message_port_status_proc(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { + switch (activity) { + case kCFRunLoopExit: + // Acquire a lock on the msg_port and signal that anyone waiting + // should continue. + pthread_mutex_lock(&msg_port_mutex); + pthread_cond_broadcast(&msg_port_cond); + pthread_mutex_unlock(&msg_port_mutex); + break; + + default: + logger(LOG_LEVEL_WARN, "%s [%u]: Unhandled RunLoop activity! (%#X)\n", + __FUNCTION__, __LINE__, (unsigned int) activity); + break; + } +} + +// Runloop to execute KeyCodeToString on the "Main" runloop due to an +// undocumented thread safety requirement. +static void message_port_proc(void *info) { + // Lock the msg_port mutex as we enter the main runloop. + pthread_mutex_lock(&msg_port_mutex); + + keycode_to_lookup(info); + + // Unlock the msg_port mutex to signal to the hook_thread that we have + // finished on the main runloop. + pthread_cond_broadcast(&msg_port_cond); + pthread_mutex_unlock(&msg_port_mutex); +} + +static int start_message_port_runloop() { + int status = IOHOOK_FAILURE; + + if (tis_message != NULL) { + // Create a runloop observer for the main runloop. + observer = CFRunLoopObserverCreate( + kCFAllocatorDefault, + kCFRunLoopExit, //kCFRunLoopEntry | kCFRunLoopExit, //kCFRunLoopAllActivities, + true, + 0, + message_port_status_proc, + NULL + ); + + if (observer != NULL) { + pthread_mutex_lock(&msg_port_mutex); + + CFRunLoopSourceContext context = { + .version = 0, + .info = tis_message, + .retain = NULL, + .release = NULL, + .copyDescription = NULL, + .equal = NULL, + .hash = NULL, + .schedule = NULL, + .cancel = NULL, + .perform = message_port_proc + }; + + CFRunLoopRef main_loop = CFRunLoopGetMain(); + + src_msg_port = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); + if (src_msg_port != NULL) { + CFRunLoopAddSource(main_loop, src_msg_port, kCFRunLoopDefaultMode); + CFRunLoopAddObserver(main_loop, observer, kCFRunLoopDefaultMode); + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Successful.\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_SUCCESS; + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopSourceCreate failure!\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE; + } + + pthread_mutex_unlock(&msg_port_mutex); + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopObserverCreate failure!\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_ERROR_CREATE_OBSERVER; + } + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: No available TIS Message pointer.\n", + __FUNCTION__, __LINE__); + } + + return status; +} + +static void stop_message_port_runloop() { + CFRunLoopRef main_loop = CFRunLoopGetMain(); + + if (CFRunLoopContainsObserver(main_loop, observer, kCFRunLoopDefaultMode)) { + CFRunLoopRemoveObserver(main_loop, observer, kCFRunLoopDefaultMode); + CFRunLoopObserverInvalidate(observer); + } + + if (CFRunLoopContainsSource(main_loop, src_msg_port, kCFRunLoopDefaultMode)) { + CFRunLoopRemoveSource(main_loop, src_msg_port, kCFRunLoopDefaultMode); + CFRelease(src_msg_port); + } + + observer = NULL; + src_msg_port = NULL; + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Successful.\n", + __FUNCTION__, __LINE__); +} +#endif + +static void hook_status_proc(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { + uint64_t timestamp = mach_absolute_time(); + + switch (activity) { + case kCFRunLoopEntry: + // Populate the hook start event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_HOOK_ENABLED; + event.mask = 0x00; + + // Fire the hook start event. + dispatch_event(&event); + break; + + case kCFRunLoopExit: + // Populate the hook stop event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_HOOK_DISABLED; + event.mask = 0x00; + + // Fire the hook stop event. + dispatch_event(&event); + break; + + default: + logger(LOG_LEVEL_WARN, "%s [%u]: Unhandled RunLoop activity! (%#X)\n", + __FUNCTION__, __LINE__, (unsigned int) activity); + break; + } +} + +static inline void process_key_pressed(uint64_t timestamp, CGEventRef event_ref) { + UInt64 keycode = CGEventGetIntegerValueField(event_ref, kCGKeyboardEventKeycode); + + // Populate key pressed event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_KEY_PRESSED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = keycode_to_scancode(keycode); + event.data.keyboard.rawcode = keycode; + event.data.keyboard.keychar = CHAR_UNDEFINED; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X pressed. (%#X)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode); + + // Fire key pressed event. + dispatch_event(&event); + + // If the pressed event was not consumed... + if (event.reserved ^ 0x01) { + tis_message->event = event_ref; + tis_message->length = 0; + bool is_runloop_main = CFEqual(event_loop, CFRunLoopGetMain()); + + #ifdef USE_WEAK_IMPORT + if (dispatch_sync_f != NULL && dispatch_get_main_queue != NULL && !is_runloop_main) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Using dispatch_sync_f for key typed events.\n", __FUNCTION__, __LINE__); + dispatch_sync_f(dispatch_get_main_queue(), tis_message, &keycode_to_lookup); + } + #else + if (dispatch_sync_f_f != NULL && dispatch_get_main_queue_f != NULL && !is_runloop_main) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Using *dispatch_sync_f_f for key typed events.\n", __FUNCTION__, __LINE__); + (*dispatch_sync_f_f)((*dispatch_get_main_queue_f)(), tis_message, &keycode_to_lookup); + } + #endif + #if ! defined(USE_CARBON_LEGACY) && defined(USE_COREFOUNDATION) + else if (!is_runloop_main) { + // Lock for code dealing with the main runloop. + pthread_mutex_lock(&msg_port_mutex); + + // Check to see if the main runloop is still running. + // TOOD I would rather this be a check on hook_enable(), + // but it makes the usage complicated by requiring a separate + // thread for the main runloop and hook registration. + CFStringRef mode = CFRunLoopCopyCurrentMode(CFRunLoopGetMain()); + if (mode != NULL) { + CFRelease(mode); + + // Lookup the Unicode representation for this event. + //CFRunLoopSourceContext context = { .version = 0 }; + //CFRunLoopSourceGetContext(src_msg_port, &context); + + // Get the run loop context info pointer. + //TISMessage *info = (TISMessage *) context.info; + + // Set the event pointer. + //info->event = event_ref; + + // Signal the custom source and wakeup the main runloop. + CFRunLoopSourceSignal(src_msg_port); + CFRunLoopWakeUp(CFRunLoopGetMain()); + + // Wait for a lock while the main runloop processes they key typed event. + pthread_cond_wait(&msg_port_cond, &msg_port_mutex); + } + else { + logger(LOG_LEVEL_WARN, "%s [%u]: Failed to signal RunLoop main!\n", + __FUNCTION__, __LINE__); + } + + // Unlock for code dealing with the main runloop. + pthread_mutex_unlock(&msg_port_mutex); + } + #endif + else { + keycode_to_lookup(tis_message); + } + unsigned int i; + for (i= 0; i < tis_message->length; i++) { + // Populate key typed event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_KEY_TYPED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = VC_UNDEFINED; + event.data.keyboard.rawcode = keycode; + event.data.keyboard.keychar = tis_message->buffer[i]; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X typed. (%lc)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, + (wint_t) event.data.keyboard.keychar); + + // Populate key typed event. + dispatch_event(&event); + } + } +} + +static inline void process_key_released(uint64_t timestamp, CGEventRef event_ref) { + UInt64 keycode = CGEventGetIntegerValueField(event_ref, kCGKeyboardEventKeycode); + + // Populate key released event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_KEY_RELEASED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = keycode_to_scancode(keycode); + event.data.keyboard.rawcode = keycode; + event.data.keyboard.keychar = CHAR_UNDEFINED; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X released. (%#X)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode); + + // Fire key released event. + dispatch_event(&event); +} + +static inline void process_modifier_changed(uint64_t timestamp, CGEventRef event_ref) { + CGEventFlags event_mask = CGEventGetFlags(event_ref); + UInt64 keycode = CGEventGetIntegerValueField(event_ref, kCGKeyboardEventKeycode); + + logger(LOG_LEVEL_INFO, "%s [%u]: Modifiers Changed for key %#X. (%#X)\n", + __FUNCTION__, __LINE__, (unsigned long) keycode, (unsigned int) event_mask); + + /* Because Apple treats modifier keys differently than normal key + * events, any changes to the modifier keys will require a key state + * change to be fired manually. + * + * NOTE Left and right keyboard masks like NX_NEXTLSHIFTKEYMASK exist and + * appear to be in use on Darwin, however they are removed by comment or + * preprocessor with a note that reads "device-dependent (really?)." To + * ensure compatability, we will do this the verbose way. + * + * NOTE The masks for scroll and number lock are set in the key event. + */ + if (keycode == kVK_Shift) { + if (event_mask & kCGEventFlagMaskShift) { + // Process as a key pressed event. + set_modifier_mask(MASK_SHIFT_L); + process_key_pressed(timestamp, event_ref); + } + else { + // Process as a key released event. + unset_modifier_mask(MASK_SHIFT_L); + process_key_released(timestamp, event_ref); + } + } + else if (keycode == kVK_Control) { + if (event_mask & kCGEventFlagMaskControl) { + // Process as a key pressed event. + set_modifier_mask(MASK_CTRL_L); + process_key_pressed(timestamp, event_ref); + } + else { + // Process as a key released event. + unset_modifier_mask(MASK_CTRL_L); + process_key_released(timestamp, event_ref); + } + } + else if (keycode == kVK_Command) { + if (event_mask & kCGEventFlagMaskCommand) { + // Process as a key pressed event. + set_modifier_mask(MASK_META_L); + process_key_pressed(timestamp, event_ref); + } + else { + // Process as a key released event. + unset_modifier_mask(MASK_META_L); + process_key_released(timestamp, event_ref); + } + } + else if (keycode == kVK_Option) { + if (event_mask & kCGEventFlagMaskAlternate) { + // Process as a key pressed event. + set_modifier_mask(MASK_ALT_L); + process_key_pressed(timestamp, event_ref); + } + else { + // Process as a key released event. + unset_modifier_mask(MASK_ALT_L); + process_key_released(timestamp, event_ref); + } + } + else if (keycode == kVK_RightShift) { + if (event_mask & kCGEventFlagMaskShift) { + // Process as a key pressed event. + set_modifier_mask(MASK_SHIFT_R); + process_key_pressed(timestamp, event_ref); + } + else { + // Process as a key released event. + unset_modifier_mask(MASK_SHIFT_R); + process_key_released(timestamp, event_ref); + } + } + else if (keycode == kVK_RightControl) { + if (event_mask & kCGEventFlagMaskControl) { + // Process as a key pressed event. + set_modifier_mask(MASK_CTRL_R); + process_key_pressed(timestamp, event_ref); + } + else { + // Process as a key released event. + unset_modifier_mask(MASK_CTRL_R); + process_key_released(timestamp, event_ref); + } + } + else if (keycode == kVK_RightCommand) { + if (event_mask & kCGEventFlagMaskCommand) { + // Process as a key pressed event. + set_modifier_mask(MASK_META_R); + process_key_pressed(timestamp, event_ref); + } + else { + // Process as a key released event. + unset_modifier_mask(MASK_META_R); + process_key_released(timestamp, event_ref); + } + } + else if (keycode == kVK_RightOption) { + if (event_mask & kCGEventFlagMaskAlternate) { + // Process as a key pressed event. + set_modifier_mask(MASK_ALT_R); + process_key_pressed(timestamp, event_ref); + } + else { + // Process as a key released event. + unset_modifier_mask(MASK_ALT_R); + process_key_released(timestamp, event_ref); + } + } + /* FIXME This should produce a modifier mask for the caps lock key! + else if (keycode == kVK_CapsLock) { + // Process as a key pressed event. + process_key_pressed(timestamp, event_ref); + + // Set the caps-lock flag for release. + caps_down = true; + } + */ +} + +/* These events are totally undocumented for the CGEvent type, but are required to grab media and caps-lock keys. + */ +static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) { + if( CGEventGetType(event_ref) == NX_SYSDEFINED) { + #ifdef USE_OBJC + // Contributed by Iván Munsuri Ibáñez + id event_data = objc_msgSend((id) objc_getClass("NSEvent"), sel_registerName("eventWithCGEvent:"), event_ref); + int subtype = (int) objc_msgSend(event_data, sel_registerName("subtype")); + #else + CFDataRef data = CGEventCreateData(kCFAllocatorDefault, event_ref); + //CFIndex len = CFDataGetLength(data); + UInt8 *buffer = malloc(12); + // CFDataRef cf_data = CFDataCreate(NULL, [nsData bytes], [nsData length]); + CFDataGetBytes(data, CFRangeMake(108, 12), buffer); + UInt32 subtype = CFSwapInt32BigToHost(*((UInt32 *) buffer)); + #endif + if (subtype == 8) { + #ifdef USE_OBJC + // int data = (int) objc_msgSend(event_data, sel_registerName("data1")); + uint16_t data = (uint16_t) objc_msgSend(event_data, sel_registerName("data1")) + #endif + + // int + uint16_t key_code = ((uint16_t)data & 0xFFFF0000) >> 16; + uint16_t key_flags = ((uint16_t)data & 0xFFFF); + //int key_state = (key_flags & 0xFF00) >> 8; + bool key_down = (key_flags & 0x1) > 0; + + if (key_code == NX_KEYTYPE_CAPS_LOCK) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_CapsLock, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } + else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } + else if (key_code == NX_KEYTYPE_SOUND_UP) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_VolumeUp, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } + else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } + else if (key_code == NX_KEYTYPE_SOUND_DOWN) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_VolumeDown, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } + else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } + else if (key_code == NX_KEYTYPE_MUTE) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_Mute, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } + else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } + + else if (key_code == NX_KEYTYPE_EJECT) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_NX_Eject, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } + else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } + else if (key_code == NX_KEYTYPE_PLAY) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_MEDIA_Play, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } + else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } + else if (key_code == NX_KEYTYPE_FAST) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_MEDIA_Next, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } + else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } + else if (key_code == NX_KEYTYPE_REWIND) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_MEDIA_Previous, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } + else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } + } + + #ifndef USE_OBJC + free(buffer); + CFRelease(data); + #endif + } +} + + +static inline void process_button_pressed(uint64_t timestamp, CGEventRef event_ref, uint16_t button) { + // Track the number of clicks. + if (button == click_button && (long int) (timestamp - click_time) <= hook_get_multi_click_time()) { + if (click_count < USHRT_MAX) { + click_count++; + } + else { + logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n", + __FUNCTION__, __LINE__); + } + } + else { + // Reset the click count. + click_count = 1; + + // Set the previous button. + click_button = button; + } + + // Save this events time to calculate the click_count. + click_time = timestamp; + + CGPoint event_point = CGEventGetLocation(event_ref); + + // Populate mouse pressed event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_PRESSED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = click_count; + event.data.mouse.x = event_point.x; + event.data.mouse.y = event_point.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u pressed %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse pressed event. + dispatch_event(&event); +} + +static inline void process_button_released(uint64_t timestamp, CGEventRef event_ref, uint16_t button) { + CGPoint event_point = CGEventGetLocation(event_ref); + + // Populate mouse released event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_RELEASED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = click_count; + event.data.mouse.x = event_point.x; + event.data.mouse.y = event_point.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u released %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse released event. + dispatch_event(&event); + + // If the pressed event was not consumed... + if (event.reserved ^ 0x01 && mouse_dragged != true) { + // Populate mouse clicked event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_CLICKED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = click_count; + event.data.mouse.x = event_point.x; + event.data.mouse.y = event_point.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u clicked %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse clicked event. + dispatch_event(&event); + } + + // Reset the number of clicks. + if (button == click_button && (long int) (event.time - click_time) > hook_get_multi_click_time()) { + // Reset the click count. + click_count = 0; + } +} + +static inline void process_mouse_moved(uint64_t timestamp, CGEventRef event_ref) { + // Reset the click count. + if (click_count != 0 && (long int) (timestamp - click_time) > hook_get_multi_click_time()) { + click_count = 0; + } + + CGPoint event_point = CGEventGetLocation(event_ref); + + // Populate mouse motion event. + event.time = timestamp; + event.reserved = 0x00; + + if (mouse_dragged) { + event.type = EVENT_MOUSE_DRAGGED; + } + else { + event.type = EVENT_MOUSE_MOVED; + } + event.mask = get_modifiers(); + + event.data.mouse.button = MOUSE_NOBUTTON; + event.data.mouse.clicks = click_count; + event.data.mouse.x = event_point.x; + event.data.mouse.y = event_point.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Mouse %s to %u, %u.\n", + __FUNCTION__, __LINE__, mouse_dragged ? "dragged" : "moved", + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse motion event. + dispatch_event(&event); +} + +static inline void process_mouse_wheel(uint64_t timestamp, CGEventRef event_ref) { + // Reset the click count and previous button. + click_count = 1; + click_button = MOUSE_NOBUTTON; + + // Check to see what axis was rotated, we only care about axis 1 for vertical rotation. + // TODO Implement horizontal scrolling by examining axis 2. + // NOTE kCGScrollWheelEventDeltaAxis3 is currently unused. + if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0 + || CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) != 0) { + CGPoint event_point = CGEventGetLocation(event_ref); + + // Populate mouse wheel event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_WHEEL; + event.mask = get_modifiers(); + + event.data.wheel.clicks = click_count; + event.data.wheel.x = event_point.x; + event.data.wheel.y = event_point.y; + + // TODO Figure out if kCGScrollWheelEventDeltaAxis2 causes mouse events with zero rotation. + if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventIsContinuous) == 0) { + // Scrolling data is line-based. + event.data.wheel.type = WHEEL_BLOCK_SCROLL; + } + else { + // Scrolling data is pixel-based. + event.data.wheel.type = WHEEL_UNIT_SCROLL; + } + + // TODO The result of kCGScrollWheelEventIsContinuous may effect this value. + // Calculate the amount based on the Point Delta / Event Delta. Integer sign should always be homogeneous resulting in a positive result. + // NOTE kCGScrollWheelEventFixedPtDeltaAxis1 a floating point value (+0.1/-0.1) that takes acceleration into account. + // NOTE kCGScrollWheelEventPointDeltaAxis1 will not build on OS X < 10.5 + + if(CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0) { + event.data.wheel.amount = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventPointDeltaAxis1) / CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1); + + // Scrolling data uses a fixed-point 16.16 signed integer format (Ex: 1.0 = 0x00010000). + event.data.wheel.rotation = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) * -1; + + } + else if(CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) != 0) { + event.data.wheel.amount = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventPointDeltaAxis2) / CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2); + + // Scrolling data uses a fixed-point 16.16 signed integer format (Ex: 1.0 = 0x00010000). + event.data.wheel.rotation = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) * -1; + } + else { + //Fail Silently if a 3rd axis gets added without changing this section of code. + event.data.wheel.amount = 0; + event.data.wheel.rotation = 0; + } + + + + if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0) { + // Wheel Rotated Up or Down. + event.data.wheel.direction = WHEEL_VERTICAL_DIRECTION; + } + else { // data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight + // Wheel Rotated Left or Right. + event.data.wheel.direction = WHEEL_HORIZONTAL_DIRECTION; + } + + logger(LOG_LEVEL_INFO, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n", + __FUNCTION__, __LINE__, event.data.wheel.type, + event.data.wheel.amount * event.data.wheel.rotation, + event.data.wheel.direction, + event.data.wheel.x, event.data.wheel.y); + + // Fire mouse wheel event. + dispatch_event(&event); + } +} + +CGEventRef hook_event_proc(CGEventTapProxy tap_proxy, CGEventType type, CGEventRef event_ref, void *refcon) { + // Get the local system time in UTC. + gettimeofday(&system_time, NULL); + + // Grab the native event timestap for use later.. + uint64_t timestamp = (uint64_t) CGEventGetTimestamp(event_ref); + + // Get the event class. + switch (type) { + case kCGEventKeyDown: + process_key_pressed(timestamp, event_ref); + break; + + case kCGEventKeyUp: + process_key_released(timestamp, event_ref); + break; + + case kCGEventFlagsChanged: + process_modifier_changed(timestamp, event_ref); + break; + + //b + // case NX_SYSDEFINED: + // process_system_key(timestamp, event_ref); + // break; + + case kCGEventLeftMouseDown: + set_modifier_mask(MASK_BUTTON1); + process_button_pressed(timestamp, event_ref, MOUSE_BUTTON1); + break; + + case kCGEventRightMouseDown: + set_modifier_mask(MASK_BUTTON2); + process_button_pressed(timestamp, event_ref, MOUSE_BUTTON2); + break; + + case kCGEventOtherMouseDown: + // Extra mouse buttons. + if (CGEventGetIntegerValueField(event_ref, kCGMouseEventButtonNumber) < UINT16_MAX) { + uint16_t button = (uint16_t) CGEventGetIntegerValueField(event_ref, kCGMouseEventButtonNumber) + 1; + + // Add support for mouse 4 & 5. + if (button == 4) { + set_modifier_mask(MOUSE_BUTTON4); + } + else if (button == 5) { + set_modifier_mask(MOUSE_BUTTON5); + } + + process_button_pressed(timestamp, event_ref, button); + } + break; + + case kCGEventLeftMouseUp: + unset_modifier_mask(MASK_BUTTON1); + process_button_released(timestamp, event_ref, MOUSE_BUTTON1); + break; + + case kCGEventRightMouseUp: + unset_modifier_mask(MASK_BUTTON2); + process_button_released(timestamp, event_ref, MOUSE_BUTTON2); + break; + + case kCGEventOtherMouseUp: + // Extra mouse buttons. + if (CGEventGetIntegerValueField(event_ref, kCGMouseEventButtonNumber) < UINT16_MAX) { + uint16_t button = (uint16_t) CGEventGetIntegerValueField(event_ref, kCGMouseEventButtonNumber) + 1; + + // Add support for mouse 4 & 5. + if (button == 4) { + unset_modifier_mask(MOUSE_BUTTON4); + } + else if (button == 5) { + unset_modifier_mask(MOUSE_BUTTON5); + } + + process_button_pressed(timestamp, event_ref, button); + } + break; + + + case kCGEventLeftMouseDragged: + case kCGEventRightMouseDragged: + case kCGEventOtherMouseDragged: + // FIXME The drag flag is confusing. Use prev x,y to determine click. + // Set the mouse dragged flag. + mouse_dragged = true; + process_mouse_moved(timestamp, event_ref); + break; + + case kCGEventMouseMoved: + // Set the mouse dragged flag. + mouse_dragged = false; + process_mouse_moved(timestamp, event_ref); + break; + + + case kCGEventScrollWheel: + process_mouse_wheel(timestamp, event_ref); + break; + + + #ifdef USE_DEBUG + case kCGEventNull: + logger(LOG_LEVEL_DEBUG, "%s [%u]: Ignoring kCGEventNull.\n", + __FUNCTION__, __LINE__); + break; + #endif + + default: + // Check for an old OS X bug where the tap seems to timeout for no reason. + // See: http://stackoverflow.com/questions/2969110/cgeventtapcreate-breaks-down-mysteriously-with-key-down-events#2971217 + if (type == (CGEventType) kCGEventTapDisabledByTimeout) { + logger(LOG_LEVEL_WARN, "%s [%u]: CGEventTap timeout!\n", + __FUNCTION__, __LINE__); + + // We need to restart the tap! + restart_tap = true; + CFRunLoopStop(CFRunLoopGetCurrent()); + } + else { + // In theory this *should* never execute. + logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled Darwin event: %#X.\n", + __FUNCTION__, __LINE__, (unsigned int) type); + } + break; + } + + CGEventRef result_ref = NULL; + if (event.reserved ^ 0x01) { + result_ref = event_ref; + } + else { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%#X) (%#p)\n", + __FUNCTION__, __LINE__, type, event_ref); + } + + return result_ref; +} + +IOHOOK_API int hook_run() { + int status = IOHOOK_SUCCESS; + + do { + // Reset the restart flag... + restart_tap = false; + + // Check for accessibility each time we start the loop. + if (is_accessibility_enabled()) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Accessibility API is enabled.\n", + __FUNCTION__, __LINE__); + + // Initialize starting modifiers. + initialize_modifiers(); + + // Try and allocate memory for hook_info. + hook_info *hook = malloc(sizeof(hook_info)); + if (hook != NULL) { + // Setup the event mask to listen for. + #ifdef USE_DEBUG + CGEventMask event_mask = kCGEventMaskForAllEvents; + #else + CGEventMask event_mask = CGEventMaskBit(kCGEventKeyDown) | + CGEventMaskBit(kCGEventKeyUp) | + CGEventMaskBit(kCGEventFlagsChanged) | + + CGEventMaskBit(kCGEventLeftMouseDown) | + CGEventMaskBit(kCGEventLeftMouseUp) | + CGEventMaskBit(kCGEventLeftMouseDragged) | + + CGEventMaskBit(kCGEventRightMouseDown) | + CGEventMaskBit(kCGEventRightMouseUp) | + CGEventMaskBit(kCGEventRightMouseDragged) | + + CGEventMaskBit(kCGEventOtherMouseDown) | + CGEventMaskBit(kCGEventOtherMouseUp) | + CGEventMaskBit(kCGEventOtherMouseDragged) | + + CGEventMaskBit(kCGEventMouseMoved) | + CGEventMaskBit(kCGEventScrollWheel) | + + // NOTE This event is undocumented and used + // for caps-lock release and multi-media keys. + CGEventMaskBit(NX_SYSDEFINED); + #endif + + // Create the event tap. + hook->port = CGEventTapCreate( + kCGSessionEventTap, // kCGHIDEventTap + kCGHeadInsertEventTap, // kCGTailAppendEventTap + kCGEventTapOptionDefault, // kCGEventTapOptionListenOnly See Bug #22 + event_mask, + hook_event_proc, + NULL); + + if (hook->port != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: CGEventTapCreate Successful.\n", + __FUNCTION__, __LINE__); + + // Create the runloop event source from the event tap. + hook->source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, hook->port, 0); + if (hook->source != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: CFMachPortCreateRunLoopSource successful.\n", + __FUNCTION__, __LINE__); + + event_loop = CFRunLoopGetCurrent(); + if (event_loop != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: CFRunLoopGetCurrent successful.\n", + __FUNCTION__, __LINE__); + + // Create run loop observers. + hook->observer = CFRunLoopObserverCreate( + kCFAllocatorDefault, + kCFRunLoopEntry | kCFRunLoopExit, //kCFRunLoopAllActivities, + true, + 0, + hook_status_proc, + NULL); + + if (hook->observer != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: CFRunLoopObserverCreate successful.\n", + __FUNCTION__, __LINE__); + + tis_message = (TISMessage *) calloc(1, sizeof(TISMessage)); + if (tis_message != NULL) { + if (! CFEqual(event_loop, CFRunLoopGetMain())) { + #ifdef USE_WEAK_IMPORT + if (dispatch_sync_f == NULL || dispatch_get_main_queue == NULL) { + #else + *(void **) (&dispatch_sync_f_f) = dlsym(RTLD_DEFAULT, "dispatch_sync_f"); + const char *dlError = dlerror(); + if (dlError != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: %s.\n", + __FUNCTION__, __LINE__, dlError); + } + + *(void **) (&dispatch_get_main_queue_f) = dlsym(RTLD_DEFAULT, "dispatch_get_main_queue"); + dlError = dlerror(); + if (dlError != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: %s.\n", + __FUNCTION__, __LINE__, dlError); + } + + if (dispatch_sync_f_f == NULL || dispatch_get_main_queue_f == NULL) { + #endif + logger(LOG_LEVEL_DEBUG, "%s [%u]: Failed to locate dispatch_sync_f() or dispatch_get_main_queue()!\n", + __FUNCTION__, __LINE__); + + #if ! defined(USE_CARBON_LEGACY) && defined(USE_COREFOUNDATION) + logger(LOG_LEVEL_DEBUG, "%s [%u]: Falling back to runloop signaling.\n", + __FUNCTION__, __LINE__); + + int runloop_status = start_message_port_runloop(); + if (runloop_status != IOHOOK_SUCCESS) { + return runloop_status; + } + #endif + } + } + + // Add the event source and observer to the runloop mode. + CFRunLoopAddSource(event_loop, hook->source, kCFRunLoopDefaultMode); + CFRunLoopAddObserver(event_loop, hook->observer, kCFRunLoopDefaultMode); + + #ifdef USE_OBJC + // Create a garbage collector to handle Cocoa events correctly. + Class NSAutoreleasePool_class = (Class) objc_getClass("NSAutoreleasePool"); + id pool = class_createInstance(NSAutoreleasePool_class, 0); + auto_release_pool = objc_msgSend(pool, sel_registerName("init")); + #endif + + // Start the hook thread runloop. + CFRunLoopRun(); + + + #ifdef USE_OBJC + //objc_msgSend(auto_release_pool, sel_registerName("drain")); + objc_msgSend(auto_release_pool, sel_registerName("release")); + #endif + + // Lock back up until we are done processing the exit. + if (CFRunLoopContainsObserver(event_loop, hook->observer, kCFRunLoopDefaultMode)) { + CFRunLoopRemoveObserver(event_loop, hook->observer, kCFRunLoopDefaultMode); + } + + if (CFRunLoopContainsSource(event_loop, hook->source, kCFRunLoopDefaultMode)) { + CFRunLoopRemoveSource(event_loop, hook->source, kCFRunLoopDefaultMode); + } + + #if ! defined(USE_CARBON_LEGACY) && defined(USE_COREFOUNDATION) + if (! CFEqual(event_loop, CFRunLoopGetMain())) { + #ifdef USE_WEAK_IMPORT + if (dispatch_sync_f == NULL || dispatch_get_main_queue == NULL) { + #else + if (dispatch_sync_f_f == NULL || dispatch_get_main_queue_f == NULL) { + #endif + stop_message_port_runloop(); + } + } + #endif + + // Free the TIS Message. + free(tis_message); + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for TIS message structure!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_OUT_OF_MEMORY; + } + + // Invalidate and free hook observer. + CFRunLoopObserverInvalidate(hook->observer); + CFRelease(hook->observer); + } + else { + // We cant do a whole lot of anything if we cant + // create run loop observer. + logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopObserverCreate failure!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_CREATE_OBSERVER; + } + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopGetCurrent failure!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_GET_RUNLOOP; + } + + // Clean up the event source. + CFRelease(hook->source); + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: CFMachPortCreateRunLoopSource failure!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE; + } + + // Stop the CFMachPort from receiving any more messages. + CFMachPortInvalidate(hook->port); + CFRelease(hook->port); + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to create event port!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_CREATE_EVENT_PORT; + } + + // Free the hook structure. + free(hook); + } + else { + status = IOHOOK_ERROR_OUT_OF_MEMORY; + } + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Accessibility API is disabled!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_AXAPI_DISABLED; + } + } while (restart_tap); + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Something, something, something, complete.\n", + __FUNCTION__, __LINE__); + + return status; +} + +IOHOOK_API int hook_stop() { + int status = IOHOOK_FAILURE; + + CFStringRef mode = CFRunLoopCopyCurrentMode(event_loop); + if (mode != NULL) { + CFRelease(mode); + + // Make sure the tap doesn't restart. + restart_tap = false; + + // Stop the run loop. + CFRunLoopStop(event_loop); + + status = IOHOOK_SUCCESS; + } + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Status: %#X.\n", + __FUNCTION__, __LINE__, status); + + return status; +} diff --git a/event/hook/darwin/input.h b/event/hook/darwin/input.h new file mode 100644 index 0000000..23977e5 --- /dev/null +++ b/event/hook/darwin/input.h @@ -0,0 +1,103 @@ + +#ifndef _included_input_helper +#define _included_input_helper + +#include +#include // For HIToolbox kVK_ keycodes and TIS funcitons. +#ifdef USE_IOKIT + #include +#endif +#include + + +#ifndef USE_IOKIT +// Some of the system key codes that are needed from IOKit. +// #define NX_KEYTYPE_SOUND_UP 0x00 +// #define NX_KEYTYPE_SOUND_DOWN 0x01 +// #define NX_KEYTYPE_MUTE 0x07 + +/* Display controls... +#define NX_KEYTYPE_BRIGHTNESS_UP 0x02 +#define NX_KEYTYPE_BRIGHTNESS_DOWN 0x03 +#define NX_KEYTYPE_CONTRAST_UP 0x0B +#define NX_KEYTYPE_CONTRAST_DOWN 0x0C +#define NX_KEYTYPE_ILLUMINATION_UP 0x15 +#define NX_KEYTYPE_ILLUMINATION_DOWN 0x16 +#define NX_KEYTYPE_ILLUMINATION_TOGGLE 0x17 +*/ + +// #define NX_KEYTYPE_CAPS_LOCK 0x04 +// #define NX_KEYTYPE_HELP 0x05 +// #define NX_POWER_KEY 0x06 + +// #define NX_KEYTYPE_EJECT 0x0E +// #define NX_KEYTYPE_PLAY 0x10 +// #define NX_KEYTYPE_NEXT 0x12 +// #define NX_KEYTYPE_PREVIOUS 0x13 + +/* There is no official fast-forward or rewind scan code support.*/ +// #define NX_KEYTYPE_FAST 0x14 +// #define NX_KEYTYPE_REWIND 0x15 + +#endif + +// These virtual key codes do not appear to be defined anywhere by Apple. +#define kVK_NX_Power 0xE0 | NX_POWER_KEY /* 0xE6 */ +#define kVK_NX_Eject 0xE0 | NX_KEYTYPE_EJECT /* 0xEE */ + +#define kVK_MEDIA_Play 0xE0 | NX_KEYTYPE_PLAY /* 0xF0 */ +#define kVK_MEDIA_Next 0xE0 | NX_KEYTYPE_NEXT /* 0xF1 */ +#define kVK_MEDIA_Previous 0xE0 | NX_KEYTYPE_PREVIOUS /* 0xF2 */ + +#define kVK_RightCommand 0x36 +#define kVK_ContextMenu 0x6E // AKA kMenuPowerGlyph +#define kVK_Undefined 0xFF + +// These button codes do not appear to be defined anywhere by Apple. +#define kVK_LBUTTON kCGMouseButtonLeft +#define kVK_RBUTTON kCGMouseButtonRight +#define kVK_MBUTTON kCGMouseButtonCenter +#define kVK_XBUTTON1 3 +#define kVK_XBUTTON2 4 + +// These button masks do not appear to be defined anywhere by Apple. +#define kCGEventFlagMaskButtonLeft 1 << 0 +#define kCGEventFlagMaskButtonRight 1 << 1 +#define kCGEventFlagMaskButtonCenter 1 << 2 +#define kCGEventFlagMaskXButton1 1 << 3 +#define kCGEventFlagMaskXButton2 1 << 4 + + +/* Check for access to Apples accessibility API. + */ +extern bool is_accessibility_enabled(); + +/* Converts an OSX key code and event mask to the appropriate Unicode character + * representation. + */ +extern UniCharCount keycode_to_unicode(CGEventRef event_ref, UniChar *buffer, UniCharCount size); + +/* Converts an OSX keycode to the appropriate IOHook scancode constant. + */ +extern uint16_t keycode_to_scancode(UInt64 keycode); + +/* Converts a IOHook scancode constant to the appropriate OSX keycode. + */ +extern UInt64 scancode_to_keycode(uint16_t keycode); + + +/* Initialize items required for KeyCodeToKeySym() and KeySymToUnicode() + * functionality. This method is called by OnLibraryLoad() and may need to be + * called in combination with UnloadInputHelper() if the native keyboard layout + * is changed. + */ +extern void load_input_helper(); + +/* De-initialize items required for KeyCodeToKeySym() and KeySymToUnicode() + * functionality. This method is called by OnLibraryUnload() and may need to be + * called in combination with LoadInputHelper() if the native keyboard layout + * is changed. + */ +extern void unload_input_helper(); + +#endif diff --git a/event/hook/darwin/input_c.h b/event/hook/darwin/input_c.h new file mode 100644 index 0000000..298f67f --- /dev/null +++ b/event/hook/darwin/input_c.h @@ -0,0 +1,542 @@ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef USE_COREFOUNDATION + #include +#endif +#ifndef USE_WEAK_IMPORT + #include +#endif + +#include +#include "input.h" +#include "../iohook.h" +#include "../logger_c.h" + +// Current dead key state. +#if defined(USE_CARBON_LEGACY) || defined(USE_COREFOUNDATION) +static UInt32 deadkey_state; +#endif + +// Input source data for the keyboard. +#if defined(USE_CARBON_LEGACY) +static KeyboardLayoutRef prev_keyboard_layout = NULL; +#elif defined(USE_COREFOUNDATION) +static TISInputSourceRef prev_keyboard_layout = NULL; +#endif + +#ifdef USE_WEAK_IMPORT +// Required to dynamically check for AXIsProcessTrustedWithOptions availability. +extern Boolean AXIsProcessTrustedWithOptions(CFDictionaryRef options) __attribute__((weak_import)); +extern CFStringRef kAXTrustedCheckOptionPrompt __attribute__((weak_import)); +#else +static Boolean (*AXIsProcessTrustedWithOptions_t)(CFDictionaryRef); +#endif + +bool is_accessibility_enabled() { + bool is_enabled = false; + + #ifdef USE_WEAK_IMPORT + // Check and make sure assistive devices is enabled. + if (AXIsProcessTrustedWithOptions != NULL) { + // New accessibility API 10.9 and later. + const void * keys[] = { kAXTrustedCheckOptionPrompt }; + const void * values[] = { kCFBooleanTrue }; + + CFDictionaryRef options = CFDictionaryCreate( + kCFAllocatorDefault, + keys, + values, + sizeof(keys) / sizeof(*keys), + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + is_enabled = AXIsProcessTrustedWithOptions(options); + } + #else + // Dynamically load the application services framework for examination. + *(void **) (&AXIsProcessTrustedWithOptions_t) = dlsym(RTLD_DEFAULT, "AXIsProcessTrustedWithOptions"); + const char *dlError = dlerror(); + if (AXIsProcessTrustedWithOptions_t != NULL && dlError == NULL) { + // Check for property CFStringRef kAXTrustedCheckOptionPrompt + void ** kAXTrustedCheckOptionPrompt_t = dlsym(RTLD_DEFAULT, "kAXTrustedCheckOptionPrompt"); + + dlError = dlerror(); + if (kAXTrustedCheckOptionPrompt_t != NULL && dlError == NULL) { + // New accessibility API 10.9 and later. + const void * keys[] = { *kAXTrustedCheckOptionPrompt_t }; + const void * values[] = { kCFBooleanTrue }; + + CFDictionaryRef options = CFDictionaryCreate( + kCFAllocatorDefault, + keys, + values, + sizeof(keys) / sizeof(*keys), + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + is_enabled = (*AXIsProcessTrustedWithOptions_t)(options); + } + } + #endif + else { + #ifndef USE_WEAK_IMPORT + if (dlError != NULL) { + // Could not load the AXIsProcessTrustedWithOptions function! + logger(LOG_LEVEL_DEBUG, "%s [%u]: %s.\n", + __FUNCTION__, __LINE__, dlError); + } + #endif + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Weak import AXIsProcessTrustedWithOptions not found.\n", + __FUNCTION__, __LINE__, dlError); + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Falling back to AXAPIEnabled().\n", + __FUNCTION__, __LINE__, dlError); + + // Old accessibility check 10.8 and older. + is_enabled = AXAPIEnabled(); + } + + return is_enabled; +} + + +UniCharCount keycode_to_unicode(CGEventRef event_ref, UniChar *buffer, UniCharCount size) { + UniCharCount count = 0; + + #if defined(USE_CARBON_LEGACY) + KeyboardLayoutRef curr_keyboard_layout; + void *inputData = NULL; + if (KLGetCurrentKeyboardLayout(&curr_keyboard_layout) == noErr) { + if (KLGetKeyboardLayoutProperty(curr_keyboard_layout, kKLuchrData, (const void **) &inputData) != noErr) { + inputData = NULL; + } + } + #elif defined(USE_COREFOUNDATION) + CFDataRef inputData = NULL; + if (CFEqual(CFRunLoopGetCurrent(), CFRunLoopGetMain())) { + // NOTE The following block must execute on the main runloop, + // Ex: CFEqual(CFRunLoopGetCurrent(), CFRunLoopGetMain()) to avoid + // Exception detected while handling key input and TSMProcessRawKeyCode failed + // (-192) errors. + TISInputSourceRef curr_keyboard_layout = TISCopyCurrentKeyboardLayoutInputSource(); + if (curr_keyboard_layout != NULL && CFGetTypeID(curr_keyboard_layout) == TISInputSourceGetTypeID()) { + CFDataRef data = (CFDataRef) TISGetInputSourceProperty(curr_keyboard_layout, kTISPropertyUnicodeKeyLayoutData); + if (data != NULL && CFGetTypeID(data) == CFDataGetTypeID() && CFDataGetLength(data) > 0) { + inputData = (CFDataRef) data; + } + } + + // Check if the keyboard layout has changed to see if the dead key state needs to be discarded. + if (prev_keyboard_layout != NULL && curr_keyboard_layout != NULL && CFEqual(curr_keyboard_layout, prev_keyboard_layout) == false) { + deadkey_state = 0x00; + } + + // Release the previous keyboard layout. + if (prev_keyboard_layout != NULL) { + CFRelease(prev_keyboard_layout); + prev_keyboard_layout = NULL; + } + + // Set the previous keyboard layout to the current layout. + if (curr_keyboard_layout != NULL) { + prev_keyboard_layout = curr_keyboard_layout; + } + } + #endif + + #if defined(USE_CARBON_LEGACY) || defined(USE_COREFOUNDATION) + if (inputData != NULL) { + #ifdef USE_CARBON_LEGACY + const UCKeyboardLayout *keyboard_layout = (const UCKeyboardLayout *) inputData; + #else + const UCKeyboardLayout *keyboard_layout = (const UCKeyboardLayout*) CFDataGetBytePtr(inputData); + #endif + + if (keyboard_layout != NULL) { + //Extract keycode and modifier information. + CGKeyCode keycode = CGEventGetIntegerValueField(event_ref, kCGKeyboardEventKeycode); + CGEventFlags modifiers = CGEventGetFlags(event_ref); + + // Disable all command modifiers for translation. This is required + // so UCKeyTranslate will provide a keysym for the separate event. + static const CGEventFlags cmd_modifiers = kCGEventFlagMaskCommand | + kCGEventFlagMaskControl | kCGEventFlagMaskAlternate; + modifiers &= ~cmd_modifiers; + + // I don't know why but UCKeyTranslate does not process the + // kCGEventFlagMaskAlphaShift (A.K.A. Caps Lock Mask) correctly. + // We need to basically turn off the mask and process the capital + // letters after UCKeyTranslate(). + bool is_caps_lock = modifiers & kCGEventFlagMaskAlphaShift; + modifiers &= ~kCGEventFlagMaskAlphaShift; + + // Run the translation with the saved deadkey_state. + OSStatus status = UCKeyTranslate( + keyboard_layout, + keycode, + kUCKeyActionDown, //kUCKeyActionDisplay, + (modifiers >> 16) & 0xFF, //(modifiers >> 16) & 0xFF, || (modifiers >> 8) & 0xFF, + LMGetKbdType(), + kNilOptions, //kNilOptions, //kUCKeyTranslateNoDeadKeysMask + &deadkey_state, + size, + &count, + buffer); + + if (status == noErr && count > 0) { + if (is_caps_lock) { + // We *had* a caps lock mask so we need to convert to uppercase. + CFMutableStringRef keytxt = CFStringCreateMutableWithExternalCharactersNoCopy(kCFAllocatorDefault, buffer, count, size, kCFAllocatorNull); + if (keytxt != NULL) { + CFLocaleRef locale = CFLocaleCopyCurrent(); + CFStringUppercase(keytxt, locale); + CFRelease(locale); + CFRelease(keytxt); + } + else { + // There was an problem creating the CFMutableStringRef. + count = 0; + } + } + } + else { + // Make sure the buffer count is zero if an error occurred. + count = 0; + } + } + } + #else + CGEventKeyboardGetUnicodeString(event_ref, size, &count, buffer); + #endif + + // The following codes should not be processed because they are invalid. + if (count == 1) { + switch (buffer[0]) { + case 0x01: // Home + case 0x04: // End + case 0x05: // Help Key + case 0x10: // Function Keys + case 0x0B: // Page Up + case 0x0C: // Page Down + case 0x1F: // Volume Up + count = 0; + } + } + + return count; +} + + +static const uint16_t keycode_scancode_table[][2] = { + /* idx { keycode, scancode }, */ + /* 0 */ { VC_A, kVK_Undefined }, // 0x00 + /* 1 */ { VC_S, kVK_Escape }, // 0x01 + /* 2 */ { VC_D, kVK_ANSI_1 }, // 0x02 + /* 3 */ { VC_F, kVK_ANSI_2 }, // 0x03 + /* 4 */ { VC_H, kVK_ANSI_3 }, // 0x04 + /* 5 */ { VC_G, kVK_ANSI_4 }, // 0x05 + /* 6 */ { VC_Z, kVK_ANSI_5 }, // 0x07 + /* 7 */ { VC_X, kVK_ANSI_6 }, // 0x08 + /* 8 */ { VC_C, kVK_ANSI_7 }, // 0x09 + /* 9 */ { VC_V, kVK_ANSI_8 }, // 0x0A + /* 10 */ { VC_UNDEFINED, kVK_ANSI_9 }, // 0x0B + /* 11 */ { VC_B, kVK_ANSI_0 }, // 0x0C + /* 12 */ { VC_Q, kVK_ANSI_Minus }, // 0x0D + /* 13 */ { VC_W, kVK_ANSI_Equal }, // 0x0E + /* 14 */ { VC_E, kVK_Delete }, // 0x0F + /* 15 */ { VC_R, kVK_Tab }, // 0x10 + /* 16 */ { VC_Y, kVK_ANSI_Q }, // 0x11 + /* 17 */ { VC_T, kVK_ANSI_W }, // 0x12 + /* 18 */ { VC_1, kVK_ANSI_E }, // 0x13 + /* 19 */ { VC_2, kVK_ANSI_R }, // 0x14 + /* 20 */ { VC_3, kVK_ANSI_T }, // 0x15 + /* 21 */ { VC_4, kVK_ANSI_Y }, // 0x16 + /* 22 */ { VC_6, kVK_ANSI_U }, // 0x17 + /* 23 */ { VC_5, kVK_ANSI_I }, // 0x18 + /* 24 */ { VC_EQUALS, kVK_ANSI_O }, // 0x19 + /* 25 */ { VC_9, kVK_ANSI_P }, // 0x19 + /* 26 */ { VC_7, kVK_ANSI_LeftBracket }, // 0x1A + /* 27 */ { VC_MINUS, kVK_ANSI_RightBracket }, // 0x1B + /* 28 */ { VC_8, kVK_Return }, // 0x1C + /* 29 */ { VC_0, kVK_Control }, // 0x1D + /* 30 */ { VC_CLOSE_BRACKET, kVK_ANSI_A }, // 0x1E + /* 31 */ { VC_O, kVK_ANSI_S }, // 0x1F + /* 32 */ { VC_U, kVK_ANSI_D }, // 0x20 + /* 33 */ { VC_OPEN_BRACKET, kVK_ANSI_F }, // 0x21 + /* 34 */ { VC_I, kVK_ANSI_G }, // 0x22 + /* 35 */ { VC_P, kVK_ANSI_H }, // 0x23 + /* 36 */ { VC_ENTER, kVK_ANSI_J }, // 0x24 + /* 37 */ { VC_L, kVK_ANSI_K }, // 0x25 + /* 38 */ { VC_J, kVK_ANSI_L }, // 0x26 + /* 39 */ { VC_QUOTE, kVK_ANSI_Semicolon }, // 0x27 + /* 40 */ { VC_K, kVK_ANSI_Quote }, // 0x28 + /* 41 */ { VC_SEMICOLON, kVK_ANSI_Grave }, // 0x29 + /* 42 */ { VC_BACK_SLASH, kVK_Shift }, // 0x2A + /* 43 */ { VC_COMMA, kVK_ANSI_Backslash }, // 0x2B + /* 44 */ { VC_SLASH, kVK_ANSI_Z }, // 0x2C + /* 45 */ { VC_N, kVK_ANSI_X }, // 0x2D + /* 46 */ { VC_M, kVK_ANSI_C }, // 0x2E + /* 47 */ { VC_PERIOD, kVK_ANSI_V }, // 0x2F + /* 48 */ { VC_TAB, kVK_ANSI_B }, // 0x30 + /* 49 */ { VC_SPACE, kVK_ANSI_N }, // 0x31 + /* 50 */ { VC_BACKQUOTE, kVK_ANSI_M }, // 0x32 + /* 51 */ { VC_BACKSPACE, kVK_ANSI_Comma }, // 0x33 + /* 52 */ { VC_UNDEFINED, kVK_ANSI_Period }, // 0x34 + /* 53 */ { VC_ESCAPE, kVK_ANSI_Slash }, // 0x35 + /* 54 */ { VC_META_R, kVK_RightShift }, // 0x36 + /* 55 */ { VC_META_L, kVK_ANSI_KeypadMultiply }, // 0x37 + /* 56 */ { VC_SHIFT_L, kVK_Option }, // 0x38 + /* 57 */ { VC_CAPS_LOCK, kVK_Space }, // 0x39 + /* 58 */ { VC_ALT_L, kVK_CapsLock }, // 0x3A + /* 59 */ { VC_CONTROL_L, kVK_F1 }, // 0x3B + /* 60 */ { VC_SHIFT_R, kVK_F2 }, // 0x3C + /* 61 */ { VC_ALT_R, kVK_F3 }, // 0x3D + /* 62 */ { VC_CONTROL_R, kVK_F4 }, // 0x3E + /* 63 */ { VC_UNDEFINED, kVK_F5 }, // 0x3F + /* 64 */ { VC_F17, kVK_F6 }, // 0x40 + /* 65 */ { VC_KP_SEPARATOR, kVK_F7 }, // 0x41 + /* 66 */ { VC_UNDEFINED, kVK_F8 }, // 0x42 + /* 67 */ { VC_KP_MULTIPLY, kVK_F9 }, // 0x43 + /* 68 */ { VC_UNDEFINED, kVK_F10 }, // 0x44 + /* 69 */ { VC_KP_ADD, kVK_ANSI_KeypadClear }, // 0x45 + /* 70 */ { VC_UNDEFINED, kVK_Undefined }, // 0x46 + /* 71 */ { VC_NUM_LOCK, kVK_ANSI_Keypad7 }, // 0x47 + /* 72 */ { VC_VOLUME_UP, kVK_ANSI_Keypad8 }, // 0x48 + /* 73 */ { VC_VOLUME_DOWN, kVK_ANSI_Keypad9 }, // 0x49 + /* 74 */ { VC_VOLUME_MUTE, kVK_ANSI_KeypadMinus }, // 0x4A + /* 75 */ { VC_KP_DIVIDE, kVK_ANSI_Keypad4 }, // 0x4B + /* 76 */ { VC_KP_ENTER, kVK_ANSI_Keypad5 }, // 0x4C + /* 77 */ { VC_UNDEFINED, kVK_ANSI_Keypad6 }, // 0x4D + /* 78 */ { VC_KP_SUBTRACT, kVK_ANSI_KeypadPlus }, // 0x4E + /* 79 */ { VC_F18, kVK_ANSI_Keypad1 }, // 0x4F + /* 80 */ { VC_F19, kVK_ANSI_Keypad2 }, // 0x50 + /* 81 */ { VC_KP_EQUALS, kVK_ANSI_Keypad3 }, // 0x51 + /* 82 */ { VC_KP_0, kVK_ANSI_Keypad0 }, // 0x52 + /* 83 */ { VC_KP_1, kVK_ANSI_KeypadDecimal }, // 0x53 + /* 84 */ { VC_KP_2, kVK_Undefined }, // 0x54 + /* 85 */ { VC_KP_3, kVK_Undefined }, // 0x55 + /* 86 */ { VC_KP_4, kVK_Undefined }, // 0x56 + /* 87 */ { VC_KP_5, kVK_F11 }, // 0x57 + /* 88 */ { VC_KP_6, kVK_F12 }, // 0x58 + /* 89 */ { VC_KP_7, kVK_Undefined }, // 0x59 + /* 90 */ { VC_F20, kVK_Undefined }, // 0x5A + /* 91 */ { VC_KP_8, kVK_F13 }, // 0x5B + /* 92 */ { VC_KP_9, kVK_F14 }, // 0x5C + /* 93 */ { VC_YEN, kVK_F15 }, // 0x5D + /* 94 */ { VC_UNDERSCORE, kVK_Undefined }, // 0x5E + /* 95 */ { VC_KP_COMMA, kVK_Undefined }, // 0x5F + /* 96 */ { VC_F5, kVK_Undefined }, // 0x60 + /* 97 */ { VC_F6, kVK_Undefined }, // 0x61 + /* 98 */ { VC_F7, kVK_Undefined }, // 0x62 + /* 99 */ { VC_F3, kVK_F16 }, // 0x63 + /* 100 */ { VC_F8, kVK_F17 }, // 0x64 + /* 101 */ { VC_F9, kVK_F18 }, // 0x65 + /* 102 */ { VC_UNDEFINED, kVK_F19 }, // 0x66 + /* 103 */ { VC_F11, kVK_F20 }, // 0x67 + /* 104 */ { VC_KATAKANA, kVK_Undefined }, // 0x68 + /* 105 */ { VC_F13, kVK_Undefined }, // 0x69 + /* 106 */ { VC_F16, kVK_Undefined }, // 0x6A + /* 107 */ { VC_F14, kVK_Undefined }, // 0x6B + /* 108 */ { VC_UNDEFINED, kVK_Undefined }, // 0x6C FIXME kVK_JIS_Eisu same as Caps Lock ? + /* 109 */ { VC_F10, kVK_Undefined }, // 0x6D + /* 110 */ { VC_UNDEFINED, kVK_Undefined }, // 0x6E + /* 111 */ { VC_F12, kVK_Undefined }, // 0x6F + /* 112 */ { VC_UNDEFINED, kVK_JIS_Kana }, // 0x70 + /* 113 */ { VC_F15, kVK_Undefined }, // 0x71 + /* 114 */ { VC_INSERT, kVK_Undefined }, // 0x72 + /* 115 */ { VC_HOME, kVK_JIS_Underscore }, // 0x73 + /* 116 */ { VC_PAGE_UP, kVK_Undefined }, // 0x74 + /* 117 */ { VC_DELETE, kVK_Undefined }, // 0x75 + /* 118 */ { VC_F4, kVK_Undefined }, // 0x76 + /* 119 */ { VC_END, kVK_Undefined }, // 0x77 + /* 120 */ { VC_F2, kVK_Undefined }, // 0x78 + /* 121 */ { VC_PAGE_DOWN, kVK_Undefined }, // 0x79 + /* 122 */ { VC_F1, kVK_Undefined }, // 0x7A + /* 123 */ { VC_LEFT, kVK_Undefined }, // 0x7B + /* 124 */ { VC_RIGHT, kVK_Undefined }, // 0x7C + /* 125 */ { VC_DOWN, kVK_JIS_Yen }, // 0x7D + /* 126 */ { VC_UP, kVK_JIS_KeypadComma }, // 0x7E + /* 127 */ { VC_UNDEFINED, kVK_Undefined }, // 0x7F + + // No Offset Offset (i & 0x007F) + 128 + + /* 128 */ { VC_UNDEFINED, kVK_Undefined }, // 0x80 + /* 129 */ { VC_UNDEFINED, kVK_Undefined }, // 0x81 + /* 130 */ { VC_UNDEFINED, kVK_Undefined }, // 0x82 + /* 131 */ { VC_UNDEFINED, kVK_Undefined }, // 0x83 + /* 132 */ { VC_UNDEFINED, kVK_Undefined }, // 0x84 + /* 133 */ { VC_UNDEFINED, kVK_Undefined }, // 0x85 + /* 134 */ { VC_UNDEFINED, kVK_Undefined }, // 0x86 + /* 135 */ { VC_UNDEFINED, kVK_Undefined }, // 0x87 + /* 136 */ { VC_UNDEFINED, kVK_Undefined }, // 0x88 + /* 137 */ { VC_UNDEFINED, kVK_Undefined }, // 0x89 + /* 138 */ { VC_UNDEFINED, kVK_Undefined }, // 0x8A + /* 139 */ { VC_UNDEFINED, kVK_Undefined }, // 0x8B + /* 140 */ { VC_UNDEFINED, kVK_Undefined }, // 0x8C + /* 141 */ { VC_UNDEFINED, kVK_ANSI_KeypadEquals }, // 0x8D + /* 142 */ { VC_UNDEFINED, kVK_Undefined }, // 0x8E + /* 143 */ { VC_UNDEFINED, kVK_Undefined }, // 0x8F + /* 144 */ { VC_UNDEFINED, kVK_MEDIA_Previous }, // 0x90 + /* 145 */ { VC_UNDEFINED, kVK_Undefined }, // 0x91 + /* 146 */ { VC_UNDEFINED, kVK_Undefined }, // 0x92 + /* 147 */ { VC_UNDEFINED, kVK_Undefined }, // 0x93 + /* 148 */ { VC_UNDEFINED, kVK_Undefined }, // 0x94 + /* 149 */ { VC_UNDEFINED, kVK_Undefined }, // 0x95 + /* 150 */ { VC_UNDEFINED, kVK_Undefined }, // 0x96 + /* 151 */ { VC_UNDEFINED, kVK_Undefined }, // 0x97 + /* 152 */ { VC_UNDEFINED, kVK_Undefined }, // 0x98 + /* 153 */ { VC_UNDEFINED, kVK_MEDIA_Next }, // 0x99 + /* 154 */ { VC_UNDEFINED, kVK_Undefined }, // 0x9A + /* 155 */ { VC_UNDEFINED, kVK_Undefined }, // 0x9B + /* 156 */ { VC_UNDEFINED, kVK_ANSI_KeypadEnter }, // 0x9C + /* 157 */ { VC_UNDEFINED, kVK_RightControl }, // 0x9D + /* 158 */ { VC_UNDEFINED, kVK_Undefined }, // 0x9E + /* 159 */ { VC_UNDEFINED, kVK_Undefined }, // 0x9F + /* 160 */ { VC_UNDEFINED, kVK_Mute }, // 0xA0 + /* 161 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA1 + /* 162 */ { VC_UNDEFINED, kVK_MEDIA_Play }, // 0xA2 + /* 163 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA3 + /* 164 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA4 + /* 165 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA5 + /* 166 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA6 + /* 167 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA7 + /* 168 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA8 + /* 169 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA9 + /* 170 */ { VC_UNDEFINED, kVK_Undefined }, // 0xAA + /* 171 */ { VC_UNDEFINED, kVK_Undefined }, // 0xAB + /* 172 */ { VC_UNDEFINED, kVK_NX_Eject }, // 0xAC + /* 173 */ { VC_UNDEFINED, kVK_Undefined }, // 0xAD + /* 174 */ { VC_UNDEFINED, kVK_VolumeDown }, // 0xAE + /* 175 */ { VC_UNDEFINED, kVK_Undefined }, // 0xAF + /* 176 */ { VC_UNDEFINED, kVK_VolumeUp }, // 0xB0 + /* 177 */ { VC_UNDEFINED, kVK_Undefined }, // 0xB1 + /* 178 */ { VC_UNDEFINED, kVK_Undefined }, // 0xB2 + /* 179 */ { VC_UNDEFINED, kVK_Undefined }, // 0xB3 + /* 180 */ { VC_UNDEFINED, kVK_Undefined }, // 0xB4 + /* 181 */ { VC_UNDEFINED, kVK_ANSI_KeypadDivide }, // 0xB5 + /* 182 */ { VC_UNDEFINED, kVK_Undefined }, // 0xB6 + /* 183 */ { VC_UNDEFINED, kVK_Undefined }, // 0xB7 + /* 184 */ { VC_UNDEFINED, kVK_RightOption }, // 0xB8 + /* 185 */ { VC_UNDEFINED, kVK_Undefined }, // 0xB9 + /* 186 */ { VC_UNDEFINED, kVK_Undefined }, // 0xBA + /* 187 */ { VC_UNDEFINED, kVK_Undefined }, // 0xBB + /* 188 */ { VC_UNDEFINED, kVK_Undefined }, // 0xBC + /* 189 */ { VC_UNDEFINED, kVK_Undefined }, // 0xBD + /* 190 */ { VC_UNDEFINED, kVK_Undefined }, // 0xBE + /* 191 */ { VC_UNDEFINED, kVK_Undefined }, // 0xBF + /* 192 */ { VC_UNDEFINED, kVK_Undefined }, // 0xC0 + /* 193 */ { VC_UNDEFINED, kVK_Undefined }, // 0xC1 + /* 194 */ { VC_UNDEFINED, kVK_Undefined }, // 0xC2 + /* 195 */ { VC_UNDEFINED, kVK_Undefined }, // 0xC3 + /* 196 */ { VC_UNDEFINED, kVK_Undefined }, // 0xC4 + /* 197 */ { VC_UNDEFINED, kVK_Undefined }, // 0xC5 + /* 198 */ { VC_UNDEFINED, kVK_Undefined }, // 0xC6 + /* 199 */ { VC_UNDEFINED, kVK_Home }, // 0xC7 + /* 200 */ { VC_UNDEFINED, kVK_UpArrow }, // 0xC8 + /* 201 */ { VC_UNDEFINED, kVK_PageUp }, // 0xC9 + /* 202 */ { VC_UNDEFINED, kVK_Undefined }, // 0xCA + /* 203 */ { VC_UNDEFINED, kVK_LeftArrow }, // 0xCB + /* 204 */ { VC_UNDEFINED, kVK_Undefined }, // 0xCC + /* 205 */ { VC_UNDEFINED, kVK_RightArrow }, // 0xCD + /* 206 */ { VC_UNDEFINED, kVK_Undefined }, // 0xCE + /* 207 */ { VC_UNDEFINED, kVK_End }, // 0xCF + /* 208 */ { VC_UNDEFINED, kVK_DownArrow }, // 0xD0 + /* 209 */ { VC_UNDEFINED, kVK_PageDown }, // 0xD1 + /* 210 */ { VC_UNDEFINED, kVK_Help }, // 0xD2 + /* 211 */ { VC_UNDEFINED, kVK_ForwardDelete }, // 0xD3 + /* 212 */ { VC_UNDEFINED, kVK_Undefined }, // 0xD4 + /* 213 */ { VC_UNDEFINED, kVK_Undefined }, // 0xD5 + /* 214 */ { VC_UNDEFINED, kVK_Undefined }, // 0xD6 + /* 215 */ { VC_UNDEFINED, kVK_Undefined }, // 0xD7 + /* 216 */ { VC_UNDEFINED, kVK_Undefined }, // 0xD8 + /* 217 */ { VC_UNDEFINED, kVK_Undefined }, // 0xD9 + /* 218 */ { VC_UNDEFINED, kVK_Undefined }, // 0xDA + /* 219 */ { VC_UNDEFINED, kVK_Command }, // 0xDB + /* 220 */ { VC_UNDEFINED, kVK_RightCommand }, // 0xDC + /* 221 */ { VC_UNDEFINED, kVK_Undefined }, // 0xDD + /* 222 */ { VC_UNDEFINED, kVK_NX_Power }, // 0xDE + /* 223 */ { VC_UNDEFINED, kVK_Undefined }, // 0xDF + /* 224 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE0 + /* 225 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE1 + /* 226 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE2 + /* 227 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE3 + /* 228 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE4 + /* 229 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE5 + /* 230 */ { VC_POWER, kVK_Undefined }, // 0xE6 + /* 231 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE7 + /* 232 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE8 + /* 233 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE9 + /* 234 */ { VC_UNDEFINED, kVK_Undefined }, // 0xEA + /* 235 */ { VC_UNDEFINED, kVK_Undefined }, // 0xEB + /* 236 */ { VC_UNDEFINED, kVK_Undefined }, // 0xEC + /* 237 */ { VC_UNDEFINED, kVK_Undefined }, // 0xED + /* 238 */ { VC_MEDIA_EJECT, kVK_Undefined }, // 0xEE + /* 239 */ { VC_UNDEFINED, kVK_Undefined }, // 0xEF + /* 240 */ { VC_MEDIA_PLAY, kVK_Undefined }, // 0xF0 + /* 241 */ { VC_MEDIA_NEXT, kVK_Undefined }, // 0xF1 + /* 242 */ { VC_MEDIA_PREVIOUS, kVK_Undefined }, // 0xF2 + /* 243 */ { VC_UNDEFINED, kVK_Undefined }, // 0xF3 + /* 244 */ { VC_UNDEFINED, kVK_Undefined }, // 0xF4 + /* 245 */ { VC_UNDEFINED, kVK_Undefined }, // 0xF5 + /* 246 */ { VC_UNDEFINED, kVK_Undefined }, // 0xF6 + /* 247 */ { VC_UNDEFINED, kVK_Undefined }, // 0xF7 + /* 248 */ { VC_UNDEFINED, kVK_Undefined }, // 0xF8 + /* 249 */ { VC_UNDEFINED, kVK_Undefined }, // 0xF9 + /* 250 */ { VC_UNDEFINED, kVK_Undefined }, // 0xFA + /* 251 */ { VC_UNDEFINED, kVK_Undefined }, // 0xFB + /* 252 */ { VC_UNDEFINED, kVK_Undefined }, // 0xFC + /* 253 */ { VC_UNDEFINED, kVK_Undefined }, // 0xFD + /* 254 */ { VC_UNDEFINED, kVK_Undefined }, // 0xFE + /* 255 */ { VC_UNDEFINED, kVK_Undefined }, // 0xFF +}; + +uint16_t keycode_to_scancode(UInt64 keycode) { + uint16_t scancode = VC_UNDEFINED; + + // Bound check 0 <= keycode < 256 + if (keycode < sizeof(keycode_scancode_table) / sizeof(keycode_scancode_table[0])) { + scancode = keycode_scancode_table[keycode][0]; + } + + return scancode; +} + +UInt64 scancode_to_keycode(uint16_t scancode) { + UInt64 keycode = kVK_Undefined; + + // Bound check 0 <= keycode < 128 + if (scancode < 128) { + keycode = keycode_scancode_table[scancode][1]; + } + else { + // Calculate the upper offset. + unsigned short i = (scancode & 0x007F) | 0x80; + + if (i < sizeof(keycode_scancode_table) / sizeof(keycode_scancode_table[1])) { + keycode = keycode_scancode_table[i][1]; + } + } + + return keycode; +} + +void load_input_helper() { + #if defined(USE_CARBON_LEGACY) || defined(USE_COREFOUNDATION) + // Start with a fresh dead key state. + //curr_deadkey_state = 0; + #endif +} + +void unload_input_helper() { + #if defined(USE_CARBON_LEGACY) || defined(USE_COREFOUNDATION) + if (prev_keyboard_layout != NULL) { + // Cleanup tracking of the previous layout. + CFRelease(prev_keyboard_layout); + prev_keyboard_layout = NULL; + } + #endif +} diff --git a/event/hook/darwin/properties_c.h b/event/hook/darwin/properties_c.h new file mode 100644 index 0000000..0a7679c --- /dev/null +++ b/event/hook/darwin/properties_c.h @@ -0,0 +1,522 @@ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifdef USE_CARBON_LEGACY + #include +#endif +#ifdef USE_COREFOUNDATION + #include +#endif +#ifdef USE_IOKIT + #include + #include +#endif + +#include +#include "../iohook.h" +#include "input.h" +// #include "../logger_c.h" + +IOHOOK_API screen_data* hook_create_screen_info(unsigned char *count) { + CGError status = kCGErrorFailure; + screen_data* screens = NULL; + + // Initialize count to zero. + *count = 0; + + // Allocate memory to hold each display id. We will just allocate our MAX + // because its only about 1K of memory. + // TODO This can probably be realistically cut to something like 16 or 32.... + // If you have more than 32 monitors, send me a picture and make a donation ;) + CGDirectDisplayID *display_ids = malloc(sizeof(CGDirectDisplayID) * UCHAR_MAX); + if (display_ids != NULL) { + // NOTE Pass UCHAR_MAX to make sure uint32_t doesn't overflow uint8_t. + // TOOD Test/Check whether CGGetOnlineDisplayList is more suitable... + status = CGGetActiveDisplayList(UCHAR_MAX, display_ids, (uint32_t *) count); + + // If there is no error and at least one monitor. + if (status == kCGErrorSuccess && *count > 0) { + logger(LOG_LEVEL_INFO, "%s [%u]: CGGetActiveDisplayList: %li.\n", + __FUNCTION__, __LINE__, *count); + + // Allocate memory for the number of screens found. + screens = malloc(sizeof(screen_data) * (*count)); + if (screens != NULL) { + uint8_t i; + for (i = 0; i < *count; i++) { + //size_t width = CGDisplayPixelsWide(display_ids[i]); + //size_t height = CGDisplayPixelsHigh(display_ids[i]); + CGRect boundsDisp = CGDisplayBounds(display_ids[i]); + if (boundsDisp.size.width > 0 && boundsDisp.size.height > 0) { + screens[i] = (screen_data) { + .number = i + 1, + //TODO: make sure we follow the same convention for the origin + //in all other platform implementations (upper-left) + //TODO: document the approach with examples in order to show different + //cases -> different resolutions (secondary monitors origin might be + //negative) + .x = boundsDisp.origin.x, + .y = boundsDisp.origin.y, + .width = boundsDisp.size.width, + .height = boundsDisp.size.height + }; + } + } + } + } + else { + logger(LOG_LEVEL_INFO, "%s [%u]: multiple_get_screen_info failed: %ld. Fallback.\n", + __FUNCTION__, __LINE__, status); + + size_t width = CGDisplayPixelsWide(CGMainDisplayID()); + size_t height = CGDisplayPixelsHigh(CGMainDisplayID()); + + if (width > 0 && height > 0) { + screens = malloc(sizeof(screen_data)); + + if (screens != NULL) { + *count = 1; + screens[0] = (screen_data) { + .number = 1, + .x = 0, + .y = 0, + .width = width, + .height = height + }; + } + } + } + + // Free the id's after we are done. + free(display_ids); + } + + return screens; +} + +/* + * Apple's documentation is not very good. I was finally able to find this + * information after many hours of googling. Value is the slider value in the + * system preferences. That value * 15 is the rate in MS. 66 / the value is the + * chars per second rate. + * + * Value MS Char/Sec + * + * 1 15 66 * Out of standard range * + * + * 2 30 33 + * 6 90 11 + * 12 180 5.5 + * 30 450 2.2 + * 60 900 1.1 + * 90 1350 0.73 + * 120 1800 0.55 + * + * V = MS / 15 + * V = 66 / CharSec + * + * MS = V * 15 + * MS = (66 / CharSec) * 15 + * + * CharSec = 66 / V + * CharSec = 66 / (MS / 15) + */ + +IOHOOK_API long int hook_get_auto_repeat_rate() { + #if defined USE_IOKIT || defined USE_COREFOUNDATION || defined USE_CARBON_LEGACY + bool successful = false; + SInt64 rate; + #endif + + long int value = -1; + + #ifdef USE_IOKIT + if (!successful) { + io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kIOHIDSystemClass)); + if (service) { + kern_return_t kren_ret = kIOReturnError; + io_connect_t connection; + + kren_ret = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &connection); + if (kren_ret == kIOReturnSuccess) { + IOByteCount size = sizeof(rate); + + kren_ret = IOHIDGetParameter(connection, CFSTR(kIOHIDKeyRepeatKey), (IOByteCount) sizeof(rate), &rate, &size); + if (kren_ret == kIOReturnSuccess) { + /* This is in some undefined unit of time that if we happen + * to multiply by 900 gives us the time in milliseconds. We + * add 0.5 to the result so that when we cast to long we + * actually get a rounded result. Saves the math.h depend. + * + * 33,333,333.0 / 1000.0 / 1000.0 / 1000.0 == 0.033333333 * Fast * + * 100,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 0.1 + * 200,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 0.2 + * 500,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 0.5 + * 1,000,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 1 + * 1,500,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 1.5 + * 2,000,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 2 * Slow * + */ + value = (long) (900.0 * ((double) rate) / 1000.0 / 1000.0 / 1000.0 + 0.5); + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: IOHIDGetParameter: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + } + #endif + + #ifdef USE_COREFOUNDATION + if (!successful) { + CFTypeRef pref_val = CFPreferencesCopyValue(CFSTR("KeyRepeat"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + if (pref_val != NULL && CFGetTypeID(pref_val) == CFNumberGetTypeID()) { + if (CFNumberGetValue((CFNumberRef) pref_val, kCFNumberSInt32Type, &rate)) { + // This is the slider value, we must multiply by 15 to convert to milliseconds. + value = (long) rate * 15; + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: CFPreferencesCopyValue: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + #endif + + #ifdef USE_CARBON_LEGACY + if (!successful) { + // Apple documentation states that value is in 'ticks'. I am not sure + // what that means, but it looks a lot like the arbitrary slider value. + rate = LMGetKeyRepThresh(); + if (rate > -1) { + /* This is the slider value, we must multiply by 15 to convert to + * milliseconds. + */ + value = (long) rate * 15; + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: LMGetKeyRepThresh: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + #endif + + return value; +} + +IOHOOK_API long int hook_get_auto_repeat_delay() { + #if defined USE_IOKIT || defined USE_COREFOUNDATION || defined USE_CARBON_LEGACY + bool successful = false; + SInt64 delay; + #endif + + long int value = -1; + + #ifdef USE_IOKIT + if (!successful) { + io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kIOHIDSystemClass)); + if (service) { + kern_return_t kren_ret = kIOReturnError; + io_connect_t connection; + + kren_ret = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &connection); + if (kren_ret == kIOReturnSuccess) { + IOByteCount size = sizeof(delay); + + kren_ret = IOHIDGetParameter(connection, CFSTR(kIOHIDInitialKeyRepeatKey), (IOByteCount) sizeof(delay), &delay, &size); + if (kren_ret == kIOReturnSuccess) { + /* This is in some undefined unit of time that if we happen + * to multiply by 900 gives us the time in milliseconds. We + * add 0.5 to the result so that when we cast to long we + * actually get a rounded result. Saves the math.h depend. + * + * 33,333,333.0 / 1000.0 / 1000.0 / 1000.0 == 0.033333333 * Fast * + * 100,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 0.1 + * 200,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 0.2 + * 500,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 0.5 + * 1,000,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 1 + * 1,500,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 1.5 + * 2,000,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 2 * Slow * + */ + value = (long) (900.0 * ((double) delay) / 1000.0 / 1000.0 / 1000.0 + 0.5); + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: IOHIDGetParameter: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + } + #endif + + #ifdef USE_COREFOUNDATION + if (!successful) { + CFTypeRef pref_val = CFPreferencesCopyValue(CFSTR("InitialKeyRepeat"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + if (pref_val != NULL && CFGetTypeID(pref_val) == CFNumberGetTypeID()) { + if (CFNumberGetValue((CFNumberRef) pref_val, kCFNumberSInt32Type, &delay)) { + // This is the slider value, we must multiply by 15 to convert to + // milliseconds. + value = (long) delay * 15; + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: CFPreferencesCopyValue: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + #endif + + #ifdef USE_CARBON_LEGACY + if (!successful) { + // Apple documentation states that value is in 'ticks'. I am not sure + // what that means, but it looks a lot like the arbitrary slider value. + delay = LMGetKeyThresh(); + if (delay > -1) { + // This is the slider value, we must multiply by 15 to convert to + // milliseconds. + value = (long) delay * 15; + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: LMGetKeyThresh: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + #endif + + return value; +} + +IOHOOK_API long int hook_get_pointer_acceleration_multiplier() { + #if defined USE_IOKIT || defined USE_COREFOUNDATION + bool successful = false; + double multiplier; + #endif + + long int value = -1; + + #ifdef USE_IOKIT + if (!successful) { + io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kIOHIDSystemClass)); + + if (service) { + kern_return_t kren_ret = kIOReturnError; + io_connect_t connection; + + kren_ret = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &connection); + if (kren_ret == kIOReturnSuccess) { + // IOByteCount size = sizeof(multiplier); + + kren_ret = IOHIDGetAccelerationWithKey(connection, CFSTR(kIOHIDMouseAccelerationType), &multiplier); + if (kren_ret == kIOReturnSuccess) { + // Calculate the greatest common factor. + + unsigned long denominator = 1000000, d = denominator; + unsigned long numerator = multiplier * denominator, gcf = numerator; + + while (d != 0) { + unsigned long i = gcf % d; + gcf = d; + d = i; + } + + value = denominator / gcf; + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: IOHIDGetAccelerationWithKey: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + } + #endif + + #ifdef USE_COREFOUNDATION + if (!successful) { + CFTypeRef pref_val = CFPreferencesCopyValue(CFSTR("com.apple.mouse.scaling"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + if (pref_val != NULL && CFGetTypeID(pref_val) == CFNumberGetTypeID()) { + if (CFNumberGetValue((CFNumberRef) pref_val, kCFNumberSInt32Type, &multiplier)) { + value = (long) multiplier; + + logger(LOG_LEVEL_INFO, "%s [%u]: CFPreferencesCopyValue: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + #endif + + return value; +} + +IOHOOK_API long int hook_get_pointer_acceleration_threshold() { + #if defined USE_COREFOUNDATION + bool successful = false; + SInt32 threshold; + #endif + + long int value = -1; + + #ifdef USE_COREFOUNDATION + if (!successful) { + CFTypeRef pref_val = CFPreferencesCopyValue(CFSTR("mouseDriverMaxSpeed"), CFSTR("com.apple.universalaccess"), kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + if (pref_val != NULL && CFGetTypeID(pref_val) == CFNumberGetTypeID()) { + if (CFNumberGetValue((CFNumberRef) pref_val, kCFNumberSInt32Type, &threshold)) { + value = (long) threshold; + + logger(LOG_LEVEL_INFO, "%s [%u]: CFPreferencesCopyValue: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + #endif + + return value; +} + +IOHOOK_API long int hook_get_pointer_sensitivity() { + #ifdef USE_IOKIT + bool successful = false; + double sensitivity; + #endif + + long int value = -1; + + #ifdef USE_IOKIT + if (!successful) { + io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kIOHIDSystemClass)); + + if (service) { + kern_return_t kren_ret = kIOReturnError; + io_connect_t connection; + + kren_ret = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &connection); + if (kren_ret == kIOReturnSuccess) { + // IOByteCount size = sizeof(multiplier); + + kren_ret = IOHIDGetAccelerationWithKey(connection, CFSTR(kIOHIDMouseAccelerationType), &sensitivity); + if (kren_ret == kIOReturnSuccess) { + // Calculate the greatest common factor. + + unsigned long denominator = 1000000, d = denominator; + unsigned long numerator = sensitivity * denominator, gcf = numerator; + + while (d != 0) { + unsigned long i = gcf % d; + gcf = d; + d = i; + } + + value = numerator / gcf; + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: IOHIDGetAccelerationWithKey: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + } + #endif + + return value; +} + +IOHOOK_API long int hook_get_multi_click_time() { + #if defined USE_IOKIT || defined USE_COREFOUNDATION || defined USE_CARBON_LEGACY + bool successful = false; + #if defined USE_IOKIT || defined USE_CARBON_LEGACY + // This needs to be defined only if we have USE_IOKIT or USE_CARBON_LEGACY. + SInt64 time; + #endif + #endif + + long int value = -1; + + #ifdef USE_IOKIT + if (!successful) { + io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kIOHIDSystemClass)); + if (service) { + kern_return_t kren_ret = kIOReturnError; + io_connect_t connection; + + kren_ret = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &connection); + if (kren_ret == kIOReturnSuccess) { + IOByteCount size = sizeof(time); + + kren_ret = IOHIDGetParameter(connection, CFSTR(kIOHIDClickTimeKey), (IOByteCount) sizeof(time), &time, &size); + if (kren_ret == kIOReturnSuccess) { + /* This is in some undefined unit of time that if we happen + * to multiply by 900 gives us the time in milliseconds. We + * add 0.5 to the result so that when we cast to long we + * actually get a rounded result. Saves the math.h depend. + */ + value = (long) (900.0 * ((double) time) / 1000.0 / 1000.0 / 1000.0 + 0.5); + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: IOHIDGetParameter: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + } + #endif + + #ifdef USE_COREFOUNDATION + if (!successful) { + Float32 clicktime; + CFTypeRef pref_val = CFPreferencesCopyValue(CFSTR("com.apple.mouse.doubleClickThreshold"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + if (pref_val != NULL && CFGetTypeID(pref_val) == CFNumberGetTypeID()) { + if (CFNumberGetValue((CFNumberRef) pref_val, kCFNumberFloat32Type, &clicktime)) { + /* This is in some undefined unit of time that if we happen + * to multiply by 900 gives us the time in milliseconds. It is + * completely possible that this value is in seconds and should be + * multiplied by 1000 but because IOKit values are undocumented and + * I have no idea what a Carbon 'tick' is so there really is no way + * to confirm this. + */ + value = (long) (clicktime * 900); + + logger(LOG_LEVEL_INFO, "%s [%u]: CFPreferencesCopyValue: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + #endif + + #ifdef USE_CARBON_LEGACY + if (!successful) { + // Apple documentation states that value is in 'ticks'. I am not sure + // what that means, but it looks a lot like the arbitrary slider value. + time = GetDblTime(); + if (time > -1) { + // This is the slider value, we must multiply by 15 to convert to + // milliseconds. + value = (long) time * 15; + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: GetDblTime: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + #endif + + return value; +} + + +// Create a shared object constructor. +__attribute__ ((constructor)) +void on_library_load() { + // Initialize Native Input Functions. + load_input_helper(); +} + +// Create a shared object destructor. +__attribute__ ((destructor)) +void on_library_unload() { + // Disable the event hook. + //hook_stop(); + + // Cleanup native input functions. + unload_input_helper(); +} diff --git a/event/hook/iohook.h b/event/hook/iohook.h new file mode 100644 index 0000000..db0ba8b --- /dev/null +++ b/event/hook/iohook.h @@ -0,0 +1,441 @@ + +#ifndef __IOHOOK_H +#define __IOHOOK_H + +// #include "../../base/os.h" +#include +#include +#include + +/* Begin Error Codes */ +#define IOHOOK_SUCCESS 0x00 +#define IOHOOK_FAILURE 0x01 + +// System level errors. +#define IOHOOK_ERROR_OUT_OF_MEMORY 0x02 + +// Unix specific errors. +#define IOHOOK_ERROR_X_OPEN_DISPLAY 0x20 +#define IOHOOK_ERROR_X_RECORD_NOT_FOUND 0x21 +#define IOHOOK_ERROR_X_RECORD_ALLOC_RANGE 0x22 +#define IOHOOK_ERROR_X_RECORD_CREATE_CONTEXT 0x23 +#define IOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT 0x24 +#define IOHOOK_ERROR_X_RECORD_GET_CONTEXT 0x25 + +// Windows specific errors. +#define IOHOOK_ERROR_SET_WINDOWS_HOOK_EX 0x30 +#define IOHOOK_ERROR_GET_MODULE_HANDLE 0x31 + +// Darwin specific errors. +#define IOHOOK_ERROR_AXAPI_DISABLED 0x40 +#define IOHOOK_ERROR_CREATE_EVENT_PORT 0x41 +#define IOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE 0x42 +#define IOHOOK_ERROR_GET_RUNLOOP 0x43 +#define IOHOOK_ERROR_CREATE_OBSERVER 0x44 +/* End Error Codes */ + +/* Begin Log Levels and Function Prototype */ +typedef enum _log_level { + LOG_LEVEL_DEBUG = 1, + LOG_LEVEL_INFO, + LOG_LEVEL_WARN, + LOG_LEVEL_ERROR +} log_level; + +// Logger callback function prototype. +typedef bool (*logger_t)(unsigned int, const char *, ...); +/* End Log Levels and Function Prototype */ + +/* Begin Virtual Event Types and Data Structures */ +typedef enum _event_type { + EVENT_HOOK_ENABLED = 1, + EVENT_HOOK_DISABLED, + EVENT_KEY_TYPED, + EVENT_KEY_PRESSED, + EVENT_KEY_RELEASED, + EVENT_MOUSE_CLICKED, + EVENT_MOUSE_PRESSED, + EVENT_MOUSE_RELEASED, + EVENT_MOUSE_MOVED, + EVENT_MOUSE_DRAGGED, + EVENT_MOUSE_WHEEL +} event_type; + +typedef struct _screen_data { + uint8_t number; + int16_t x; + int16_t y; + uint16_t width; + uint16_t height; +} screen_data; + +typedef struct _keyboard_event_data { + uint16_t keycode; + uint16_t rawcode; + uint16_t keychar; + // char *keychar; +} keyboard_event_data, + key_pressed_event_data, + key_released_event_data, + key_typed_event_data; + +typedef struct _mouse_event_data { + uint16_t button; + uint16_t clicks; + int16_t x; + int16_t y; +} mouse_event_data, + mouse_pressed_event_data, + mouse_released_event_data, + mouse_clicked_event_data; + +typedef struct _mouse_wheel_event_data { + uint16_t clicks; + int16_t x; + int16_t y; + uint8_t type; + uint16_t amount; + int16_t rotation; + uint8_t direction; +} mouse_wheel_event_data; + +typedef struct _iohook_event { + event_type type; + uint64_t time; + uint16_t mask; + uint16_t reserved; + union { + keyboard_event_data keyboard; + mouse_event_data mouse; + mouse_wheel_event_data wheel; + } data; +} iohook_event; + +typedef void (*dispatcher_t)(iohook_event *const); +/* End Virtual Event Types and Data Structures */ + + +/* Begin Virtual Key Codes */ +#define VC_ESCAPE 0x0001 + +// Begin Function Keys +#define VC_F1 0x003B +#define VC_F2 0x003C +#define VC_F3 0x003D +#define VC_F4 0x003E +#define VC_F5 0x003F +#define VC_F6 0x0040 +#define VC_F7 0x0041 +#define VC_F8 0x0042 +#define VC_F9 0x0043 +#define VC_F10 0x0044 +#define VC_F11 0x0057 +#define VC_F12 0x0058 + +#define VC_F13 0x005B +#define VC_F14 0x005C +#define VC_F15 0x005D +#define VC_F16 0x0063 +#define VC_F17 0x0064 +#define VC_F18 0x0065 +#define VC_F19 0x0066 +#define VC_F20 0x0067 +#define VC_F21 0x0068 +#define VC_F22 0x0069 +#define VC_F23 0x006A +#define VC_F24 0x006B +// End Function Keys + + +// Begin Alphanumeric Zone +#define VC_BACKQUOTE 0x0029 + +#define VC_1 0x0002 +#define VC_2 0x0003 +#define VC_3 0x0004 +#define VC_4 0x0005 +#define VC_5 0x0006 +#define VC_6 0x0007 +#define VC_7 0x0008 +#define VC_8 0x0009 +#define VC_9 0x000A +#define VC_0 0x000B + +#define VC_MINUS 0x000C // '-' +#define VC_EQUALS 0x000D // '=' +#define VC_BACKSPACE 0x000E + +#define VC_TAB 0x000F +#define VC_CAPS_LOCK 0x003A + +#define VC_A 0x001E +#define VC_B 0x0030 +#define VC_C 0x002E +#define VC_D 0x0020 +#define VC_E 0x0012 +#define VC_F 0x0021 +#define VC_G 0x0022 +#define VC_H 0x0023 +#define VC_I 0x0017 +#define VC_J 0x0024 +#define VC_K 0x0025 +#define VC_L 0x0026 +#define VC_M 0x0032 +#define VC_N 0x0031 +#define VC_O 0x0018 +#define VC_P 0x0019 +#define VC_Q 0x0010 +#define VC_R 0x0013 +#define VC_S 0x001F +#define VC_T 0x0014 +#define VC_U 0x0016 +#define VC_V 0x002F +#define VC_W 0x0011 +#define VC_X 0x002D +#define VC_Y 0x0015 +#define VC_Z 0x002C + +#define VC_OPEN_BRACKET 0x001A // '[' +#define VC_CLOSE_BRACKET 0x001B // ']' +#define VC_BACK_SLASH 0x002B // '\' + +#define VC_SEMICOLON 0x0027 // ';' +#define VC_QUOTE 0x0028 +#define VC_ENTER 0x001C + +#define VC_COMMA 0x0033 // ',' +#define VC_PERIOD 0x0034 // '.' +#define VC_SLASH 0x0035 // '/' + +#define VC_SPACE 0x0039 +// End Alphanumeric Zone + + +#define VC_PRINTSCREEN 0x0E37 +#define VC_SCROLL_LOCK 0x0046 +#define VC_PAUSE 0x0E45 + + +// Begin Edit Key Zone +#define VC_INSERT 0x0E52 +#define VC_DELETE 0x0E53 +#define VC_HOME 0x0E47 +#define VC_END 0x0E4F +#define VC_PAGE_UP 0x0E49 +#define VC_PAGE_DOWN 0x0E51 +// End Edit Key Zone + + +// Begin Cursor Key Zone +#define VC_UP 0xE048 +#define VC_LEFT 0xE04B +#define VC_CLEAR 0xE04C +#define VC_RIGHT 0xE04D +#define VC_DOWN 0xE050 +// End Cursor Key Zone + + +// Begin Numeric Zone +#define VC_NUM_LOCK 0x0045 +#define VC_KP_DIVIDE 0x0E35 +#define VC_KP_MULTIPLY 0x0037 +#define VC_KP_SUBTRACT 0x004A +#define VC_KP_EQUALS 0x0E0D +#define VC_KP_ADD 0x004E +#define VC_KP_ENTER 0x0E1C +#define VC_KP_SEPARATOR 0x0053 + +#define VC_KP_1 0x004F +#define VC_KP_2 0x0050 +#define VC_KP_3 0x0051 +#define VC_KP_4 0x004B +#define VC_KP_5 0x004C +#define VC_KP_6 0x004D +#define VC_KP_7 0x0047 +#define VC_KP_8 0x0048 +#define VC_KP_9 0x0049 +#define VC_KP_0 0x0052 + +#define VC_KP_END 0xEE00 | VC_KP_1 +#define VC_KP_DOWN 0xEE00 | VC_KP_2 +#define VC_KP_PAGE_DOWN 0xEE00 | VC_KP_3 +#define VC_KP_LEFT 0xEE00 | VC_KP_4 +#define VC_KP_CLEAR 0xEE00 | VC_KP_5 +#define VC_KP_RIGHT 0xEE00 | VC_KP_6 +#define VC_KP_HOME 0xEE00 | VC_KP_7 +#define VC_KP_UP 0xEE00 | VC_KP_8 +#define VC_KP_PAGE_UP 0xEE00 | VC_KP_9 +#define VC_KP_INSERT 0xEE00 | VC_KP_0 +#define VC_KP_DELETE 0xEE00 | VC_KP_SEPARATOR +// End Numeric Zone + + +// Begin Modifier and Control Keys +#define VC_SHIFT_L 0x002A +#define VC_SHIFT_R 0x0036 +#define VC_CONTROL_L 0x001D +#define VC_CONTROL_R 0x0E1D +#define VC_ALT_L 0x0038 // Option or Alt Key +#define VC_ALT_R 0x0E38 // Option or Alt Key +#define VC_META_L 0x0E5B // Windows or Command Key +#define VC_META_R 0x0E5C // Windows or Command Key +#define VC_CONTEXT_MENU 0x0E5D +// End Modifier and Control Keys + + +// Begin Media Control Keys +#define VC_POWER 0xE05E +#define VC_SLEEP 0xE05F +#define VC_WAKE 0xE063 + +#define VC_MEDIA_PLAY 0xE022 +#define VC_MEDIA_STOP 0xE024 +#define VC_MEDIA_PREVIOUS 0xE010 +#define VC_MEDIA_NEXT 0xE019 +#define VC_MEDIA_SELECT 0xE06D +#define VC_MEDIA_EJECT 0xE02C + +#define VC_VOLUME_MUTE 0xE020 +#define VC_VOLUME_UP 0xE030 +#define VC_VOLUME_DOWN 0xE02E + +#define VC_APP_MAIL 0xE06C +#define VC_APP_CALCULATOR 0xE021 +#define VC_APP_MUSIC 0xE03C +#define VC_APP_PICTURES 0xE064 + +#define VC_BROWSER_SEARCH 0xE065 +#define VC_BROWSER_HOME 0xE032 +#define VC_BROWSER_BACK 0xE06A +#define VC_BROWSER_FORWARD 0xE069 +#define VC_BROWSER_STOP 0xE068 +#define VC_BROWSER_REFRESH 0xE067 +#define VC_BROWSER_FAVORITES 0xE066 +// End Media Control Keys + +// Begin Japanese Language Keys +#define VC_KATAKANA 0x0070 +#define VC_UNDERSCORE 0x0073 +#define VC_FURIGANA 0x0077 +#define VC_KANJI 0x0079 +#define VC_HIRAGANA 0x007B +#define VC_YEN 0x007D +#define VC_KP_COMMA 0x007E +// End Japanese Language Keys + +// Begin Sun keyboards +#define VC_SUN_HELP 0xFF75 + +#define VC_SUN_STOP 0xFF78 +#define VC_SUN_PROPS 0xFF76 +#define VC_SUN_FRONT 0xFF77 +#define VC_SUN_OPEN 0xFF74 +#define VC_SUN_FIND 0xFF7E +#define VC_SUN_AGAIN 0xFF79 +#define VC_SUN_UNDO 0xFF7A +#define VC_SUN_COPY 0xFF7C +#define VC_SUN_INSERT 0xFF7D +#define VC_SUN_CUT 0xFF7B +// End Sun keyboards + +#define VC_UNDEFINED 0x0000 // KeyCode Unknown + +#define CHAR_UNDEFINED 0xFFFF // CharCode Unknown +/* End Virtual Key Codes */ + + +/* Begin Virtual Modifier Masks */ +#define MASK_SHIFT_L 1 << 0 +#define MASK_CTRL_L 1 << 1 +#define MASK_META_L 1 << 2 +#define MASK_ALT_L 1 << 3 + +#define MASK_SHIFT_R 1 << 4 +#define MASK_CTRL_R 1 << 5 +#define MASK_META_R 1 << 6 +#define MASK_ALT_R 1 << 7 + +#define MASK_SHIFT MASK_SHIFT_L | MASK_SHIFT_R +#define MASK_CTRL MASK_CTRL_L | MASK_CTRL_R +#define MASK_META MASK_META_L | MASK_META_R +#define MASK_ALT MASK_ALT_L | MASK_ALT_R + +#define MASK_BUTTON1 1 << 8 +#define MASK_BUTTON2 1 << 9 +#define MASK_BUTTON3 1 << 10 +#define MASK_BUTTON4 1 << 11 +#define MASK_BUTTON5 1 << 12 + +#define MASK_NUM_LOCK 1 << 13 +#define MASK_CAPS_LOCK 1 << 14 +#define MASK_SCROLL_LOCK 1 << 15 +/* End Virtual Modifier Masks */ + + +/* Begin Virtual Mouse Buttons */ +#define MOUSE_NOBUTTON 0 // Any Button +#define MOUSE_BUTTON1 1 // Left Button +#define MOUSE_BUTTON2 2 // Right Button +#define MOUSE_BUTTON3 3 // Middle Button +#define MOUSE_BUTTON4 4 // Extra Mouse Button +#define MOUSE_BUTTON5 5 // Extra Mouse Button + +#define WHEEL_UNIT_SCROLL 1 +#define WHEEL_BLOCK_SCROLL 2 + +#define WHEEL_VERTICAL_DIRECTION 3 +#define WHEEL_HORIZONTAL_DIRECTION 4 +/* End Virtual Mouse Buttons */ + + +#ifdef _WIN32 +#define IOHOOK_API __declspec(dllexport) +#else +#define IOHOOK_API +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + // Set the logger callback functions. + IOHOOK_API void hook_set_logger_proc(logger_t logger_proc); + + // Send a virtual event back to the system. + IOHOOK_API void hook_post_event(iohook_event * const event); + + // Set the event callback function. + IOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc); + + // Insert the event hook. + IOHOOK_API int hook_run(); + + // Withdraw the event hook. + IOHOOK_API int hook_stop(); + + // Retrieves an array of screen data for each available monitor. + IOHOOK_API screen_data* hook_create_screen_info(unsigned char *count); + + // Retrieves the keyboard auto repeat rate. + IOHOOK_API long int hook_get_auto_repeat_rate(); + + // Retrieves the keyboard auto repeat delay. + IOHOOK_API long int hook_get_auto_repeat_delay(); + + // Retrieves the mouse acceleration multiplier. + IOHOOK_API long int hook_get_pointer_acceleration_multiplier(); + + // Retrieves the mouse acceleration threshold. + IOHOOK_API long int hook_get_pointer_acceleration_threshold(); + + // Retrieves the mouse sensitivity. + IOHOOK_API long int hook_get_pointer_sensitivity(); + + // Retrieves the double/triple click interval. + IOHOOK_API long int hook_get_multi_click_time(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/event/hook/logger.h b/event/hook/logger.h new file mode 100644 index 0000000..0314e0f --- /dev/null +++ b/event/hook/logger.h @@ -0,0 +1,15 @@ + +#ifndef _included_logger +#define _included_logger + +#include "iohook.h" +#include + +#ifndef __FUNCTION__ +#define __FUNCTION__ __func__ +#endif + +// logger(level, message) +extern logger_t logger; + +#endif diff --git a/event/hook/logger_c.h b/event/hook/logger_c.h new file mode 100644 index 0000000..8752245 --- /dev/null +++ b/event/hook/logger_c.h @@ -0,0 +1,53 @@ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include +#include + +#include "iohook.h" +#include "logger.h" + +static bool default_logger(unsigned int level, const char *format, ...) { + bool status = false; + + #ifndef USE_QUIET + va_list args; + switch (level) { + #ifdef USE_DEBUG + case LOG_LEVEL_DEBUG: + #endif + case LOG_LEVEL_INFO: + va_start(args, format); + status = vfprintf(stdout, format, args) >= 0; + va_end(args); + break; + + case LOG_LEVEL_WARN: + case LOG_LEVEL_ERROR: + va_start(args, format); + status = vfprintf(stderr, format, args) >= 0; + va_end(args); + break; + } + #endif + + return status; +} + +// Current logger function pointer, this should never be null. +// FIXME This should be static and wrapped with a public facing function. +logger_t logger = &default_logger; + + +IOHOOK_API void hookSetlogger(logger_t logger_proc) { + if (logger_proc == NULL) { + logger = &default_logger; + } + else { + logger = logger_proc; + } +} diff --git a/event/hook/windows/event_c.h b/event/hook/windows/event_c.h new file mode 100644 index 0000000..2eb51f6 --- /dev/null +++ b/event/hook/windows/event_c.h @@ -0,0 +1,328 @@ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include "../iohook.h" +#include + +// #include "logger.h" +#include "input.h" + +// Some buggy versions of MinGW and MSys do not include these constants in winuser.h. +#ifndef MAPVK_VK_TO_VSC +#define MAPVK_VK_TO_VSC 0 +#define MAPVK_VSC_TO_VK 1 +#define MAPVK_VK_TO_CHAR 2 +#define MAPVK_VSC_TO_VK_EX 3 +#endif +// Some buggy versions of MinGW and MSys only define this value for Windows +// versions >= 0x0600 (Windows Vista) when it should be 0x0500 (Windows 2000). +#ifndef MAPVK_VK_TO_VSC_EX +#define MAPVK_VK_TO_VSC_EX 4 +#endif + +#ifndef KEYEVENTF_SCANCODE +#define KEYEVENTF_EXTENDEDKEY 0x0001 +#define KEYEVENTF_KEYUP 0x0002 +#define KEYEVENTF_UNICODE 0x0004 +#define KEYEVENTF_SCANCODE 0x0008 +#endif + +#ifndef KEYEVENTF_KEYDOWN +#define KEYEVENTF_KEYDOWN 0x0000 +#endif + +#define MAX_WINDOWS_COORD_VALUE 65535 + +static UINT keymask_lookup[8] = { + VK_LSHIFT, + VK_LCONTROL, + VK_LWIN, + VK_LMENU, + + VK_RSHIFT, + VK_RCONTROL, + VK_RWIN, + VK_RMENU +}; + +IOHOOK_API void hook_post_event(iohook_event * const event) { + //FIXME implement multiple monitor support + uint16_t screen_width = GetSystemMetrics( SM_CXSCREEN ); + uint16_t screen_height = GetSystemMetrics( SM_CYSCREEN ); + + unsigned char events_size = 0, events_max = 28; + INPUT *events = malloc(sizeof(INPUT) * events_max); + + if (event->mask & (MASK_SHIFT | MASK_CTRL | MASK_META | MASK_ALT)) { + unsigned int i; + for (i = 0; i < sizeof(keymask_lookup) / sizeof(UINT); i++) { + if (event->mask & 1 << i) { + events[events_size].type = INPUT_KEYBOARD; + events[events_size].ki.wVk = keymask_lookup[i]; + events[events_size].ki.dwFlags = KEYEVENTF_KEYDOWN; + events[events_size].ki.time = 0; // Use current system time. + events_size++; + } + } + } + + if (event->mask & (MASK_BUTTON1 | MASK_BUTTON2 | MASK_BUTTON3 | MASK_BUTTON4 | MASK_BUTTON5)) { + events[events_size].type = INPUT_MOUSE; + events[events_size].mi.dx = 0; // Relative mouse movement due to + events[events_size].mi.dy = 0; // MOUSEEVENTF_ABSOLUTE not being set. + events[events_size].mi.mouseData = 0x00; + events[events_size].mi.time = 0; // Use current system time. + + if (event->mask & MASK_BUTTON1) { + events[events_size].mi.mouseData |= MOUSEEVENTF_LEFTDOWN; + } + + if (event->mask & MASK_BUTTON2) { + events[events_size].mi.mouseData |= MOUSEEVENTF_RIGHTDOWN; + } + + if (event->mask & MASK_BUTTON3) { + events[events_size].mi.mouseData |= MOUSEEVENTF_MIDDLEDOWN; + } + + if (event->mask & MASK_BUTTON4) { + events[events_size].mi.mouseData = XBUTTON1; + events[events_size].mi.mouseData |= MOUSEEVENTF_XDOWN; + } + + if (event->mask & MASK_BUTTON5) { + events[events_size].mi.mouseData = XBUTTON2; + events[events_size].mi.dwFlags |= MOUSEEVENTF_XDOWN; + } + + events_size++; + } + + + switch (event->type) { + case EVENT_KEY_PRESSED: + events[events_size].ki.wVk = scancode_to_keycode(event->data.keyboard.keycode); + if (events[events_size].ki.wVk != 0x0000) { + events[events_size].type = INPUT_KEYBOARD; + events[events_size].ki.dwFlags = KEYEVENTF_KEYDOWN; // |= KEYEVENTF_SCANCODE; + events[events_size].ki.wScan = 0; // event->data.keyboard.keycode; + events[events_size].ki.time = 0; // GetSystemTime() + events_size++; + } + else { + logger(LOG_LEVEL_INFO, "%s [%u]: Unable to lookup scancode: %li\n", + __FUNCTION__, __LINE__, + event->data.keyboard.keycode); + } + break; + + case EVENT_KEY_RELEASED: + events[events_size].ki.wVk = scancode_to_keycode(event->data.keyboard.keycode); + if (events[events_size].ki.wVk != 0x0000) { + events[events_size].type = INPUT_KEYBOARD; + events[events_size].ki.dwFlags = KEYEVENTF_KEYUP; // |= KEYEVENTF_SCANCODE; + events[events_size].ki.wVk = scancode_to_keycode(event->data.keyboard.keycode); + events[events_size].ki.wScan = 0; // event->data.keyboard.keycode; + events[events_size].ki.time = 0; // GetSystemTime() + events_size++; + } + else { + logger(LOG_LEVEL_INFO, "%s [%u]: Unable to lookup scancode: %li\n", + __FUNCTION__, __LINE__, + event->data.keyboard.keycode); + } + break; + + + case EVENT_MOUSE_PRESSED: + events[events_size].type = INPUT_MOUSE; + events[events_size].mi.dwFlags = MOUSEEVENTF_XDOWN; + + switch (event->data.mouse.button) { + case MOUSE_BUTTON1: + events[events_size].mi.dwFlags = MOUSEEVENTF_LEFTDOWN; + break; + + case MOUSE_BUTTON2: + events[events_size].mi.dwFlags = MOUSEEVENTF_RIGHTDOWN; + break; + + case MOUSE_BUTTON3: + events[events_size].mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN; + break; + + case MOUSE_BUTTON4: + events[events_size].mi.mouseData = XBUTTON1; + break; + + case MOUSE_BUTTON5: + events[events_size].mi.mouseData = XBUTTON2; + break; + + default: + // Extra buttons. + if (event->data.mouse.button > 3) { + events[events_size].mi.mouseData = event->data.mouse.button - 3; + } + } + + events[events_size].mi.dx = event->data.mouse.x * (MAX_WINDOWS_COORD_VALUE / screen_width) + 1; + events[events_size].mi.dy = event->data.mouse.y * (MAX_WINDOWS_COORD_VALUE / screen_height) + 1; + + events[events_size].mi.dwFlags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + events[events_size].mi.time = 0; // GetSystemTime() + + events_size++; + break; + + case EVENT_MOUSE_RELEASED: + events[events_size].type = INPUT_MOUSE; + events[events_size].mi.dwFlags = MOUSEEVENTF_XUP; + + switch (event->data.mouse.button) { + case MOUSE_BUTTON1: + events[events_size].mi.dwFlags = MOUSEEVENTF_LEFTUP; + break; + + case MOUSE_BUTTON2: + events[events_size].mi.dwFlags = MOUSEEVENTF_RIGHTUP; + break; + + case MOUSE_BUTTON3: + events[events_size].mi.dwFlags = MOUSEEVENTF_MIDDLEUP; + break; + + case MOUSE_BUTTON4: + events[events_size].mi.mouseData = XBUTTON1; + break; + + case MOUSE_BUTTON5: + events[events_size].mi.mouseData = XBUTTON2; + break; + + default: + // Extra buttons. + if (event->data.mouse.button > 3) { + events[events_size].mi.mouseData = event->data.mouse.button - 3; + } + } + + events[events_size].mi.dx = event->data.mouse.x * (MAX_WINDOWS_COORD_VALUE / screen_width) + 1; + events[events_size].mi.dy = event->data.mouse.y * (MAX_WINDOWS_COORD_VALUE / screen_height) + 1; + + events[events_size].mi.dwFlags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + events[events_size].mi.time = 0; // GetSystemTime() + events_size++; + break; + + + case EVENT_MOUSE_WHEEL: + events[events_size].type = INPUT_MOUSE; + events[events_size].mi.dwFlags = MOUSEEVENTF_WHEEL; + + // type, amount and rotation? + events[events_size].mi.mouseData = event->data.wheel.amount * event->data.wheel.rotation * WHEEL_DELTA; + + events[events_size].mi.dx = event->data.wheel.x * (MAX_WINDOWS_COORD_VALUE / screen_width) + 1; + events[events_size].mi.dy = event->data.wheel.y * (MAX_WINDOWS_COORD_VALUE / screen_height) + 1; + + events[events_size].mi.dwFlags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + events[events_size].mi.time = 0; // GetSystemTime() + events_size++; + break; + + + case EVENT_MOUSE_DRAGGED: + // The button masks are all applied with the modifier masks. + + case EVENT_MOUSE_MOVED: + events[events_size].type = INPUT_MOUSE; + events[events_size].mi.dwFlags = MOUSEEVENTF_MOVE; + + events[events_size].mi.dx = event->data.mouse.x * (MAX_WINDOWS_COORD_VALUE / screen_width) + 1; + events[events_size].mi.dy = event->data.mouse.y * (MAX_WINDOWS_COORD_VALUE / screen_height) + 1; + + events[events_size].mi.dwFlags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + events[events_size].mi.time = 0; // GetSystemTime() + events_size++; + break; + + + case EVENT_MOUSE_CLICKED: + case EVENT_KEY_TYPED: + // Ignore clicked and typed events. + + case EVENT_HOOK_ENABLED: + case EVENT_HOOK_DISABLED: + // Ignore hook enabled / disabled events. + + default: + // Ignore any other garbage. + logger(LOG_LEVEL_WARN, "%s [%u]: Ignoring post event type %#X\n", + __FUNCTION__, __LINE__, event->type); + break; + } + + // Release the previously held modifier keys used to fake the event mask. + if (event->mask & (MASK_SHIFT | MASK_CTRL | MASK_META | MASK_ALT)) { + unsigned int i; + for (i = 0; i < sizeof(keymask_lookup) / sizeof(UINT); i++) { + if (event->mask & 1 << i) { + events[events_size].type = INPUT_KEYBOARD; + events[events_size].ki.wVk = keymask_lookup[i]; + events[events_size].ki.dwFlags = KEYEVENTF_KEYUP; + events[events_size].ki.time = 0; // Use current system time. + events_size++; + } + } + } + + if (event->mask & (MASK_BUTTON1 | MASK_BUTTON2 | MASK_BUTTON3 | MASK_BUTTON4 | MASK_BUTTON5)) { + events[events_size].type = INPUT_MOUSE; + events[events_size].mi.dx = 0; // Relative mouse movement due to + events[events_size].mi.dy = 0; // MOUSEEVENTF_ABSOLUTE not being set. + events[events_size].mi.mouseData = 0x00; + events[events_size].mi.time = 0; // Use current system time. + + // If dwFlags does not contain MOUSEEVENTF_WHEEL, MOUSEEVENTF_XDOWN, or MOUSEEVENTF_XUP, + // then mouseData should be zero. + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646273%28v=vs.85%29.aspx + if (event->mask & MASK_BUTTON1) { + events[events_size].mi.dwFlags |= MOUSEEVENTF_LEFTUP; + } + + if (event->mask & MASK_BUTTON2) { + events[events_size].mi.dwFlags |= MOUSEEVENTF_RIGHTUP; + } + + if (event->mask & MASK_BUTTON3) { + events[events_size].mi.dwFlags |= MOUSEEVENTF_MIDDLEUP; + } + + if (event->mask & MASK_BUTTON4) { + events[events_size].mi.mouseData = XBUTTON1; + events[events_size].mi.dwFlags |= MOUSEEVENTF_XUP; + } + + if (event->mask & MASK_BUTTON5) { + events[events_size].mi.mouseData = XBUTTON2; + events[events_size].mi.dwFlags |= MOUSEEVENTF_XUP; + } + + events_size++; + } + + // Create the key release input + // memcpy(key_events + 1, key_events, sizeof(INPUT)); + // key_events[1].ki.dwFlags |= KEYEVENTF_KEYUP; + + if (! SendInput(events_size, events, sizeof(INPUT)) ) { + logger(LOG_LEVEL_ERROR, "%s [%u]: SendInput() failed! (%#lX)\n", + __FUNCTION__, __LINE__, (unsigned long) GetLastError()); + } + + free(events); +} diff --git a/event/hook/windows/hook_c.h b/event/hook/windows/hook_c.h new file mode 100644 index 0000000..ed15515 --- /dev/null +++ b/event/hook/windows/hook_c.h @@ -0,0 +1,747 @@ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#include "../iohook.h" +#include "input.h" +// #include "logger.h" + +// Thread and hook handles. +static DWORD hook_thread_id = 0; +static HHOOK keyboard_event_hhook = NULL, mouse_event_hhook = NULL; +static HWINEVENTHOOK win_event_hhook = NULL; + +// The handle to the DLL module pulled in DllMain on DLL_PROCESS_ATTACH. +extern HINSTANCE hInst; + +// Modifiers for tracking key masks. +static unsigned short int current_modifiers = 0x0000; + +// Click count globals. +static unsigned short click_count = 0; +static DWORD click_time = 0; +static unsigned short int click_button = MOUSE_NOBUTTON; +static POINT last_click; + +// Static event memory. +static iohook_event event; + +// Event dispatch callback. +static dispatcher_t dispatcher = NULL; + +IOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n", + __FUNCTION__, __LINE__, dispatch_proc); + + dispatcher = dispatch_proc; +} + +// Send out an event if a dispatcher was set. +static inline void dispatch_event(iohook_event *const event) { + if (dispatcher != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n", + __FUNCTION__, __LINE__, event->type); + + dispatcher(event); + } + else { + logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n", + __FUNCTION__, __LINE__); + } +} + + +// Set the native modifier mask for future events. +static inline void set_modifier_mask(unsigned short int mask) { + current_modifiers |= mask; +} + +// Unset the native modifier mask for future events. +static inline void unset_modifier_mask(unsigned short int mask) { + current_modifiers ^= mask; +} + +// Get the current native modifier mask state. +static inline unsigned short int get_modifiers() { + return current_modifiers; +} + +// Initialize the modifier mask to the current modifiers. +static void initialize_modifiers() { + current_modifiers = 0x0000; + + // NOTE We are checking the high order bit, so it will be < 0 for a singed short. + if (GetKeyState(VK_LSHIFT) < 0) { set_modifier_mask(MASK_SHIFT_L); } + if (GetKeyState(VK_RSHIFT) < 0) { set_modifier_mask(MASK_SHIFT_R); } + if (GetKeyState(VK_LCONTROL) < 0) { set_modifier_mask(MASK_CTRL_L); } + if (GetKeyState(VK_RCONTROL) < 0) { set_modifier_mask(MASK_CTRL_R); } + if (GetKeyState(VK_LMENU) < 0) { set_modifier_mask(MASK_ALT_L); } + if (GetKeyState(VK_RMENU) < 0) { set_modifier_mask(MASK_ALT_R); } + if (GetKeyState(VK_LWIN) < 0) { set_modifier_mask(MASK_META_L); } + if (GetKeyState(VK_RWIN) < 0) { set_modifier_mask(MASK_META_R); } + + if (GetKeyState(VK_LBUTTON) < 0) { set_modifier_mask(MASK_BUTTON1); } + if (GetKeyState(VK_RBUTTON) < 0) { set_modifier_mask(MASK_BUTTON2); } + if (GetKeyState(VK_MBUTTON) < 0) { set_modifier_mask(MASK_BUTTON3); } + if (GetKeyState(VK_XBUTTON1) < 0) { set_modifier_mask(MASK_BUTTON4); } + if (GetKeyState(VK_XBUTTON2) < 0) { set_modifier_mask(MASK_BUTTON5); } + + if (GetKeyState(VK_NUMLOCK) < 0) { set_modifier_mask(MASK_NUM_LOCK); } + if (GetKeyState(VK_CAPITAL) < 0) { set_modifier_mask(MASK_CAPS_LOCK); } + if (GetKeyState(VK_SCROLL) < 0) { set_modifier_mask(MASK_SCROLL_LOCK); } +} + + +/* Retrieves the mouse wheel scroll type. This function cannot be included as + * part of the input.h due to platform specific calling restrictions. + */ +static unsigned short int get_scroll_wheel_type() { + unsigned short int value; + UINT wheel_type; + + SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheel_type, 0); + if (wheel_type == WHEEL_PAGESCROLL) { + value = WHEEL_BLOCK_SCROLL; + } + else { + value = WHEEL_UNIT_SCROLL; + } + + return value; +} + +/* Retrieves the mouse wheel scroll amount. This function cannot be included as + * part of the input.h due to platform specific calling restrictions. + */ +static unsigned short int get_scroll_wheel_amount() { + unsigned short int value; + UINT wheel_amount; + + SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheel_amount, 0); + if (wheel_amount == WHEEL_PAGESCROLL) { + value = 1; + } + else { + value = (unsigned short int) wheel_amount; + } + + return value; +} + +void unregister_running_hooks() { + // Stop the event hook and any timer still running. + if (win_event_hhook != NULL) { + UnhookWinEvent(win_event_hhook); + win_event_hhook = NULL; + } + + // Destroy the native hooks. + if (keyboard_event_hhook != NULL) { + UnhookWindowsHookEx(keyboard_event_hhook); + keyboard_event_hhook = NULL; + } + + if (mouse_event_hhook != NULL) { + UnhookWindowsHookEx(mouse_event_hhook); + mouse_event_hhook = NULL; + } +} + +void hook_start_proc() { + // Get the local system time in UNIX epoch form. + uint64_t timestamp = GetMessageTime(); + + // Populate the hook start event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_HOOK_ENABLED; + event.mask = 0x00; + + // Fire the hook start event. + dispatch_event(&event); +} + +void hook_stop_proc() { + // Get the local system time in UNIX epoch form. + uint64_t timestamp = GetMessageTime(); + + // Populate the hook stop event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_HOOK_DISABLED; + event.mask = 0x00; + + // Fire the hook stop event. + dispatch_event(&event); +} + +static void process_key_pressed(KBDLLHOOKSTRUCT *kbhook) { + // Check and setup modifiers. + if (kbhook->vkCode == VK_LSHIFT) { set_modifier_mask(MASK_SHIFT_L); } + else if (kbhook->vkCode == VK_RSHIFT) { set_modifier_mask(MASK_SHIFT_R); } + else if (kbhook->vkCode == VK_LCONTROL) { set_modifier_mask(MASK_CTRL_L); } + else if (kbhook->vkCode == VK_RCONTROL) { set_modifier_mask(MASK_CTRL_R); } + else if (kbhook->vkCode == VK_LMENU) { set_modifier_mask(MASK_ALT_L); } + else if (kbhook->vkCode == VK_RMENU) { set_modifier_mask(MASK_ALT_R); } + else if (kbhook->vkCode == VK_LWIN) { set_modifier_mask(MASK_META_L); } + else if (kbhook->vkCode == VK_RWIN) { set_modifier_mask(MASK_META_R); } + else if (kbhook->vkCode == VK_NUMLOCK) { set_modifier_mask(MASK_NUM_LOCK); } + else if (kbhook->vkCode == VK_CAPITAL) { set_modifier_mask(MASK_CAPS_LOCK); } + else if (kbhook->vkCode == VK_SCROLL) { set_modifier_mask(MASK_SCROLL_LOCK); } + + // Populate key pressed event. + event.time = kbhook->time; + event.reserved = 0x00; + + event.type = EVENT_KEY_PRESSED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = keycode_to_scancode(kbhook->vkCode, kbhook->flags); + event.data.keyboard.rawcode = kbhook->vkCode; + event.data.keyboard.keychar = CHAR_UNDEFINED; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X pressed. (%#X)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode); + + // Populate key pressed event. + dispatch_event(&event); + + // If the pressed event was not consumed... + if (event.reserved ^ 0x01) { + // Buffer for unicode typed chars. No more than 2 needed. + WCHAR buffer[2]; // = { WCH_NONE }; + + // If the pressed event was not consumed and a unicode char exists... + SIZE_T count = keycode_to_unicode(kbhook->vkCode, buffer, sizeof(buffer)); + unsigned int i; + for (i = 0; i < count; i++) { + // Populate key typed event. + event.time = kbhook->time; + event.reserved = 0x00; + + event.type = EVENT_KEY_TYPED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = VC_UNDEFINED; + event.data.keyboard.rawcode = kbhook->vkCode; + event.data.keyboard.keychar = buffer[i]; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X typed. (%lc)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, (wint_t) event.data.keyboard.keychar); + + // Fire key typed event. + dispatch_event(&event); + } + } +} + +static void process_key_released(KBDLLHOOKSTRUCT *kbhook) { + // Check and setup modifiers. + if (kbhook->vkCode == VK_LSHIFT) { unset_modifier_mask(MASK_SHIFT_L); } + else if (kbhook->vkCode == VK_RSHIFT) { unset_modifier_mask(MASK_SHIFT_R); } + else if (kbhook->vkCode == VK_LCONTROL) { unset_modifier_mask(MASK_CTRL_L); } + else if (kbhook->vkCode == VK_RCONTROL) { unset_modifier_mask(MASK_CTRL_R); } + else if (kbhook->vkCode == VK_LMENU) { unset_modifier_mask(MASK_ALT_L); } + else if (kbhook->vkCode == VK_RMENU) { unset_modifier_mask(MASK_ALT_R); } + else if (kbhook->vkCode == VK_LWIN) { unset_modifier_mask(MASK_META_L); } + else if (kbhook->vkCode == VK_RWIN) { unset_modifier_mask(MASK_META_R); } + else if (kbhook->vkCode == VK_NUMLOCK) { unset_modifier_mask(MASK_NUM_LOCK); } + else if (kbhook->vkCode == VK_CAPITAL) { unset_modifier_mask(MASK_CAPS_LOCK); } + else if (kbhook->vkCode == VK_SCROLL) { unset_modifier_mask(MASK_SCROLL_LOCK); } + + // Populate key pressed event. + event.time = kbhook->time; + event.reserved = 0x00; + + event.type = EVENT_KEY_RELEASED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = keycode_to_scancode(kbhook->vkCode, kbhook->flags); + event.data.keyboard.rawcode = kbhook->vkCode; + event.data.keyboard.keychar = CHAR_UNDEFINED; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X released. (%#X)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode); + + // Fire key released event. + dispatch_event(&event); +} + +LRESULT CALLBACK keyboard_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) { + KBDLLHOOKSTRUCT *kbhook = (KBDLLHOOKSTRUCT *) lParam; + switch (wParam) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + process_key_pressed(kbhook); + break; + + case WM_KEYUP: + case WM_SYSKEYUP: + process_key_released(kbhook); + break; + + default: + // In theory this *should* never execute. + logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled Windows keyboard event: %#X.\n", + __FUNCTION__, __LINE__, (unsigned int) wParam); + break; + } + + LRESULT hook_result = -1; + if (nCode < 0 || event.reserved ^ 0x01) { + hook_result = CallNextHookEx(keyboard_event_hhook, nCode, wParam, lParam); + } + else { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%li)\n", + __FUNCTION__, __LINE__, (long) hook_result); + } + + return hook_result; +} + + +static void process_button_pressed(MSLLHOOKSTRUCT *mshook, uint16_t button) { + uint64_t timestamp = GetMessageTime(); + + // Track the number of clicks, the button must match the previous button. + if (button == click_button && (long int) (timestamp - click_time) <= hook_get_multi_click_time()) { + if (click_count < USHRT_MAX) { + click_count++; + } + else { + logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n", + __FUNCTION__, __LINE__); + } + } + else { + // Reset the click count. + click_count = 1; + + // Set the previous button. + click_button = button; + } + + // Save this events time to calculate the click_count. + click_time = timestamp; + + // Store the last click point. + last_click.x = mshook->pt.x; + last_click.y = mshook->pt.y; + + // Populate mouse pressed event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_PRESSED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = click_count; + + event.data.mouse.x = mshook->pt.x; + event.data.mouse.y = mshook->pt.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u pressed %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse pressed event. + dispatch_event(&event); +} + +static void process_button_released(MSLLHOOKSTRUCT *mshook, uint16_t button) { + // Populate mouse released event. + event.time = GetMessageTime(); + event.reserved = 0x00; + + event.type = EVENT_MOUSE_RELEASED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = click_count; + + event.data.mouse.x = mshook->pt.x; + event.data.mouse.y = mshook->pt.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u released %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, + event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse released event. + dispatch_event(&event); + + // If the pressed event was not consumed... + if (event.reserved ^ 0x01 && last_click.x == mshook->pt.x && last_click.y == mshook->pt.y) { + // Populate mouse clicked event. + event.time = GetMessageTime(); + event.reserved = 0x00; + + event.type = EVENT_MOUSE_CLICKED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = click_count; + event.data.mouse.x = mshook->pt.x; + event.data.mouse.y = mshook->pt.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u clicked %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse clicked event. + dispatch_event(&event); + } + + // Reset the number of clicks. + if (button == click_button && (long int) (event.time - click_time) > hook_get_multi_click_time()) { + // Reset the click count. + click_count = 0; + } +} + +static void process_mouse_moved(MSLLHOOKSTRUCT *mshook) { + uint64_t timestamp = GetMessageTime(); + + // We received a mouse move event with the mouse actually moving. + // This verifies that the mouse was moved after being depressed. + if (last_click.x != mshook->pt.x || last_click.y != mshook->pt.y) { + // Reset the click count. + if (click_count != 0 && (long) (timestamp - click_time) > hook_get_multi_click_time()) { + click_count = 0; + } + + // Populate mouse move event. + event.time = timestamp; + event.reserved = 0x00; + + event.mask = get_modifiers(); + + // Check the modifier mask range for MASK_BUTTON1 - 5. + bool mouse_dragged = event.mask & (MASK_BUTTON1 | MASK_BUTTON2 | MASK_BUTTON3 | MASK_BUTTON4 | MASK_BUTTON5); + if (mouse_dragged) { + // Create Mouse Dragged event. + event.type = EVENT_MOUSE_DRAGGED; + } + else { + // Create a Mouse Moved event. + event.type = EVENT_MOUSE_MOVED; + } + + event.data.mouse.button = MOUSE_NOBUTTON; + event.data.mouse.clicks = click_count; + event.data.mouse.x = mshook->pt.x; + event.data.mouse.y = mshook->pt.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Mouse %s to %u, %u.\n", + __FUNCTION__, __LINE__, mouse_dragged ? "dragged" : "moved", + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse move event. + dispatch_event(&event); + } +} + +static void process_mouse_wheel(MSLLHOOKSTRUCT *mshook, uint8_t direction) { + // Track the number of clicks. + // Reset the click count and previous button. + click_count = 1; + click_button = MOUSE_NOBUTTON; + + // Populate mouse wheel event. + event.time = GetMessageTime(); + event.reserved = 0x00; + + event.type = EVENT_MOUSE_WHEEL; + event.mask = get_modifiers(); + + event.data.wheel.clicks = click_count; + event.data.wheel.x = mshook->pt.x; + event.data.wheel.y = mshook->pt.y; + + event.data.wheel.type = get_scroll_wheel_type(); + event.data.wheel.amount = get_scroll_wheel_amount(); + + /* Delta HIWORD(mshook->mouseData) + * A positive value indicates that the wheel was rotated + * forward, away from the user; a negative value indicates that + * the wheel was rotated backward, toward the user. One wheel + * click is defined as WHEEL_DELTA, which is 120. */ + event.data.wheel.rotation = ((int16_t) HIWORD(mshook->mouseData) / WHEEL_DELTA) * -1; + + // Set the direction based on what event was received. + event.data.wheel.direction = direction; + + logger(LOG_LEVEL_INFO, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n", + __FUNCTION__, __LINE__, event.data.wheel.type, + event.data.wheel.amount * event.data.wheel.rotation, + event.data.wheel.direction, + event.data.wheel.x, event.data.wheel.y); + + // Fire mouse wheel event. + dispatch_event(&event); +} + +LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) { + MSLLHOOKSTRUCT *mshook = (MSLLHOOKSTRUCT *) lParam; + switch (wParam) { + case WM_LBUTTONDOWN: + set_modifier_mask(MASK_BUTTON1); + process_button_pressed(mshook, MOUSE_BUTTON1); + break; + + case WM_RBUTTONDOWN: + set_modifier_mask(MASK_BUTTON2); + process_button_pressed(mshook, MOUSE_BUTTON2); + break; + + case WM_MBUTTONDOWN: + set_modifier_mask(MASK_BUTTON3); + process_button_pressed(mshook, MOUSE_BUTTON3); + break; + + case WM_XBUTTONDOWN: + case WM_NCXBUTTONDOWN: + if (HIWORD(mshook->mouseData) == XBUTTON1) { + set_modifier_mask(MASK_BUTTON4); + process_button_pressed(mshook, MOUSE_BUTTON4); + } + else if (HIWORD(mshook->mouseData) == XBUTTON2) { + set_modifier_mask(MASK_BUTTON5); + process_button_pressed(mshook, MOUSE_BUTTON5); + } + else { + // Extra mouse buttons. + uint16_t button = HIWORD(mshook->mouseData); + + // Add support for mouse 4 & 5. + if (button == 4) { + set_modifier_mask(MOUSE_BUTTON4); + } + else if (button == 5) { + set_modifier_mask(MOUSE_BUTTON5); + } + + process_button_pressed(mshook, button); + } + break; + + + case WM_LBUTTONUP: + unset_modifier_mask(MASK_BUTTON1); + process_button_released(mshook, MOUSE_BUTTON1); + break; + + case WM_RBUTTONUP: + unset_modifier_mask(MASK_BUTTON2); + process_button_released(mshook, MOUSE_BUTTON2); + break; + + case WM_MBUTTONUP: + unset_modifier_mask(MASK_BUTTON3); + process_button_released(mshook, MOUSE_BUTTON3); + break; + + case WM_XBUTTONUP: + case WM_NCXBUTTONUP: + if (HIWORD(mshook->mouseData) == XBUTTON1) { + unset_modifier_mask(MASK_BUTTON4); + process_button_released(mshook, MOUSE_BUTTON4); + } + else if (HIWORD(mshook->mouseData) == XBUTTON2) { + unset_modifier_mask(MASK_BUTTON5); + process_button_released(mshook, MOUSE_BUTTON5); + } + else { + // Extra mouse buttons. + uint16_t button = HIWORD(mshook->mouseData); + + // Add support for mouse 4 & 5. + if (button == 4) { + unset_modifier_mask(MOUSE_BUTTON4); + } + else if (button == 5) { + unset_modifier_mask(MOUSE_BUTTON5); + } + + process_button_released(mshook, MOUSE_BUTTON5); + } + break; + + case WM_MOUSEMOVE: + process_mouse_moved(mshook); + break; + + case WM_MOUSEWHEEL: + process_mouse_wheel(mshook, WHEEL_VERTICAL_DIRECTION); + break; + + /* For horizontal scroll wheel support. + * NOTE Windows >= Vista + * case 0x020E: + */ + case WM_MOUSEHWHEEL: + process_mouse_wheel(mshook, WHEEL_HORIZONTAL_DIRECTION); + break; + + default: + // In theory this *should* never execute. + logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled Windows mouse event: %#X.\n", + __FUNCTION__, __LINE__, (unsigned int) wParam); + break; + } + + LRESULT hook_result = -1; + if (nCode < 0 || event.reserved ^ 0x01) { + hook_result = CallNextHookEx(mouse_event_hhook, nCode, wParam, lParam); + } + else { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%li)\n", + __FUNCTION__, __LINE__, (long) hook_result); + } + + return hook_result; +} + + +// Callback function that handles events. +void CALLBACK win_hook_event_proc(HWINEVENTHOOK hook, DWORD event, HWND hWnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime) { + switch (event) { + case EVENT_OBJECT_NAMECHANGE: + logger(LOG_LEVEL_INFO, "%s [%u]: Restarting Windows input hook on window event: %#X.\n", + __FUNCTION__, __LINE__, event); + + // Remove any keyboard or mouse hooks that are still running. + if (keyboard_event_hhook != NULL) { + UnhookWindowsHookEx(keyboard_event_hhook); + } + + if (mouse_event_hhook != NULL) { + UnhookWindowsHookEx(mouse_event_hhook); + } + + // Restart the event hooks. + keyboard_event_hhook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_hook_event_proc, hInst, 0); + mouse_event_hhook = SetWindowsHookEx(WH_MOUSE_LL, mouse_hook_event_proc, hInst, 0); + + // Re-initialize modifier masks. + initialize_modifiers(); + + // FIXME We should compare the modifier mask before and after the restart + // to determine if we should synthesize missing events. + + // Check for event hook error. + if (keyboard_event_hhook == NULL || mouse_event_hhook == NULL) { + logger(LOG_LEVEL_ERROR, "%s [%u]: SetWindowsHookEx() failed! (%#lX)\n", + __FUNCTION__, __LINE__, (unsigned long) GetLastError()); + } + break; + + default: + logger(LOG_LEVEL_INFO, "%s [%u]: Unhandled Windows window event: %#X.\n", + __FUNCTION__, __LINE__, event); + } +} + + +IOHOOK_API int hook_run() { + int status = IOHOOK_FAILURE; + + // Set the thread id we want to signal later. + hook_thread_id = GetCurrentThreadId(); + + // Spot check the hInst incase the library was statically linked and DllMain + // did not receive a pointer on load. + if (hInst == NULL) { + logger(LOG_LEVEL_INFO, "%s [%u]: hInst was not set by DllMain().\n", + __FUNCTION__, __LINE__); + + hInst = GetModuleHandle(NULL); + if (hInst != NULL) { + // Initialize native input helper functions. + load_input_helper(); + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Could not determine hInst for SetWindowsHookEx()! (%#lX)\n", + __FUNCTION__, __LINE__, (unsigned long) GetLastError()); + + status = IOHOOK_ERROR_GET_MODULE_HANDLE; + } + } + + // Create the native hooks. + keyboard_event_hhook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_hook_event_proc, hInst, 0); + mouse_event_hhook = SetWindowsHookEx(WH_MOUSE_LL, mouse_hook_event_proc, hInst, 0); + + // Create a window event hook to listen for capture change. + win_event_hhook = SetWinEventHook( + EVENT_OBJECT_NAMECHANGE, EVENT_OBJECT_NAMECHANGE, + NULL, + win_hook_event_proc, + 0, 0, + WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); + + // If we did not encounter a problem, start processing events. + if (keyboard_event_hhook != NULL && mouse_event_hhook != NULL) { + if (win_event_hhook == NULL) { + logger(LOG_LEVEL_WARN, "%s [%u]: SetWinEventHook() failed!\n", + __FUNCTION__, __LINE__); + } + + logger(LOG_LEVEL_DEBUG, "%s [%u]: SetWindowsHookEx() successful.\n", + __FUNCTION__, __LINE__); + + // Check and setup modifiers. + initialize_modifiers(); + + // Set the exit status. + status = IOHOOK_SUCCESS; + + // Windows does not have a hook start event or callback so we need to + // manually fake it. + hook_start_proc(); + + // Block until the thread receives an WM_QUIT request. + MSG message; + while (GetMessage(&message, (HWND) NULL, 0, 0) > 0) { + TranslateMessage(&message); + DispatchMessage(&message); + } + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: SetWindowsHookEx() failed! (%#lX)\n", + __FUNCTION__, __LINE__, (unsigned long) GetLastError()); + + status = IOHOOK_ERROR_SET_WINDOWS_HOOK_EX; + } + + + // Unregister any hooks that may still be installed. + unregister_running_hooks(); + + // We must explicitly call the cleanup handler because Windows does not + // provide a thread cleanup method like POSIX pthread_cleanup_push/pop. + hook_stop_proc(); + + return status; +} + +IOHOOK_API int hook_stop() { + int status = IOHOOK_FAILURE; + + // Try to exit the thread naturally. + if (PostThreadMessage(hook_thread_id, WM_QUIT, (WPARAM) NULL, (LPARAM) NULL)) { + status = IOHOOK_SUCCESS; + } + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Status: %#X.\n", + __FUNCTION__, __LINE__, status); + + return status; +} diff --git a/event/hook/windows/input.h b/event/hook/windows/input.h new file mode 100644 index 0000000..6ba9817 --- /dev/null +++ b/event/hook/windows/input.h @@ -0,0 +1,104 @@ +/*********************************************************************** + Input + ***********************************************************************/ + +#ifndef _included_input_helper +#define _included_input_helper + +#include +#include + +#ifndef LPFN_ISWOW64PROCESS +typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); +#endif + +typedef void* (CALLBACK *KbdLayerDescriptor) (VOID); + +#define CAPLOK 0x01 +#define WCH_NONE 0xF000 +#define WCH_DEAD 0xF001 + +#ifndef WM_MOUSEHWHEEL +#define WM_MOUSEHWHEEL 0x020E +#endif + +typedef struct _VK_TO_WCHARS { + BYTE VirtualKey; + BYTE Attributes; + WCHAR wch[]; +} VK_TO_WCHARS, *PVK_TO_WCHARS; + +typedef struct _LIGATURE { + BYTE VirtualKey; + WORD ModificationNumber; + WCHAR wch[]; +} LIGATURE, *PLIGATURE; + +typedef struct _VK_TO_BIT { + BYTE Vk; + BYTE ModBits; +} VK_TO_BIT, *PVK_TO_BIT; + +typedef struct _MODIFIERS { + PVK_TO_BIT pVkToBit; // __ptr64 + WORD wMaxModBits; + BYTE ModNumber[]; +} MODIFIERS, *PMODIFIERS; + +typedef struct _VSC_VK { + BYTE Vsc; + USHORT Vk; +} VSC_VK, *PVSC_VK; + +typedef struct _VK_TO_WCHAR_TABLE { + PVK_TO_WCHARS pVkToWchars; // __ptr64 + BYTE nModifications; + BYTE cbSize; +} VK_TO_WCHAR_TABLE, *PVK_TO_WCHAR_TABLE; + +typedef struct _DEADKEY { + DWORD dwBoth; + WCHAR wchComposed; + USHORT uFlags; +} DEADKEY, *PDEADKEY; + +typedef struct _VSC_LPWSTR { + BYTE vsc; + WCHAR *pwsz; // __ptr64 +} VSC_LPWSTR, *PVSC_LPWSTR; + +typedef struct tagKbdLayer { + PMODIFIERS pCharModifiers; // __ptr64 + PVK_TO_WCHAR_TABLE pVkToWcharTable; // __ptr64 + PDEADKEY pDeadKey; // __ptr64 + PVSC_LPWSTR pKeyNames; // __ptr64 + PVSC_LPWSTR pKeyNamesExt; // __ptr64 + WCHAR **pKeyNamesDead; // __ptr64 + USHORT *pusVSCtoVK; // __ptr64 + BYTE bMaxVSCtoVK; + PVSC_VK pVSCtoVK_E0; // __ptr64 + PVSC_VK pVSCtoVK_E1; // __ptr64 + DWORD fLocaleFlags; + BYTE nLgMax; + BYTE cbLgEntry; + PLIGATURE pLigature; // __ptr64 + DWORD dwType; + DWORD dwSubType; +} KBDTABLES, *PKBDTABLES; // __ptr64 + + +extern SIZE_T keycode_to_unicode(DWORD keycode, PWCHAR buffer, SIZE_T size); + +//extern DWORD unicode_to_keycode(wchar_t unicode); + +extern unsigned short keycode_to_scancode(DWORD vk_code, DWORD flags); + +extern DWORD scancode_to_keycode(unsigned short scancode); + +// Initialize the locale list and wow64 pointer size. +extern int load_input_helper(); + +// Cleanup the initialized locales. +extern int unload_input_helper(); + +#endif diff --git a/event/hook/windows/input_c.h b/event/hook/windows/input_c.h new file mode 100644 index 0000000..f061b1b --- /dev/null +++ b/event/hook/windows/input_c.h @@ -0,0 +1,832 @@ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include +#include +#include +#include + +#include "../iohook.h" +#include "../logger_c.h" +#include "input.h" + +static const uint16_t keycode_scancode_table[][2] = { + /* idx { vk_code, scancode }, */ + /* 0 */ { VC_UNDEFINED, 0x0000 }, // 0x00 + /* 1 */ { MOUSE_BUTTON1, VK_ESCAPE }, // 0x01 + /* 2 */ { MOUSE_BUTTON2, 0x0031 }, // 0x02 + /* 3 */ { VC_UNDEFINED, 0x0032 }, // 0x03 VK_CANCEL + /* 4 */ { MOUSE_BUTTON3, 0x0033 }, // 0x04 + /* 5 */ { MOUSE_BUTTON4, 0x0034 }, // 0x05 + /* 6 */ { MOUSE_BUTTON5, 0x0035 }, // 0x06 + /* 7 */ { VC_UNDEFINED, 0x0036 }, // 0x07 Undefined + /* 8 */ { VC_BACKSPACE, 0x0037 }, // 0x08 VK_BACK + /* 9 */ { VC_TAB, 0x0038 }, // 0x09 VK_TAB + /* 10 */ { VC_UNDEFINED, 0x0039 }, // 0x0A Reserved + /* 11 */ { VC_UNDEFINED, 0x0030 }, // 0x0B Reserved + /* 12 */ { VC_CLEAR, VK_OEM_MINUS }, // 0x0C VK_CLEAR + /* 13 */ { VC_ENTER, VK_OEM_PLUS }, // 0x0D VK_RETURN + /* 14 */ { VC_UNDEFINED, VK_BACK }, // 0x0E Undefined + /* 15 */ { VC_UNDEFINED, VK_TAB }, // 0x0F Undefined + /* 16 */ { VC_SHIFT_L, 0x0051 }, // 0x10 VK_SHIFT + /* 17 */ { VC_CONTROL_L, 0x0057 }, // 0x11 VK_CONTROL + /* 18 */ { VC_ALT_L, 0x0045 }, // 0x12 VK_MENU ALT key + /* 19 */ { VC_PAUSE, 0x0052 }, // 0x13 VK_PAUSE + /* 20 */ { VC_CAPS_LOCK, 0x0054 }, // 0x14 VK_CAPITAL CAPS LOCK key + /* 21 */ { VC_KATAKANA, 0x0059 }, // 0x15 VK_KANA IME Kana mode + /* 22 */ { VC_UNDEFINED, 0x0055 }, // 0x16 Undefined + /* 23 */ { VC_UNDEFINED, 0x0049 }, // 0x17 VK_JUNJA IME Junja mode + /* 24 */ { VC_UNDEFINED, 0x004F }, // 0x18 VK_FINAL + /* 25 */ { VC_KANJI, 0x0050 }, // 0x19 VK_KANJI / VK_HANJA IME Kanji / Hanja mode + /* 26 */ { VC_UNDEFINED, 0x00DB }, // 0x1A Undefined + /* 27 */ { VC_ESCAPE, 0x00DD }, // 0x1B VK_ESCAPE ESC key + /* 28 */ { VC_UNDEFINED, VK_RETURN }, // 0x1C VK_CONVERT IME convert// 0x1C + /* 29 */ { VC_UNDEFINED, VK_LCONTROL }, // 0x1D VK_NONCONVERT IME nonconvert + /* 30 */ { VC_UNDEFINED, 0x0041 }, // 0x1E VK_ACCEPT IME accept + /* 31 */ { VC_UNDEFINED, 0x0053 }, // 0x1F VK_MODECHANGE IME mode change request + /* 32 */ { VC_SPACE, 0x0044 }, // 0x20 VK_SPACE SPACEBAR + /* 33 */ { VC_PAGE_UP, 0x0046 }, // 0x21 VK_PRIOR PAGE UP key + /* 34 */ { VC_PAGE_DOWN, 0x0047 }, // 0x22 VK_NEXT PAGE DOWN key + /* 35 */ { VC_END, 0x0048 }, // 0x23 VK_END END key + /* 36 */ { VC_HOME, 0x004A }, // 0x24 VK_HOME HOME key + /* 37 */ { VC_LEFT, 0x004B }, // 0x25 VK_LEFT LEFT ARROW key + /* 38 */ { VC_UP, 0x004C }, // 0x26 VK_UP UP ARROW key + /* 39 */ { VC_RIGHT, VK_OEM_1 }, // 0x27 VK_RIGHT RIGHT ARROW key + /* 40 */ { VC_DOWN, VK_OEM_7 }, // 0x28 VK_DOWN DOWN ARROW key + /* 41 */ { VC_UNDEFINED, VK_OEM_3 }, // 0x29 VK_SELECT SELECT key + /* 42 */ { VC_UNDEFINED, VK_LSHIFT }, // 0x2A VK_PRINT PRINT key + /* 43 */ { VC_UNDEFINED, VK_OEM_5 }, // 0x2B VK_EXECUTE EXECUTE key + /* 44 */ { VC_PRINTSCREEN, 0x005A }, // 0x2C VK_SNAPSHOT PRINT SCREEN key + /* 45 */ { VC_INSERT, 0x0058 }, // 0x2D VK_INSERT INS key + /* 46 */ { VC_DELETE, 0x0043 }, // 0x2E VK_DELETE DEL key + /* 47 */ { VC_UNDEFINED, 0x0056 }, // 0x2F VK_HELP HELP key + /* 48 */ { VC_0, 0x0042 }, // 0x30 0 key + /* 49 */ { VC_1, 0x004E }, // 0x31 1 key + /* 50 */ { VC_2, 0x004D }, // 0x32 2 key + /* 51 */ { VC_3, VK_OEM_COMMA }, // 0x33 3 key + /* 52 */ { VC_4, VK_OEM_PERIOD }, // 0x34 4 key + /* 53 */ { VC_5, VK_OEM_2 }, // 0x35 5 key + /* 54 */ { VC_6, VK_RSHIFT }, // 0x36 6 key + /* 55 */ { VC_7, VK_MULTIPLY }, // 0x37 7 key + /* 56 */ { VC_8, VK_LMENU }, // 0x38 8 key + /* 57 */ { VC_9, VK_SPACE }, // 0x39 9 key + /* 58 */ { VC_UNDEFINED, VK_CAPITAL }, // 0x3A Undefined + /* 59 */ { VC_UNDEFINED, VK_F1 }, // 0x3B Undefined + /* 60 */ { VC_UNDEFINED, VK_F2 }, // 0x3C Undefined + /* 61 */ { VC_UNDEFINED, VK_F3 }, // 0x3D Undefined + /* 62 */ { VC_UNDEFINED, VK_F4 }, // 0x3E Undefined + /* 63 */ { VC_UNDEFINED, VK_F5 }, // 0x3F Undefined + /* 64 */ { VC_UNDEFINED, VK_F6 }, // 0x40 Undefined + /* 65 */ { VC_A, VK_F7 }, // 0x41 A key + /* 66 */ { VC_B, VK_F8 }, // 0x42 B key + /* 67 */ { VC_C, VK_F9 }, // 0x43 C key + /* 68 */ { VC_D, VK_F10 }, // 0x44 D key + /* 69 */ { VC_E, VK_NUMLOCK }, // 0x45 E key + /* 70 */ { VC_F, VK_SCROLL }, // 0x46 F key + /* 71 */ { VC_G, VK_NUMPAD7 }, // 0x47 G key + /* 72 */ { VC_H, VK_NUMPAD8 }, // 0x48 H key + /* 73 */ { VC_I, VK_NUMPAD9 }, // 0x49 I key + /* 74 */ { VC_J, VK_SUBTRACT }, // 0x4A J key + /* 75 */ { VC_K, VK_NUMPAD4 }, // 0x4B K key + /* 76 */ { VC_L, VK_NUMPAD5 }, // 0x4C L key + /* 77 */ { VC_M, VK_NUMPAD6 }, // 0x4D M key + /* 78 */ { VC_N, VK_ADD }, // 0x4E N key + /* 79 */ { VC_O, VK_NUMPAD1 }, // 0x4F O key + /* 80 */ { VC_P, VK_NUMPAD2 }, // 0x50 P key + /* 81 */ { VC_Q, VK_NUMPAD3 }, // 0x51 Q key + /* 82 */ { VC_R, VK_NUMPAD0 }, // 0x52 R key + /* 83 */ { VC_S, VK_DECIMAL }, // 0x53 S key + /* 84 */ { VC_T, 0x0000 }, // 0x54 T key + /* 85 */ { VC_U, 0x0000 }, // 0x55 U key + /* 86 */ { VC_V, 0x0000 }, // 0x56 V key + /* 87 */ { VC_W, VK_F11 }, // 0x57 W key + /* 88 */ { VC_X, VK_F12 }, // 0x58 X key + /* 89 */ { VC_Y, 0x0000 }, // 0x59 Y key + /* 90 */ { VC_Z, 0x0000 }, // 0x5A Z key + /* 91 */ { VC_META_L, VK_F13 }, // 0x5B VK_LWIN Left Windows key (Natural keyboard) + /* 92 */ { VC_META_R, VK_F14 }, // 0x5C VK_RWIN Right Windows key (Natural keyboard) + /* 93 */ { VC_CONTEXT_MENU, VK_F15 }, // 0x5D VK_APPS Applications key (Natural keyboard) + /* 94 */ { VC_UNDEFINED, 0x0000 }, // 0x5E Reserved + /* 95 */ { VC_SLEEP, 0x0000 }, // 0x5F VK_SLEEP Computer Sleep key + /* 96 */ { VC_KP_0, 0x0000 }, // 0x60 VK_NUMPAD0 Numeric keypad 0 key + /* 97 */ { VC_KP_1, 0x0000 }, // 0x61 VK_NUMPAD1 Numeric keypad 1 key + /* 98 */ { VC_KP_2, 0x0000 }, // 0x62 VK_NUMPAD2 Numeric keypad 2 key + /* 99 */ { VC_KP_3, VK_F16 }, // 0x63 VK_NUMPAD3 Numeric keypad 3 key + /* 100 */ { VC_KP_4, VK_F17 }, // 0x64 VK_NUMPAD4 Numeric keypad 4 key + /* 101 */ { VC_KP_5, VK_F18 }, // 0x65 VK_NUMPAD5 Numeric keypad 5 key + /* 102 */ { VC_KP_6, VK_F19 }, // 0x66 VK_NUMPAD6 Numeric keypad 6 key + /* 103 */ { VC_KP_7, VK_F20 }, // 0x67 VK_NUMPAD7 Numeric keypad 7 key + /* 104 */ { VC_KP_8, VK_F21 }, // 0x68 VK_NUMPAD8 Numeric keypad 8 key + /* 105 */ { VC_KP_9, VK_F22 }, // 0x69 VK_NUMPAD9 Numeric keypad 9 key + /* 106 */ { VC_KP_MULTIPLY, VK_F23 }, // 0x6A VK_MULTIPLY Multiply key + /* 107 */ { VC_KP_ADD, VK_F24 }, // 0x6B VK_ADD Add key + /* 108 */ { VC_UNDEFINED, 0x0000 }, // 0x6C VK_SEPARATOR Separator key + /* 109 */ { VC_KP_SUBTRACT, 0x0000 }, // 0x6D VK_SUBTRACT Subtract key + /* 110 */ { VC_KP_SEPARATOR, 0x0000 }, // 0x6E VK_DECIMAL Decimal key + /* 111 */ { VC_KP_DIVIDE, 0x0000 }, // 0x6F VK_DIVIDE Divide key + /* 112 */ { VC_F1, VK_KANA }, // 0x70 VK_F1 F1 key + /* 113 */ { VC_F2, 0x0000 }, // 0x71 VK_F2 F2 key + /* 114 */ { VC_F3, 0x0000 }, // 0x72 VK_F3 F3 key + /* 115 */ { VC_F4, 0x0000 }, // 0x73 VK_F4 F4 key + /* 116 */ { VC_F5, 0x0000 }, // 0x74 VK_F5 F5 key + /* 117 */ { VC_F6, 0x0000 }, // 0x75 VK_F6 F6 key + /* 118 */ { VC_F7, 0x0000 }, // 0x76 VK_F7 F7 key + /* 119 */ { VC_F8, 0x0000 }, // 0x77 VK_F8 F8 key + /* 120 */ { VC_F9, 0x0000 }, // 0x78 VK_F9 F9 key + /* 121 */ { VC_F10, VK_KANJI }, // 0x79 VK_F10 F10 key + /* 122 */ { VC_F11, 0x0000 }, // 0x7A VK_F11 F11 key + /* 123 */ { VC_F12, 0x0000 }, // 0x7B VK_F12 F12 key + /* 124 */ { VC_F13, 0x0000 }, // 0x7C VK_F13 F13 key + /* 125 */ { VC_F14, VK_OEM_8 }, // 0x7D VK_F14 F14 key + /* 126 */ { VC_F15, 0x0000 }, // 0x7E VK_F15 F15 key + /* 127 */ { VC_F16, 0x0000 }, // 0x7F VK_F16 F16 key + + // No Offset Offset (i & 0x007F) | 0x80 + + /* 128 */ { VC_F17, 0x0000 }, // 0x80 VK_F17 F17 key + /* 129 */ { VC_F18, 0x0000 }, // 0x81 VK_F18 F18 key + /* 130 */ { VC_F19, 0x0000 }, // 0x82 VK_F19 F19 key + /* 131 */ { VC_F20, 0x0000 }, // 0x83 VK_F20 F20 key + /* 132 */ { VC_F21, 0x0000 }, // 0x84 VK_F21 F21 key + /* 133 */ { VC_F22, 0x0000 }, // 0x85 VK_F22 F22 key + /* 134 */ { VC_F23, 0x0000 }, // 0x86 VK_F23 F23 key + /* 135 */ { VC_F24, 0x0000 }, // 0x87 VK_F24 F24 key + /* 136 */ { VC_UNDEFINED, 0x0000 }, // 0x88 Unassigned + /* 137 */ { VC_UNDEFINED, 0x0000 }, // 0x89 Unassigned + /* 138 */ { VC_UNDEFINED, 0x0000 }, // 0x8A Unassigned + /* 139 */ { VC_UNDEFINED, 0x0000 }, // 0x8B Unassigned + /* 140 */ { VC_UNDEFINED, 0x0000 }, // 0x8C Unassigned + /* 141 */ { VC_UNDEFINED, 0x0000 }, // 0x8D Unassigned + /* 142 */ { VC_UNDEFINED, 0x0000 }, // 0x8E Unassigned + /* 143 */ { VC_UNDEFINED, 0x0000 }, // 0x8F Unassigned + /* 144 */ { VC_NUM_LOCK, VK_MEDIA_PREV_TRACK }, // 0x90 VK_NUMLOCK NUM LOCK key + /* 145 */ { VC_SCROLL_LOCK, 0x0000 }, // 0x91 VK_SCROLL SCROLL LOCK key + /* 146 */ { VC_UNDEFINED, 0x0000 }, // 0x92 OEM specific + /* 147 */ { VC_UNDEFINED, 0x0000 }, // 0x93 OEM specific + /* 148 */ { VC_UNDEFINED, 0x0000 }, // 0x94 OEM specific + /* 149 */ { VC_UNDEFINED, 0x0000 }, // 0x95 OEM specific + /* 150 */ { VC_UNDEFINED, 0x0000 }, // 0x96 OEM specific + /* 151 */ { VC_UNDEFINED, 0x0000 }, // 0x97 Unassigned + /* 152 */ { VC_UNDEFINED, 0x0000 }, // 0x98 Unassigned + /* 153 */ { VC_UNDEFINED, VK_MEDIA_NEXT_TRACK }, // 0x99 Unassigned + /* 154 */ { VC_UNDEFINED, 0x0000 }, // 0x9A Unassigned + /* 155 */ { VC_UNDEFINED, 0x0000 }, // 0x9B Unassigned + /* 156 */ { VC_UNDEFINED, 0x0000 }, // 0x9C Unassigned + /* 157 */ { VC_UNDEFINED, VK_RCONTROL }, // 0x9D Unassigned + /* 158 */ { VC_UNDEFINED, 0x0000 }, // 0x9E Unassigned + /* 159 */ { VC_UNDEFINED, 0x0000 }, // 0x9F Unassigned + /* 160 */ { VC_SHIFT_L, VK_VOLUME_MUTE }, // 0xA0 VK_LSHIFT Left SHIFT key + /* 161 */ { VC_SHIFT_R, VK_LAUNCH_APP2 }, // 0xA1 VK_RSHIFT Right SHIFT key + /* 162 */ { VC_CONTROL_L, VK_MEDIA_PLAY_PAUSE }, // 0xA2 VK_LCONTROL Left CONTROL key + /* 163 */ { VC_CONTROL_R, 0x0000 }, // 0xA3 VK_RCONTROL Right CONTROL key + /* 164 */ { VC_ALT_L, VK_MEDIA_STOP }, // 0xA4 VK_LMENU Left MENU key + /* 165 */ { VC_ALT_R, 0x0000 }, // 0xA5 VK_RMENU Right MENU key + /* 166 */ { VC_BROWSER_BACK, 0x0000 }, // 0xA6 VK_BROWSER_BACK Browser Back key + /* 167 */ { VC_BROWSER_FORWARD, 0x0000 }, // 0xA7 VK_BROWSER_FORWARD Browser Forward key + /* 168 */ { VC_BROWSER_REFRESH, 0x0000 }, // 0xA8 VK_BROWSER_REFRESH Browser Refresh key + /* 169 */ { VC_BROWSER_STOP, 0x0000 }, // 0xA9 VK_BROWSER_STOP Browser Stop key + /* 170 */ { VC_BROWSER_SEARCH, 0x0000 }, // 0xAA VK_BROWSER_SEARCH Browser Search key + /* 171 */ { VC_BROWSER_FAVORITES, 0x0000 }, // 0xAB VK_BROWSER_FAVORITES Browser Favorites key + /* 172 */ { VC_BROWSER_HOME, 0x0000 }, // 0xAC VK_BROWSER_HOME Browser Start and Home key + /* 173 */ { VC_VOLUME_MUTE, 0x0000 }, // 0xAD VK_VOLUME_MUTE Volume Mute key + /* 174 */ { VC_VOLUME_DOWN, VK_VOLUME_DOWN }, // 0xAE VK_VOLUME_DOWN Volume Down key + /* 175 */ { VC_VOLUME_UP, 0x0000 }, // 0xAF VK_VOLUME_UP Volume Up key + /* 176 */ { VC_MEDIA_NEXT, VK_VOLUME_UP }, // 0xB0 VK_MEDIA_NEXT_TRACK Next Track key + /* 177 */ { VC_MEDIA_PREVIOUS, 0x0000 }, // 0xB1 VK_MEDIA_PREV_TRACK Previous Track key + /* 178 */ { VC_MEDIA_STOP, VK_BROWSER_HOME }, // 0xB2 VK_MEDIA_STOP Stop Media key + /* 179 */ { VC_MEDIA_PLAY, 0x0000 }, // 0xB3 VK_MEDIA_PLAY_PAUSE Play/Pause Media key + /* 180 */ { VC_UNDEFINED, 0x0000 }, // 0xB4 VK_LAUNCH_MAIL Start Mail key + /* 181 */ { VC_MEDIA_SELECT, VK_DIVIDE }, // 0xB5 VK_LAUNCH_MEDIA_SELECT Select Media key + /* 182 */ { VC_APP_MAIL, 0x0000 }, // 0xB6 VK_LAUNCH_APP1 Start Application 1 key + /* 183 */ { VC_APP_CALCULATOR, VK_SNAPSHOT }, // 0xB7 VK_LAUNCH_APP2 Start Application 2 key + /* 184 */ { VC_UNDEFINED, VK_RMENU }, // 0xB8 Reserved + /* 185 */ { VC_UNDEFINED, 0x0000 }, // 0xB9 Reserved + /* 186 */ { VC_SEMICOLON, 0x0000 }, // 0xBA VK_OEM_1 Varies by keyboard. For the US standard keyboard, the ';:' key + /* 187 */ { VC_EQUALS, 0x0000 }, // 0xBB VK_OEM_PLUS For any country/region, the '+' key + /* 188 */ { VC_COMMA, 0x00E6 }, // 0xBC VK_OEM_COMMA For any country/region, the ',' key + /* 189 */ { VC_MINUS, 0x0000 }, // 0xBD VK_OEM_MINUS For any country/region, the '-' key + /* 190 */ { VC_PERIOD, 0x0000 }, // 0xBE VK_OEM_PERIOD For any country/region, the '.' key + /* 191 */ { VC_SLASH, 0x0000 }, // 0xBF VK_OEM_2 Varies by keyboard. For the US standard keyboard, the '/?' key + /* 192 */ { VC_BACKQUOTE, 0x0000 }, // 0xC0 VK_OEM_3 Varies by keyboard. For the US standard keyboard, the '`~' key + /* 193 */ { VC_UNDEFINED, 0x0000 }, // 0xC1 Reserved + /* 194 */ { VC_UNDEFINED, 0x0000 }, // 0xC2 Reserved + /* 195 */ { VC_UNDEFINED, 0x0000 }, // 0xC3 Reserved + /* 196 */ { VC_UNDEFINED, 0x0000 }, // 0xC4 Reserved + /* 197 */ { VC_UNDEFINED, VK_PAUSE }, // 0xC5 Reserved + /* 198 */ { VC_UNDEFINED, 0x0000 }, // 0xC6 Reserved + /* 199 */ { VC_UNDEFINED, VK_HOME }, // 0xC7 Reserved + /* 200 */ { VC_UNDEFINED, VK_UP }, // 0xC8 Reserved + /* 201 */ { VC_UNDEFINED, VK_PRIOR }, // 0xC9 Reserved + /* 202 */ { VC_UNDEFINED, 0x0000 }, // 0xCA Reserved + /* 203 */ { VC_UNDEFINED, VK_LEFT }, // 0xCB Reserved + /* 204 */ { VC_UNDEFINED, VK_CLEAR }, // 0xCC Reserved + /* 205 */ { VC_UNDEFINED, VK_RIGHT }, // 0xCD Reserved + /* 206 */ { VC_UNDEFINED, 0x0000 }, // 0xCE Reserved + /* 207 */ { VC_UNDEFINED, VK_END }, // 0xCF Reserved + /* 208 */ { VC_UNDEFINED, VK_DOWN }, // 0xD0 Reserved + /* 209 */ { VC_UNDEFINED, VK_NEXT }, // 0xD1 Reserved + /* 210 */ { VC_UNDEFINED, VK_INSERT }, // 0xD2 Reserved + /* 211 */ { VC_UNDEFINED, VK_DELETE }, // 0xD3 Reserved + /* 212 */ { VC_UNDEFINED, 0x0000 }, // 0xD4 Reserved + /* 213 */ { VC_UNDEFINED, 0x0000 }, // 0xD5 Reserved + /* 214 */ { VC_UNDEFINED, 0x0000 }, // 0xD6 Reserved + /* 215 */ { VC_UNDEFINED, 0x0000 }, // 0xD7 Reserved + /* 216 */ { VC_UNDEFINED, 0x0000 }, // 0xD8 Unassigned + /* 217 */ { VC_UNDEFINED, 0x0000 }, // 0xD9 Unassigned + /* 218 */ { VC_UNDEFINED, 0x0000 }, // 0xDA Unassigned + /* 219 */ { VC_OPEN_BRACKET, VK_LWIN }, // 0xDB VK_OEM_4 Varies by keyboard. For the US standard keyboard, the '[{' key + /* 220 */ { VC_BACK_SLASH, VK_RWIN }, // 0xDC VK_OEM_5 Varies by keyboard. For the US standard keyboard, the '\|' key + /* 221 */ { VC_CLOSE_BRACKET, VK_APPS }, // 0xDD VK_OEM_6 Varies by keyboard. For the US standard keyboard, the ']}' key + /* 222 */ { VC_QUOTE, 0x0000 }, // 0xDE VK_OEM_7 Varies by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key + /* 223 */ { VC_YEN, VK_SLEEP }, // 0xDF VK_OEM_8 Varies by keyboard. + /* 224 */ { VC_UNDEFINED, 0x0000 }, // 0xE0 Reserved + /* 225 */ { VC_UNDEFINED, 0x0000 }, // 0xE1 OEM specific + /* 226 */ { VC_UNDEFINED, 0x0000 }, // 0xE2 VK_OEM_102 Either the angle bracket key or the backslash key on the RT 102-key keyboard + /* 227 */ { VC_UNDEFINED, 0x0000 }, // 0xE3 OEM specific + /* 228 */ { VC_UNDEFINED, 0x00E5 }, // 0xE4 VC_APP_PICTURES OEM specific + /* 229 */ { VC_APP_PICTURES, VK_BROWSER_SEARCH }, // 0xE5 VK_PROCESSKEY IME PROCESS key + /* 230 */ { VC_APP_MUSIC, VK_BROWSER_FAVORITES }, // 0xE6 OEM specific + /* 231 */ { VC_UNDEFINED, VK_BROWSER_REFRESH }, // 0xE7 VK_PACKET Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. + /* 232 */ { VC_UNDEFINED, VK_BROWSER_STOP }, // 0xE8 Unassigned + /* 233 */ { VC_UNDEFINED, VK_BROWSER_FORWARD }, // 0xE9 OEM specific + /* 234 */ { VC_UNDEFINED, VK_BROWSER_BACK }, // 0xEA OEM specific + /* 235 */ { VC_UNDEFINED, 0x0000 }, // 0xEB OEM specific + /* 236 */ { VC_UNDEFINED, VK_LAUNCH_APP1 }, // 0xEC OEM specific + /* 237 */ { VC_UNDEFINED, VK_LAUNCH_MEDIA_SELECT }, // 0xED OEM specific + /* 238 */ { VC_UNDEFINED, 0x0000 }, // 0xEE OEM specific + /* 239 */ { VC_UNDEFINED, 0x0000 }, // 0xEF OEM specific + /* 240 */ { VC_UNDEFINED, 0x0000 }, // 0xF0 OEM specific + /* 241 */ { VC_UNDEFINED, 0x0000 }, // 0xF1 OEM specific + /* 242 */ { VC_UNDEFINED, 0x0000 }, // 0xF2 OEM specific + /* 243 */ { VC_UNDEFINED, 0x0000 }, // 0xF3 OEM specific + /* 244 */ { VC_UNDEFINED, 0x0000 }, // 0xF4 OEM specific + /* 245 */ { VC_UNDEFINED, 0x0000 }, // 0xF5 OEM specific + /* 246 */ { VC_UNDEFINED, 0x0000 }, // 0xF6 VK_ATTN Attn key + /* 247 */ { VC_UNDEFINED, 0x0000 }, // 0xF7 VK_CRSEL CrSel key + /* 248 */ { VC_UNDEFINED, 0x0000 }, // 0xF8 VK_EXSEL ExSel key + /* 249 */ { VC_UNDEFINED, 0x0000 }, // 0xF9 VK_EREOF Erase EOF key + /* 250 */ { VC_UNDEFINED, 0x0000 }, // 0xFA VK_PLAY Play key + /* 251 */ { VC_UNDEFINED, 0x0000 }, // 0xFB VK_ZOOM Zoom key + /* 252 */ { VC_UNDEFINED, 0x0000 }, // 0xFC VK_NONAME Reserved + /* 253 */ { VC_UNDEFINED, 0x0000 }, // 0xFD + /* 254 */ { VC_CLEAR, 0x0000 }, // 0xFE VK_OEM_CLEAR Clear key + /* 255 */ { VC_UNDEFINED, 0x0000 } // 0xFE Unassigned +}; + +unsigned short keycode_to_scancode(DWORD vk_code, DWORD flags) { + unsigned short scancode = VC_UNDEFINED; + + // Check the vk_code is in range. + // NOTE vk_code >= 0 is assumed because DWORD is unsigned. + if (vk_code < sizeof(keycode_scancode_table) / sizeof(keycode_scancode_table[0])) { + scancode = keycode_scancode_table[vk_code][0]; + + if (flags & LLKHF_EXTENDED) { + logger(LOG_LEVEL_WARN, "%s [%u]: EXTD2, vk_code %li\n", + __FUNCTION__, __LINE__, vk_code); + + switch (vk_code) { + case VK_PRIOR: + case VK_NEXT: + case VK_END: + case VK_HOME: + case VK_LEFT: + case VK_UP: + case VK_RIGHT: + case VK_DOWN: + + case VK_INSERT: + case VK_DELETE: + scancode |= 0xEE00; + break; + + case VK_RETURN: + scancode |= 0x0E00; + break; + } + } + else { + // logger(LOG_LEVEL_WARN, "%s [%u]: Test2, vk_code %li\n", + // __FUNCTION__, __LINE__, vk_code); + } + } + + return scancode; +} + +DWORD scancode_to_keycode(unsigned short scancode) { + unsigned short keycode = 0x0000; + + // Check the vk_code is in range. + // NOTE vk_code >= 0 is assumed because the scancode is unsigned. + if (scancode < 128) { + keycode = keycode_scancode_table[scancode][1]; + } + else { + // Calculate the upper offset based on the lower half of the scancode + 128. + unsigned short int i = (scancode & 0x007F) | 0x80; + + if (i < sizeof(keycode_scancode_table) / sizeof(keycode_scancode_table[1])) { + keycode = keycode_scancode_table[i][1]; + } + } + + return keycode; +} + + +/************************************************************************/ + +// Structure and pointers for the keyboard locale cache. +typedef struct _KeyboardLocale { + HKL id; // Locale ID + HINSTANCE library; // Keyboard DLL instance. + PVK_TO_BIT pVkToBit; // Pointers struct arrays. + PVK_TO_WCHAR_TABLE pVkToWcharTable; + PDEADKEY pDeadKey; + struct _KeyboardLocale* next; +} KeyboardLocale; + +static KeyboardLocale* locale_first = NULL; +static KeyboardLocale* locale_current = NULL; +static WCHAR deadChar = WCH_NONE; + +// Amount of pointer padding to apply for Wow64 instances. +static unsigned short int ptr_padding = 0; + +#if defined(_WIN32) && !defined(_WIN64) +// Small function to check and see if we are executing under Wow64. +static BOOL is_wow64() { + BOOL status = FALSE; + + LPFN_ISWOW64PROCESS pIsWow64Process = (LPFN_ISWOW64PROCESS) + GetProcAddress(GetModuleHandle("kernel32"), "IsWow64Process"); + + if (pIsWow64Process != NULL) { + HANDLE current_proc = GetCurrentProcess(); + + if (!pIsWow64Process(current_proc, &status)) { + status = FALSE; + + logger(LOG_LEVEL_DEBUG, "%s [%u]: pIsWow64Process(%#p, %#p) failed!\n", + __FUNCTION__, __LINE__, current_proc, &status); + } + } + + return status; +} +#endif + +// Locate the DLL that contains the current keyboard layout. +static int get_keyboard_layout_file(char *layoutFile, DWORD bufferSize) { + int status = IOHOOK_FAILURE; + HKEY hKey; + DWORD varType = REG_SZ; + + char kbdName[KL_NAMELENGTH]; + if (GetKeyboardLayoutName(kbdName)) { + char kbdKeyPath[51 + KL_NAMELENGTH]; + snprintf(kbdKeyPath, 51 + KL_NAMELENGTH, "SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s", kbdName); + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCTSTR) kbdKeyPath, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) { + if (RegQueryValueEx(hKey, "Layout File", NULL, &varType, (LPBYTE) layoutFile, &bufferSize) == ERROR_SUCCESS) { + RegCloseKey(hKey); + status = IOHOOK_SUCCESS; + } + } + } + + return status; +} + +static int refresh_locale_list() { + int count = 0; + + // Get the number of layouts the user has activated. + int hkl_size = GetKeyboardLayoutList(0, NULL); + if (hkl_size > 0) { + logger(LOG_LEVEL_INFO, "%s [%u]: GetKeyboardLayoutList(0, NULL) found %i layouts.\n", + __FUNCTION__, __LINE__, hkl_size); + + // Get the thread id that currently has focus for our default. + DWORD focus_pid = GetWindowThreadProcessId(GetForegroundWindow(), NULL); + HKL hlk_focus = GetKeyboardLayout(focus_pid); + HKL hlk_default = GetKeyboardLayout(0); + HKL *hkl_list = malloc(sizeof(HKL) * hkl_size); + + int new_size = GetKeyboardLayoutList(hkl_size, hkl_list); + if (new_size > 0) { + if (new_size != hkl_size) { + logger(LOG_LEVEL_WARN, "%s [%u]: Locale size mismatch! " + "Expected %i, received %i!\n", + __FUNCTION__, __LINE__, hkl_size, new_size); + } + else { + logger(LOG_LEVEL_INFO, "%s [%u]: Received %i locales.\n", + __FUNCTION__, __LINE__, new_size); + } + + KeyboardLocale* locale_previous = NULL; + KeyboardLocale* locale_item = locale_first; + + // Go though the linked list and remove KeyboardLocale's that are + // no longer loaded. + while (locale_item != NULL) { + // Check to see if the old HKL is in the new list. + bool is_loaded = false; + int i; + for (i = 0; i < new_size && !is_loaded; i++) { + if (locale_item->id == hkl_list[i]) { + // Flag and jump out of the loop. + hkl_list[i] = NULL; + is_loaded = true; + } + } + + + if (is_loaded) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Found locale ID %#p in the cache.\n", + __FUNCTION__, __LINE__, locale_item->id); + + // Set the previous local to the current locale. + locale_previous = locale_item; + + // Check and see if the locale is our current active locale. + if (locale_item->id == hlk_focus) { + locale_current = locale_item; + } + + count++; + } + else { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Removing locale ID %#p from the cache.\n", + __FUNCTION__, __LINE__, locale_item->id); + + // If the old id is not in the new list, remove it. + locale_previous->next = locale_item->next; + + // Make sure the locale_current points NULL or something valid. + if (locale_item == locale_current) { + locale_current = NULL; + } + + // Free the memory used by locale_item; + free(locale_item); + + // Set the item to the pervious item to guarantee a next. + locale_item = locale_previous; + } + + // Iterate to the next linked list item. + locale_item = locale_item->next; + } + + + // Insert anything new into the linked list. + int i; + for (i = 0; i < new_size; i++) { + // Check to see if the item was already in the list. + if (hkl_list[i] != NULL) { + // Set the active keyboard layout for this thread to the HKL. + ActivateKeyboardLayout(hkl_list[i], 0x00); + + // Try to pull the current keyboard layout DLL from the registry. + char layoutFile[MAX_PATH]; + if (get_keyboard_layout_file(layoutFile, sizeof(layoutFile)) == IOHOOK_SUCCESS) { + // You can't trust the %SYSPATH%, look it up manually. + char systemDirectory[MAX_PATH]; + if (GetSystemDirectory(systemDirectory, MAX_PATH) != 0) { + char kbdLayoutFilePath[MAX_PATH]; + snprintf(kbdLayoutFilePath, MAX_PATH, "%s\\%s", systemDirectory, layoutFile); + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Loading layout for %#p: %s.\n", + __FUNCTION__, __LINE__, hkl_list[i], layoutFile); + + // Create the new locale item. + locale_item = malloc(sizeof(KeyboardLocale)); + locale_item->id = hkl_list[i]; + locale_item->library = LoadLibrary(kbdLayoutFilePath); + + // Get the function pointer from the library to get the keyboard layer descriptor. + KbdLayerDescriptor pKbdLayerDescriptor = (KbdLayerDescriptor) GetProcAddress(locale_item->library, "KbdLayerDescriptor"); + if (pKbdLayerDescriptor != NULL) { + PKBDTABLES pKbd = pKbdLayerDescriptor(); + + // Store the memory address of the following 3 structures. + BYTE *base = (BYTE *) pKbd; + + // First element of each structure, no offset adjustment needed. + locale_item->pVkToBit = pKbd->pCharModifiers->pVkToBit; + + // Second element of pKbd, +4 byte offset on wow64. + locale_item->pVkToWcharTable = *((PVK_TO_WCHAR_TABLE *) (base + offsetof(KBDTABLES, pVkToWcharTable) + ptr_padding)); + + // Third element of pKbd, +8 byte offset on wow64. + locale_item->pDeadKey = *((PDEADKEY *) (base + offsetof(KBDTABLES, pDeadKey) + (ptr_padding * 2))); + + // This will always be added to the end of the list. + locale_item->next = NULL; + + // Insert the item into the linked list. + if (locale_previous == NULL) { + // If nothing came before, the list is empty. + locale_first = locale_item; + } + else { + // Append the new locale to the end of the list. + locale_previous->next = locale_item; + } + + // Check and see if the locale is our current active locale. + if (locale_item->id == hlk_focus) { + locale_current = locale_item; + } + + // Set the pervious locale item to the new one. + locale_previous = locale_item; + + count++; + } + else { + logger(LOG_LEVEL_ERROR, + "%s [%u]: GetProcAddress() failed for KbdLayerDescriptor!\n", + __FUNCTION__, __LINE__); + + FreeLibrary(locale_item->library); + free(locale_item); + locale_item = NULL; + } + } + else { + logger(LOG_LEVEL_ERROR, + "%s [%u]: GetSystemDirectory() failed!\n", + __FUNCTION__, __LINE__); + } + } + else { + logger(LOG_LEVEL_ERROR, + "%s [%u]: Could not find keyboard map for locale %#p!\n", + __FUNCTION__, __LINE__, hkl_list[i]); + } + } + } + } + else { + logger(LOG_LEVEL_ERROR, + "%s [%u]: GetKeyboardLayoutList() failed!\n", + __FUNCTION__, __LINE__); + + // TODO Try and recover by using the current layout. + // Hint: Use locale_id instead of hkl_list[i] in the loop above. + } + + free(hkl_list); + ActivateKeyboardLayout(hlk_default, 0x00); + } + + return count; +} + +SIZE_T keycode_to_unicode(DWORD keycode, PWCHAR buffer, SIZE_T size) { + // Get the thread id that currently has focus and ask for its current + // locale. + DWORD focus_pid = GetWindowThreadProcessId(GetForegroundWindow(), NULL); + HKL locale_id = GetKeyboardLayout(focus_pid); + + // If the current Locale is not the new locale, search the linked list. + if (locale_current == NULL || locale_current->id != locale_id) { + locale_current = NULL; + KeyboardLocale* locale_item = locale_first; + + // Search the linked list... + while (locale_item != NULL && locale_item->id != locale_id) { + locale_item = locale_item->next; + } + + // You may already be a winner! + if (locale_item != NULL && locale_item->id != locale_id) { + logger(LOG_LEVEL_INFO, + "%s [%u]: Activating keyboard layout %#p.\n", + __FUNCTION__, __LINE__, locale_item->id); + + // Switch the current locale. + locale_current = locale_item; + locale_item = NULL; + + // If they layout changes the dead key state needs to be reset. + // This is consistent with the way Windows handles locale changes. + deadChar = WCH_NONE; + } + else { + logger(LOG_LEVEL_DEBUG, + "%s [%u]: Refreshing locale cache.\n", + __FUNCTION__, __LINE__); + + refresh_locale_list(); + } + } + + // Initialize to empty. + SIZE_T charCount = 0; + // buffer[i] = WCH_NONE; + + // Check and make sure the Unicode helper was loaded. + if (locale_current != NULL) { + logger(LOG_LEVEL_INFO, + "%s [%u]: Using keyboard layout %#p.\n", + __FUNCTION__, __LINE__, locale_current->id); + + int mod = 0; + + int capsLock = (GetKeyState(VK_CAPITAL) & 0x01); + + PVK_TO_BIT pVkToBit = locale_current->pVkToBit; + PVK_TO_WCHAR_TABLE pVkToWcharTable = locale_current->pVkToWcharTable; + PDEADKEY pDeadKey = locale_current->pDeadKey; + + /* Loop over the modifier keys for this locale and determine what is + * currently depressed. Because this is only a structure of two + * bytes, we don't need to worry about the structure padding of __ptr64 + * offsets on Wow64. + */ + bool is_shift = false, is_ctrl = false, is_alt = false; + int i; + for (i = 0; pVkToBit[i].Vk != 0; i++) { + short state = GetAsyncKeyState(pVkToBit[i].Vk); + + // Check to see if the most significant bit is active. + if (state & ~SHRT_MAX) { + if (pVkToBit[i].Vk == VK_SHIFT) { + is_shift = true; + } + else if (pVkToBit[i].Vk == VK_CONTROL) { + is_ctrl = true; + } + else if (pVkToBit[i].Vk == VK_MENU) { + is_alt = true; + } + } + } + + // Check the Shift modifier. + if (is_shift) { + mod = 1; + } + + // Check for the AltGr modifier. + if (is_ctrl && is_alt) { + mod += 3; + } + + // Default 32 bit structure size should be 6 bytes (4 for the pointer and 2 + // additional byte fields) that are padded out to 8 bytes by the compiler. + unsigned short sizeVkToWcharTable = sizeof(VK_TO_WCHAR_TABLE); + #if defined(_WIN32) && !defined(_WIN64) + if (is_wow64()) { + // If we are running under Wow64 the size of the first pointer will be + // 8 bringing the total size to 10 bytes padded out to 16. + sizeVkToWcharTable = (sizeVkToWcharTable + ptr_padding + 7) & -8; + } + #endif + + BYTE *ptrCurrentVkToWcharTable = (BYTE *) pVkToWcharTable; + + int cbSize, n; + do { + // cbSize is used to calculate n, and n is used for the size of pVkToWchars[j].wch[n] + cbSize = *(ptrCurrentVkToWcharTable + offsetof(VK_TO_WCHAR_TABLE, cbSize) + ptr_padding); + n = (cbSize - 2) / 2; + + // Same as VK_TO_WCHARS pVkToWchars[] = pVkToWcharTable[i].pVkToWchars + PVK_TO_WCHARS pVkToWchars = (PVK_TO_WCHARS) ((PVK_TO_WCHAR_TABLE) ptrCurrentVkToWcharTable)->pVkToWchars; + + if (pVkToWchars != NULL && mod < n) { + // pVkToWchars[j].VirtualKey + BYTE *pCurrentVkToWchars = (BYTE *) pVkToWchars; + + do { + if (((PVK_TO_WCHARS) pCurrentVkToWchars)->VirtualKey == keycode) { + if ((((PVK_TO_WCHARS) pCurrentVkToWchars)->Attributes == CAPLOK) && capsLock) { + if (is_shift && mod > 0) { + mod -= 1; + } + else { + mod += 1; + } + } + + // Set the initial unicode char. + WCHAR unicode = ((PVK_TO_WCHARS) pCurrentVkToWchars)->wch[mod]; + + // Increment the pCurrentVkToWchars by the size of wch[n]. + pCurrentVkToWchars += sizeof(VK_TO_WCHARS) + (sizeof(WCHAR) * n); + + + if (unicode == WCH_DEAD) { + // The current unicode char is a dead key... + if (deadChar == WCH_NONE) { + // No previous dead key was set so cache the next + // wchar so we know what to do next time its pressed. + deadChar = ((PVK_TO_WCHARS) pCurrentVkToWchars)->wch[mod]; + } + else { + if (size >= 2) { + // Received a second dead key. + memset(buffer, deadChar, 2); + //buffer[0] = deadChar; + //buffer[1] = deadChar; + + deadChar = WCH_NONE; + charCount = 2; + } + } + } + else if (unicode != WCH_NONE) { + // We are not WCH_NONE or WCH_DEAD + if (size >= 1) { + buffer[0] = unicode; + charCount = 1; + } + } + + break; + } + else { + // Add sizeof WCHAR because we are really an array of WCHAR[n] not WCHAR[] + pCurrentVkToWchars += sizeof(VK_TO_WCHARS) + (sizeof(WCHAR) * n); + } + } while ( ((PVK_TO_WCHARS) pCurrentVkToWchars)->VirtualKey != 0 ); + } + + // This is effectively the same as: ptrCurrentVkToWcharTable = pVkToWcharTable[++i]; + ptrCurrentVkToWcharTable += sizeVkToWcharTable; + } while (cbSize != 0); + + + // If the current local has a dead key set. + if (deadChar != WCH_NONE) { + // Loop over the pDeadKey lookup table for the locale. + int i; + for (i = 0; pDeadKey[i].dwBoth != 0; i++) { + WCHAR baseChar = (WCHAR) pDeadKey[i].dwBoth; + WCHAR diacritic = (WCHAR) (pDeadKey[i].dwBoth >> 16); + + // If we locate an extended dead char, set it. + if (size >= 1 && baseChar == buffer[0] && diacritic == deadChar) { + deadChar = WCH_NONE; + + if (charCount <= size) { + memset(buffer, (WCHAR) pDeadKey[i].wchComposed, charCount); + //buffer[i] = (WCHAR) pDeadKey[i].wchComposed; + } + } + } + } + } + + return charCount; +} + +int load_input_helper() { + int count = 0; + + #if defined(_WIN32) && !defined(_WIN64) + if (is_wow64()) { + ptr_padding = sizeof(void *); + } + #endif + + count = refresh_locale_list(); + + logger(LOG_LEVEL_INFO, + "%s [%u]: refresh_locale_list() found %i locale(s).\n", + __FUNCTION__, __LINE__, count); + + return count; +} + +// This returns the number of locales that were removed. +int unload_input_helper() { + int count = 0; + + // Cleanup and free memory from the old list. + KeyboardLocale* locale_item = locale_first; + while (locale_item != NULL) { + // Remove the first item from the linked list. + FreeLibrary(locale_item->library); + locale_first = locale_item->next; + free(locale_item); + locale_item = locale_first; + + count++; + } + + // Reset the current local. + locale_current = NULL; + + return count; +} diff --git a/event/hook/windows/properties_c.h b/event/hook/windows/properties_c.h new file mode 100644 index 0000000..3c8ea2b --- /dev/null +++ b/event/hook/windows/properties_c.h @@ -0,0 +1,211 @@ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include "../iohook.h" +#include "input.h" +// #include "logger.h" + +// The handle to the DLL module pulled in DllMain on DLL_PROCESS_ATTACH. +HINSTANCE hInst; + +// input_hook.c +extern void unregister_running_hooks(); + + +// Structure for the monitor_enum_proc() callback so we can track the count. +typedef struct _screen_info { + uint8_t count; + screen_data *data; +} screen_info; + + +static BOOL CALLBACK monitor_enum_proc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { + int width = lprcMonitor->right - lprcMonitor->left; + int height = lprcMonitor->bottom - lprcMonitor->top; + int origin_x = lprcMonitor->left; + int origin_y = lprcMonitor->top; + + if (width > 0 && height > 0) { + screen_info *screens = (screen_info *) dwData; + + if (screens->data == NULL) { + screens->data = (screen_data *) malloc(sizeof(screen_data)); + } + else { + screens->data = (screen_data *) realloc(screens, sizeof(screen_data) * screens->count); + } + + screens->data[screens->count++] = (screen_data) { + // Should monitor count start @ zero? Currently it starts at 1. + .number = screens->count, + .x = origin_x, + .y = origin_y, + .width = width, + .height = height + }; + + logger(LOG_LEVEL_INFO, "%s [%u]: Monitor %d: %ldx%ld (%ld, %ld)\n", + __FUNCTION__, __LINE__, screens->count, width, height, origin_x, origin_y); + } + + return TRUE; +} + +IOHOOK_API screen_data* hook_create_screen_info(unsigned char *count) { + // Initialize count to zero. + *count = 0; + + // Create a simple structure to make working with monitor_enum_proc easier. + screen_info screens = { + .count = 0, + .data = NULL + }; + + BOOL status = EnumDisplayMonitors(NULL, NULL, monitor_enum_proc, (LPARAM) &screens); + + if (!status || screens.count == 0) { + // Fallback in case EnumDisplayMonitors fails. + logger(LOG_LEVEL_INFO, "%s [%u]: EnumDisplayMonitors failed. Fallback.\n", + __FUNCTION__, __LINE__); + + int width = GetSystemMetrics(SM_CXSCREEN); + int height = GetSystemMetrics(SM_CYSCREEN); + + if (width > 0 && height > 0) { + screens.data = (screen_data *) malloc(sizeof(screen_data)); + + if (screens.data != NULL) { + *count = 1; + screens.data[0] = (screen_data) { + .number = 1, + .x = 0, + .y = 0, + .width = width, + .height = height + }; + } + } + } else { + // Populate the count. + *count = screens.count; + } + + return screens.data; +} + +IOHOOK_API long int hook_get_auto_repeat_rate() { + long int value = -1; + long int rate; + + if (SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &rate, 0)) { + logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETKEYBOARDSPEED: %li.\n", + __FUNCTION__, __LINE__, rate); + + value = rate; + } + + return value; +} + +IOHOOK_API long int hook_get_auto_repeat_delay() { + long int value = -1; + long int delay; + + if (SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &delay, 0)) { + logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETKEYBOARDDELAY: %li.\n", + __FUNCTION__, __LINE__, delay); + + value = delay; + } + + return value; +} + +IOHOOK_API long int hook_get_pointer_acceleration_multiplier() { + long int value = -1; + int mouse[3]; // 0-Threshold X, 1-Threshold Y and 2-Speed. + + if (SystemParametersInfo(SPI_GETMOUSE, 0, &mouse, 0)) { + logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETMOUSE[2]: %i.\n", + __FUNCTION__, __LINE__, mouse[2]); + + value = mouse[2]; + } + + return value; +} + +IOHOOK_API long int hook_get_pointer_acceleration_threshold() { + long int value = -1; + int mouse[3]; // 0-Threshold X, 1-Threshold Y and 2-Speed. + + if (SystemParametersInfo(SPI_GETMOUSE, 0, &mouse, 0)) { + logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETMOUSE[0]: %i.\n", + __FUNCTION__, __LINE__, mouse[0]); + logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETMOUSE[1]: %i.\n", + __FUNCTION__, __LINE__, mouse[1]); + + // Average the x and y thresholds. + value = (mouse[0] + mouse[1]) / 2; + } + + return value; +} + +IOHOOK_API long int hook_get_pointer_sensitivity() { + long int value = -1; + int sensitivity; + + if (SystemParametersInfo(SPI_GETMOUSESPEED, 0, &sensitivity, 0)) { + logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETMOUSESPEED: %i.\n", + __FUNCTION__, __LINE__, sensitivity); + + value = sensitivity; + } + + return value; +} + +IOHOOK_API long int hook_get_multi_click_time() { + long int value = -1; + UINT clicktime; + + clicktime = GetDoubleClickTime(); + logger(LOG_LEVEL_INFO, "%s [%u]: GetDoubleClickTime: %u.\n", + __FUNCTION__, __LINE__, (unsigned int) clicktime); + + value = (long int) clicktime; + + return value; +} + +// DLL Entry point. +BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpReserved) { + switch (fdwReason) { + case DLL_PROCESS_ATTACH: + // Save the DLL address. + hInst = hInstDLL; + + // Initialize native input helper functions. + load_input_helper(); + break; + + case DLL_PROCESS_DETACH: + // Unregister any hooks that may still be installed. + unregister_running_hooks(); + + // Deinitialize native input helper functions. + unload_input_helper(); + break; + + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + // Do Nothing. + break; + } + + return TRUE; +} diff --git a/event/hook/x11/event_c.h b/event/hook/x11/event_c.h new file mode 100644 index 0000000..50e5ede --- /dev/null +++ b/event/hook/x11/event_c.h @@ -0,0 +1,386 @@ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include +#include +#include +#ifdef USE_XTEST + #include +#endif + +#include "../iohook.h" +#include "input.h" +// #include "../logger.h" + +extern Display *properties_disp; + +// This lookup table must be in the same order the masks are defined. +#ifdef USE_XTEST +static KeySym keymask_lookup[8] = { + XK_Shift_L, + XK_Control_L, + XK_Meta_L, + XK_Alt_L, + + XK_Shift_R, + XK_Control_R, + XK_Meta_R, + XK_Alt_R +}; + +static unsigned int btnmask_lookup[5] = { + MASK_BUTTON1, + MASK_BUTTON2, + MASK_BUTTON3, + MASK_BUTTON4, + MASK_BUTTON5 +}; +#else +// TODO Possibly relocate to input helper. +static unsigned int convert_to_native_mask(unsigned int mask) { + unsigned int native_mask = 0x00; + + if (mask & (MASK_SHIFT)) { native_mask |= ShiftMask; } + if (mask & (MASK_CTRL)) { native_mask |= ControlMask; } + if (mask & (MASK_META)) { native_mask |= Mod4Mask; } + if (mask & (MASK_ALT)) { native_mask |= Mod1Mask; } + + if (mask & MASK_BUTTON1) { native_mask |= Button1Mask; } + if (mask & MASK_BUTTON2) { native_mask |= Button2Mask; } + if (mask & MASK_BUTTON3) { native_mask |= Button3Mask; } + if (mask & MASK_BUTTON4) { native_mask |= Button4Mask; } + if (mask & MASK_BUTTON5) { native_mask |= Button5Mask; } + + return native_mask; +} +#endif + +static inline void post_key_event(iohook_event * const event) { + #ifdef USE_XTEST + // FIXME Currently ignoring EVENT_KEY_TYPED. + if (event->type == EVENT_KEY_PRESSED) { + XTestFakeKeyEvent( + properties_disp, + scancode_to_keycode(event->data.keyboard.keycode), + True, + 0); + } + else if (event->type == EVENT_KEY_RELEASED) { + XTestFakeKeyEvent( + properties_disp, + scancode_to_keycode(event->data.keyboard.keycode), + False, + 0); + } + #else + XKeyEvent key_event; + + key_event.serial = 0x00; + key_event.send_event = False; + key_event.display = properties_disp; + key_event.time = CurrentTime; + key_event.same_screen = True; + + unsigned int mask; + if (!XQueryPointer(properties_disp, DefaultRootWindow(properties_disp), &(key_event.root), &(key_event.subwindow), &(key_event.x_root), &(key_event.y_root), &(key_event.x), &(key_event.y), &mask)) { + key_event.root = DefaultRootWindow(properties_disp); + key_event.window = key_event.root; + key_event.subwindow = None; + + key_event.x_root = 0; + key_event.y_root = 0; + key_event.x = 0; + key_event.y = 0; + } + + key_event.state = convert_to_native_mask(event->mask); + key_event.keycode = XKeysymToKeycode(properties_disp, scancode_to_keycode(event->data.keyboard.keycode)); + + // FIXME Currently ignoring typed events. + if (event->type == EVENT_KEY_PRESSED) { + key_event.type = KeyPress; + XSendEvent(properties_disp, InputFocus, False, KeyPressMask, (XEvent *) &key_event); + } + else if (event->type == EVENT_KEY_RELEASED) { + key_event.type = KeyRelease; + XSendEvent(properties_disp, InputFocus, False, KeyReleaseMask, (XEvent *) &key_event); + } + #endif +} + +static inline void post_mouse_button_event(iohook_event * const event) { + #ifdef USE_XTEST + Window ret_root; + Window ret_child; + int root_x; + int root_y; + int win_x; + int win_y; + unsigned int mask; + + Window win_root = XDefaultRootWindow(properties_disp); + Bool query_status = XQueryPointer(properties_disp, win_root, &ret_root, &ret_child, &root_x, &root_y, &win_x, &win_y, &mask); + if (query_status) { + if (event->data.mouse.x != root_x || event->data.mouse.y != root_y) { + // Move the pointer to the specified position. + XTestFakeMotionEvent(properties_disp, -1, event->data.mouse.x, event->data.mouse.y, 0); + } + else { + query_status = False; + } + } + + if (event->type == EVENT_MOUSE_WHEEL) { + // Wheel events should be the same as click events on X11. + // type, amount and rotation + if (event->data.wheel.rotation < 0) { + XTestFakeButtonEvent(properties_disp, WheelUp, True, 0); + XTestFakeButtonEvent(properties_disp, WheelUp, False, 0); + } + else { + XTestFakeButtonEvent(properties_disp, WheelDown, True, 0); + XTestFakeButtonEvent(properties_disp, WheelDown, False, 0); + } + } + else if (event->type == EVENT_MOUSE_PRESSED) { + XTestFakeButtonEvent(properties_disp, event->data.mouse.button, True, 0); + } + else if (event->type == EVENT_MOUSE_RELEASED) { + XTestFakeButtonEvent(properties_disp, event->data.mouse.button, False, 0); + } + else if (event->type == EVENT_MOUSE_CLICKED) { + XTestFakeButtonEvent(properties_disp, event->data.mouse.button, True, 0); + XTestFakeButtonEvent(properties_disp, event->data.mouse.button, False, 0); + } + + if (query_status) { + // Move the pointer back to the original position. + XTestFakeMotionEvent(properties_disp, -1, root_x, root_y, 0); + } + #else + XButtonEvent btn_event; + + btn_event.serial = 0x00; + btn_event.send_event = False; + btn_event.display = properties_disp; + btn_event.time = CurrentTime; + btn_event.same_screen = True; + + btn_event.root = DefaultRootWindow(properties_disp); + btn_event.window = btn_event.root; + btn_event.subwindow = None; + + btn_event.type = 0x00; + btn_event.state = 0x00; + btn_event.x_root = 0; + btn_event.y_root = 0; + btn_event.x = 0; + btn_event.y = 0; + btn_event.button = 0x00; + + btn_event.state = convert_to_native_mask(event->mask); + + btn_event.x = event->data.mouse.x; + btn_event.y = event->data.mouse.y; + + #if defined(USE_XINERAMA) || defined(USE_XRANDR) + uint8_t screen_count; + screen_data *screens = hook_create_screen_info(&screen_count); + if (screen_count > 1) { + btn_event.x += screens[0].x; + btn_event.y += screens[0].y; + } + + if (screens != NULL) { + free(screens); + } + #endif + + // These are the same because Window == Root Window. + btn_event.x_root = btn_event.x; + btn_event.y_root = btn_event.y; + + if (event->type == EVENT_MOUSE_WHEEL) { + // type, amount and rotation + if (event->data.wheel.rotation < 0) { + btn_event.button = WheelUp; + } + else { + btn_event.button = WheelDown; + } + } + + if (event->type != EVENT_MOUSE_RELEASED) { + // FIXME Where do we set event->button? + btn_event.type = ButtonPress; + XSendEvent(properties_disp, InputFocus, False, ButtonPressMask, (XEvent *) &btn_event); + } + + if (event->type != EVENT_MOUSE_PRESSED) { + btn_event.type = ButtonRelease; + XSendEvent(properties_disp, InputFocus, False, ButtonReleaseMask, (XEvent *) &btn_event); + } + #endif +} + +static inline void post_mouse_motion_event(iohook_event * const event) { + #ifdef USE_XTEST + XTestFakeMotionEvent(properties_disp, -1, event->data.mouse.x, event->data.mouse.y, 0); + #else + XMotionEvent mov_event; + + mov_event.serial = MotionNotify; + mov_event.send_event = False; + mov_event.display = properties_disp; + mov_event.time = CurrentTime; + mov_event.same_screen = True; + mov_event.is_hint = NotifyNormal, + mov_event.root = DefaultRootWindow(properties_disp); + mov_event.window = mov_event.root; + mov_event.subwindow = None; + + mov_event.type = 0x00; + mov_event.state = 0x00; + mov_event.x_root = 0; + mov_event.y_root = 0; + mov_event.x = 0; + mov_event.y = 0; + + mov_event.state = convert_to_native_mask(event->mask); + + mov_event.x = event->data.mouse.x; + mov_event.y = event->data.mouse.y; + + #if defined(USE_XINERAMA) || defined(USE_XRANDR) + uint8_t screen_count; + screen_data *screens = hook_create_screen_info(&screen_count); + if (screen_count > 1) { + mov_event.x += screens[0].x; + mov_event.y += screens[0].y; + } + + if (screens != NULL) { + free(screens); + } + #endif + + // These are the same because Window == Root Window. + mov_event.x_root = mov_event.x; + mov_event.y_root = mov_event.y; + + long int event_mask = NoEventMask; + if (event->type == EVENT_MOUSE_DRAGGED) { + #if Button1Mask == Button1MotionMask && \ + Button2Mask == Button2MotionMask && \ + Button3Mask == Button3MotionMask && \ + Button4Mask == Button4MotionMask && \ + Button5Mask == Button5MotionMask + // This little trick only works if Button#MotionMasks align with + // the Button#Masks. + event_mask = mov_event.state & + (Button1MotionMask | Button2MotionMask | + Button2MotionMask | Button3MotionMask | Button5MotionMask); + #else + // Fallback to some slightly larger... + if (event->state & Button1Mask) { + event_mask |= Button1MotionMask; + } + + if (event->state & Button2Mask) { + event_mask |= Button2MotionMask; + } + + if (event->state & Button3Mask) { + event_mask |= Button3MotionMask; + } + + if (event->state & Button4Mask) { + event_mask |= Button4MotionMask; + } + + if (event->state & Button5Mask) { + event_mask |= Button5MotionMask; + } + #endif + } + + // NOTE x_mask = NoEventMask. + XSendEvent(properties_disp, InputFocus, False, event_mask, (XEvent *) &mov_event); + #endif +} + +IOHOOK_API void hook_post_event(iohook_event * const event) { + XLockDisplay(properties_disp); + + #ifdef USE_XTEST + // XTest does not have modifier support, so we fake it by depressing the + // appropriate modifier keys. + unsigned int i; + for (i = 0; i < sizeof(keymask_lookup) / sizeof(KeySym); i++) { + if (event->mask & 1 << i) { + XTestFakeKeyEvent(properties_disp, XKeysymToKeycode(properties_disp, keymask_lookup[i]), True, 0); + } + } + + unsigned int i; + for (i = 0; i < sizeof(btnmask_lookup) / sizeof(unsigned int); i++) { + if (event->mask & btnmask_lookup[i]) { + XTestFakeButtonEvent(properties_disp, i + 1, True, 0); + } + } + #endif + + switch (event->type) { + case EVENT_KEY_PRESSED: + case EVENT_KEY_RELEASED: + case EVENT_KEY_TYPED: + post_key_event(event); + break; + + case EVENT_MOUSE_PRESSED: + case EVENT_MOUSE_RELEASED: + case EVENT_MOUSE_WHEEL: + case EVENT_MOUSE_CLICKED: + post_mouse_button_event(event); + break; + + case EVENT_MOUSE_DRAGGED: + case EVENT_MOUSE_MOVED: + post_mouse_motion_event(event); + break; + + case EVENT_HOOK_ENABLED: + case EVENT_HOOK_DISABLED: + // Ignore hook enabled / disabled events. + + default: + // Ignore any other garbage. + logger(LOG_LEVEL_WARN, "%s [%u]: Ignoring post event type %#X\n", + __FUNCTION__, __LINE__, event->type); + break; + } + + #ifdef USE_XTEST + // Release the previously held modifier keys used to fake the event mask. + unsigned int i ; + for (i= 0; i < sizeof(keymask_lookup) / sizeof(KeySym); i++) { + if (event->mask & 1 << i) { + XTestFakeKeyEvent(properties_disp, XKeysymToKeycode(properties_disp, keymask_lookup[i]), False, 0); + } + } + unsigned int i; + for (i = 0; i < sizeof(btnmask_lookup) / sizeof(unsigned int); i++) { + if (event->mask & btnmask_lookup[i]) { + XTestFakeButtonEvent(properties_disp, i + 1, False, 0); + } + } + #endif + + // Don't forget to flush! + XSync(properties_disp, True); + XUnlockDisplay(properties_disp); +} diff --git a/event/hook/x11/hook_c.h b/event/hook/x11/hook_c.h new file mode 100644 index 0000000..5813cc2 --- /dev/null +++ b/event/hook/x11/hook_c.h @@ -0,0 +1,1132 @@ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#define USE_XKB 0 +#define USE_XKBCOMMON 0 +#include +#include +#ifdef USE_XRECORD_ASYNC + #include +#endif +#include + +#include +#include +#include +#include +// #ifdef USE_XKB +#include +#include +// #endif +#if defined(USE_XINERAMA) && !defined(USE_XRANDR) + #include +#elif defined(USE_XRANDR) + #include +#else +// TODO We may need to fallback to the xf86vm extension for things like TwinView. +// #pragma message("*** Warning: Xinerama or XRandR support is required to produce cross-platform mouse coordinates for multi-head configurations!") +// #pragma message("... Assuming single-head display.") +#endif + +#include "../iohook.h" +// #include "../logger.h" +#include "input.h" + +// Thread and hook handles. +#ifdef USE_XRECORD_ASYNC +static bool running; + +static pthread_cond_t hook_xrecord_cond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t hook_xrecord_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +typedef struct _hook_info { + struct _data { + Display *display; + XRecordRange *range; + } data; + struct _ctrl { + Display *display; + XRecordContext context; + } ctrl; + struct _input { + #ifdef USE_XKBCOMMON + xcb_connection_t *connection; + struct xkb_context *context; + #endif + uint16_t mask; + struct _mouse { + bool is_dragged; + struct _click { + unsigned short int count; + long int time; + unsigned short int button; + } click; + } mouse; + } input; +} hook_info; +static hook_info *hook; + +// For this struct, refer to libxnee, requires Xlibint.h +typedef union { + unsigned char type; + xEvent event; + xResourceReq req; + xGenericReply reply; + xError error; + xConnSetupPrefix setup; +} XRecordDatum; + +#if defined(USE_XKBCOMMON) +//struct xkb_keymap *keymap; +//struct xkb_state *state = xkb_state_new(keymap); +static struct xkb_state *state = NULL; +#endif + +// Virtual event pointer. +static iohook_event event; + +// Event dispatch callback. +static dispatcher_t dispatcher = NULL; + +IOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n", + __FUNCTION__, __LINE__, dispatch_proc); + + dispatcher = dispatch_proc; +} + +// Send out an event if a dispatcher was set. +static inline void dispatch_event(iohook_event *const event) { + if (dispatcher != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n", + __FUNCTION__, __LINE__, event->type); + + dispatcher(event); + } + else { + logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n", + __FUNCTION__, __LINE__); + } +} + +// Set the native modifier mask for future events. +static inline void set_modifier_mask(uint16_t mask) { + hook->input.mask |= mask; +} + +// Unset the native modifier mask for future events. +static inline void unset_modifier_mask(uint16_t mask) { + hook->input.mask &= ~mask; +} + +// Get the current native modifier mask state. +static inline uint16_t get_modifiers() { + return hook->input.mask; +} + +// Initialize the modifier lock masks. +static void initialize_locks() { + #ifdef USE_XKBCOMMON + + if (xkb_state_led_name_is_active(state, XKB_LED_NAME_CAPS)) { + set_modifier_mask(MASK_CAPS_LOCK); + } + else { + unset_modifier_mask(MASK_CAPS_LOCK); + } + + if (xkb_state_led_name_is_active(state, XKB_LED_NAME_NUM)) { + set_modifier_mask(MASK_NUM_LOCK); + } + else { + unset_modifier_mask(MASK_NUM_LOCK); + } + + if (xkb_state_led_name_is_active(state, XKB_LED_NAME_SCROLL)) { + set_modifier_mask(MASK_SCROLL_LOCK); + } + else { + unset_modifier_mask(MASK_SCROLL_LOCK); + } + #else + unsigned int led_mask = 0x00; + if (XkbGetIndicatorState(hook->ctrl.display, XkbUseCoreKbd, &led_mask) == Success) { + if (led_mask & 0x01) { + set_modifier_mask(MASK_CAPS_LOCK); + } + else { + unset_modifier_mask(MASK_CAPS_LOCK); + } + + if (led_mask & 0x02) { + set_modifier_mask(MASK_NUM_LOCK); + } + else { + unset_modifier_mask(MASK_NUM_LOCK); + } + + if (led_mask & 0x04) { + set_modifier_mask(MASK_SCROLL_LOCK); + } + else { + unset_modifier_mask(MASK_SCROLL_LOCK); + } + } + else { + logger(LOG_LEVEL_WARN, "%s [%u]: XkbGetIndicatorState failed to get current led mask!\n", + __FUNCTION__, __LINE__); + } + #endif +} + +// Initialize the modifier mask to the current modifiers. +static void initialize_modifiers() { + hook->input.mask = 0x0000; + + KeyCode keycode; + char keymap[32]; + XQueryKeymap(hook->ctrl.display, keymap); + + Window unused_win; + int unused_int; + unsigned int mask; + if (XQueryPointer(hook->ctrl.display, DefaultRootWindow(hook->ctrl.display), &unused_win, &unused_win, &unused_int, &unused_int, &unused_int, &unused_int, &mask)) { + if (mask & ShiftMask) { + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_R); } + } + if (mask & ControlMask) { + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_R); } + } + if (mask & Mod1Mask) { + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_R); } + } + if (mask & Mod4Mask) { + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_R); } + } + + if (mask & Button1Mask) { set_modifier_mask(MASK_BUTTON1); } + if (mask & Button2Mask) { set_modifier_mask(MASK_BUTTON2); } + if (mask & Button3Mask) { set_modifier_mask(MASK_BUTTON3); } + if (mask & Button4Mask) { set_modifier_mask(MASK_BUTTON4); } + if (mask & Button5Mask) { set_modifier_mask(MASK_BUTTON5); } + } + else { + logger(LOG_LEVEL_WARN, "%s [%u]: XQueryPointer failed to get current modifiers!\n", + __FUNCTION__, __LINE__); + + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_R); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_R); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_R); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_R); } + } + + initialize_locks(); +} + +void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { + uint64_t timestamp = (uint64_t) recorded_data->server_time; + + if (recorded_data->category == XRecordStartOfData) { + // Populate the hook start event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_HOOK_ENABLED; + event.mask = 0x00; + + // Fire the hook start event. + dispatch_event(&event); + } + else if (recorded_data->category == XRecordEndOfData) { + // Populate the hook stop event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_HOOK_DISABLED; + event.mask = 0x00; + + // Fire the hook stop event. + dispatch_event(&event); + } + else if (recorded_data->category == XRecordFromServer || recorded_data->category == XRecordFromClient) { + // Get XRecord data. + XRecordDatum *data = (XRecordDatum *) recorded_data->data; + + if (data->type == KeyPress) { + // The X11 KeyCode associated with this event. + KeyCode keycode = (KeyCode) data->event.u.u.detail; + KeySym keysym = 0x00; + #if defined(USE_XKBCOMMON) + if (state != NULL) { + keysym = xkb_state_key_get_one_sym(state, keycode); + } + #else + keysym = keycode_to_keysym(keycode, data->event.u.keyButtonPointer.state); + #endif + + unsigned short int scancode = keycode_to_scancode(keycode); + + // TODO If you have a better suggestion for this ugly, let me know. + if (scancode == VC_SHIFT_L) { set_modifier_mask(MASK_SHIFT_L); } + else if (scancode == VC_SHIFT_R) { set_modifier_mask(MASK_SHIFT_R); } + else if (scancode == VC_CONTROL_L) { set_modifier_mask(MASK_CTRL_L); } + else if (scancode == VC_CONTROL_R) { set_modifier_mask(MASK_CTRL_R); } + else if (scancode == VC_ALT_L) { set_modifier_mask(MASK_ALT_L); } + else if (scancode == VC_ALT_R) { set_modifier_mask(MASK_ALT_R); } + else if (scancode == VC_META_L) { set_modifier_mask(MASK_META_L); } + else if (scancode == VC_META_R) { set_modifier_mask(MASK_META_R); } + xkb_state_update_key(state, keycode, XKB_KEY_DOWN); + initialize_locks(); + + if ((get_modifiers() & MASK_NUM_LOCK) == 0) { + switch (scancode) { + case VC_KP_SEPARATOR: + case VC_KP_1: + case VC_KP_2: + case VC_KP_3: + case VC_KP_4: + case VC_KP_5: + case VC_KP_6: + case VC_KP_7: + case VC_KP_8: + case VC_KP_0: + case VC_KP_9: + scancode |= 0xEE00; + break; + } + } + + // Populate key pressed event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_KEY_PRESSED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = scancode; + event.data.keyboard.rawcode = keysym; + event.data.keyboard.keychar = CHAR_UNDEFINED; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X pressed. (%#X)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode); + + // Fire key pressed event. + dispatch_event(&event); + + // If the pressed event was not consumed... + if (event.reserved ^ 0x01) { + uint16_t buffer[2]; + size_t count = 0; + + // Check to make sure the key is printable. + #ifdef USE_XKBCOMMON + if (state != NULL) { + count = keycode_to_unicode(state, keycode, buffer, sizeof(buffer) / sizeof(uint16_t)); + } + #else + count = keysym_to_unicode(keysym, buffer, sizeof(buffer) / sizeof(uint16_t)); + #endif + + unsigned int i; + for (i = 0; i < count; i++) { + // Populate key typed event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_KEY_TYPED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = VC_UNDEFINED; + event.data.keyboard.rawcode = keysym; + event.data.keyboard.keychar = buffer[i]; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X typed. (%lc)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, (uint16_t) event.data.keyboard.keychar); + + // Fire key typed event. + dispatch_event(&event); + } + } + } + else if (data->type == KeyRelease) { + // The X11 KeyCode associated with this event. + KeyCode keycode = (KeyCode) data->event.u.u.detail; + KeySym keysym = 0x00; + #ifdef USE_XKBCOMMON + if (state != NULL) { + keysym = xkb_state_key_get_one_sym(state, keycode); + } + #else + keysym = keycode_to_keysym(keycode, data->event.u.keyButtonPointer.state); + #endif + + unsigned short int scancode = keycode_to_scancode(keycode); + + // TODO If you have a better suggestion for this ugly, let me know. + if (scancode == VC_SHIFT_L) { unset_modifier_mask(MASK_SHIFT_L); } + else if (scancode == VC_SHIFT_R) { unset_modifier_mask(MASK_SHIFT_R); } + else if (scancode == VC_CONTROL_L) { unset_modifier_mask(MASK_CTRL_L); } + else if (scancode == VC_CONTROL_R) { unset_modifier_mask(MASK_CTRL_R); } + else if (scancode == VC_ALT_L) { unset_modifier_mask(MASK_ALT_L); } + else if (scancode == VC_ALT_R) { unset_modifier_mask(MASK_ALT_R); } + else if (scancode == VC_META_L) { unset_modifier_mask(MASK_META_L); } + else if (scancode == VC_META_R) { unset_modifier_mask(MASK_META_R); } + xkb_state_update_key(state, keycode, XKB_KEY_UP); + initialize_locks(); + + if ((get_modifiers() & MASK_NUM_LOCK) == 0) { + switch (scancode) { + case VC_KP_SEPARATOR: + case VC_KP_1: + case VC_KP_2: + case VC_KP_3: + case VC_KP_4: + case VC_KP_5: + case VC_KP_6: + case VC_KP_7: + case VC_KP_8: + case VC_KP_0: + case VC_KP_9: + scancode |= 0xEE00; + break; + } + } + + // Populate key released event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_KEY_RELEASED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = scancode; + event.data.keyboard.rawcode = keysym; + event.data.keyboard.keychar = CHAR_UNDEFINED; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X released. (%#X)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode); + + // Fire key released event. + dispatch_event(&event); + } + else if (data->type == ButtonPress) { + // X11 handles wheel events as button events. + if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelDown + || data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight) { + + // Reset the click count and previous button. + hook->input.mouse.click.count = 1; + hook->input.mouse.click.button = MOUSE_NOBUTTON; + + /* Scroll wheel release events. + * Scroll type: WHEEL_UNIT_SCROLL + * Scroll amount: 3 unit increments per notch + * Units to scroll: 3 unit increments + * Vertical unit increment: 15 pixels + */ + + // Populate mouse wheel event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_WHEEL; + event.mask = get_modifiers(); + + event.data.wheel.clicks = hook->input.mouse.click.count; + event.data.wheel.x = data->event.u.keyButtonPointer.rootX; + event.data.wheel.y = data->event.u.keyButtonPointer.rootY; + + #if defined(USE_XINERAMA) || defined(USE_XRANDR) + uint8_t count; + screen_data *screens = hook_create_screen_info(&count); + if (count > 1) { + event.data.wheel.x -= screens[0].x; + event.data.wheel.y -= screens[0].y; + } + + if (screens != NULL) { + free(screens); + } + #endif + + /* X11 does not have an API call for acquiring the mouse scroll type. This + * maybe part of the XInput2 (XI2) extention but I will wont know until it + * is available on my platform. For the time being we will just use the + * unit scroll value. + */ + event.data.wheel.type = WHEEL_UNIT_SCROLL; + + /* Some scroll wheel properties are available via the new XInput2 (XI2) + * extension. Unfortunately the extension is not available on my + * development platform at this time. For the time being we will just + * use the Windows default value of 3. + */ + event.data.wheel.amount = 3; + + if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelLeft) { + // Wheel Rotated Up and Away. + event.data.wheel.rotation = -1; + } + else { // data->event.u.u.detail == WheelDown + // Wheel Rotated Down and Towards. + event.data.wheel.rotation = 1; + } + + if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelDown) { + // Wheel Rotated Up or Down. + event.data.wheel.direction = WHEEL_VERTICAL_DIRECTION; + } + else { // data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight + // Wheel Rotated Left or Right. + event.data.wheel.direction = WHEEL_HORIZONTAL_DIRECTION; + } + + logger(LOG_LEVEL_INFO, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n", + __FUNCTION__, __LINE__, event.data.wheel.type, + event.data.wheel.amount * event.data.wheel.rotation, + event.data.wheel.direction, + event.data.wheel.x, event.data.wheel.y); + + // Fire mouse wheel event. + dispatch_event(&event); + } + else { + /* This information is all static for X11, its up to the WM to + * decide how to interpret the wheel events. + */ + uint16_t button = MOUSE_NOBUTTON; + switch (data->event.u.u.detail) { + // FIXME This should use a lookup table to handle button remapping. + case Button1: + button = MOUSE_BUTTON1; + set_modifier_mask(MASK_BUTTON1); + break; + + case Button2: + button = MOUSE_BUTTON2; + set_modifier_mask(MASK_BUTTON2); + break; + + case Button3: + button = MOUSE_BUTTON3; + set_modifier_mask(MASK_BUTTON3); + break; + + case XButton1: + button = MOUSE_BUTTON4; + set_modifier_mask(MASK_BUTTON5); + break; + + case XButton2: + button = MOUSE_BUTTON5; + set_modifier_mask(MASK_BUTTON5); + break; + + default: + // Do not set modifier masks past button MASK_BUTTON5. + break; + } + + + // Track the number of clicks, the button must match the previous button. + if (button == hook->input.mouse.click.button && (long int) (timestamp - hook->input.mouse.click.time) <= hook_get_multi_click_time()) { + if (hook->input.mouse.click.count < USHRT_MAX) { + hook->input.mouse.click.count++; + } + else { + logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n", + __FUNCTION__, __LINE__); + } + } + else { + // Reset the click count. + hook->input.mouse.click.count = 1; + + // Set the previous button. + hook->input.mouse.click.button = button; + } + + // Save this events time to calculate the hook->input.mouse.click.count. + hook->input.mouse.click.time = timestamp; + + + // Populate mouse pressed event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_PRESSED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = hook->input.mouse.click.count; + event.data.mouse.x = data->event.u.keyButtonPointer.rootX; + event.data.mouse.y = data->event.u.keyButtonPointer.rootY; + + #if defined(USE_XINERAMA) || defined(USE_XRANDR) + uint8_t count; + screen_data *screens = hook_create_screen_info(&count); + if (count > 1) { + event.data.mouse.x -= screens[0].x; + event.data.mouse.y -= screens[0].y; + } + + if (screens != NULL) { + free(screens); + } + #endif + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u pressed %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse pressed event. + dispatch_event(&event); + } + } + else if (data->type == ButtonRelease) { + // X11 handles wheel events as button events. + if (data->event.u.u.detail != WheelUp && data->event.u.u.detail != WheelDown) { + /* This information is all static for X11, its up to the WM to + * decide how to interpret the wheel events. + */ + uint16_t button = MOUSE_NOBUTTON; + switch (data->event.u.u.detail) { + // FIXME This should use a lookup table to handle button remapping. + case Button1: + button = MOUSE_BUTTON1; + unset_modifier_mask(MASK_BUTTON1); + break; + + case Button2: + button = MOUSE_BUTTON2; + unset_modifier_mask(MASK_BUTTON2); + break; + + case Button3: + button = MOUSE_BUTTON3; + unset_modifier_mask(MASK_BUTTON3); + break; + + case XButton1: + button = MOUSE_BUTTON4; + unset_modifier_mask(MASK_BUTTON5); + break; + + case XButton2: + button = MOUSE_BUTTON5; + unset_modifier_mask(MASK_BUTTON5); + break; + + default: + // Do not set modifier masks past button MASK_BUTTON5. + break; + } + + // Populate mouse released event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_RELEASED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = hook->input.mouse.click.count; + event.data.mouse.x = data->event.u.keyButtonPointer.rootX; + event.data.mouse.y = data->event.u.keyButtonPointer.rootY; + + #if defined(USE_XINERAMA) || defined(USE_XRANDR) + uint8_t count; + screen_data *screens = hook_create_screen_info(&count); + if (count > 1) { + event.data.mouse.x -= screens[0].x; + event.data.mouse.y -= screens[0].y; + } + + if (screens != NULL) { + free(screens); + } + #endif + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u released %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, + event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse released event. + dispatch_event(&event); + + // If the pressed event was not consumed... + if (event.reserved ^ 0x01 && hook->input.mouse.is_dragged != true) { + // Populate mouse clicked event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_CLICKED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = hook->input.mouse.click.count; + event.data.mouse.x = data->event.u.keyButtonPointer.rootX; + event.data.mouse.y = data->event.u.keyButtonPointer.rootY; + + #if defined(USE_XINERAMA) || defined(USE_XRANDR) + uint8_t count; + screen_data *screens = hook_create_screen_info(&count); + if (count > 1) { + event.data.mouse.x -= screens[0].x; + event.data.mouse.y -= screens[0].y; + } + + if (screens != NULL) { + free(screens); + } + #endif + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u clicked %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, + event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse clicked event. + dispatch_event(&event); + } + + // Reset the number of clicks. + if (button == hook->input.mouse.click.button && (long int) (event.time - hook->input.mouse.click.time) > hook_get_multi_click_time()) { + // Reset the click count. + hook->input.mouse.click.count = 0; + } + } + } + else if (data->type == MotionNotify) { + // Reset the click count. + if (hook->input.mouse.click.count != 0 && (long int) (timestamp - hook->input.mouse.click.time) > hook_get_multi_click_time()) { + hook->input.mouse.click.count = 0; + } + + // Populate mouse move event. + event.time = timestamp; + event.reserved = 0x00; + + event.mask = get_modifiers(); + + // Check the upper half of virtual modifiers for non-zero + // values and set the mouse dragged flag. + hook->input.mouse.is_dragged = (event.mask >> 8 > 0); + if (hook->input.mouse.is_dragged) { + // Create Mouse Dragged event. + event.type = EVENT_MOUSE_DRAGGED; + } + else { + // Create a Mouse Moved event. + event.type = EVENT_MOUSE_MOVED; + } + + event.data.mouse.button = MOUSE_NOBUTTON; + event.data.mouse.clicks = hook->input.mouse.click.count; + event.data.mouse.x = data->event.u.keyButtonPointer.rootX; + event.data.mouse.y = data->event.u.keyButtonPointer.rootY; + + #if defined(USE_XINERAMA) || defined(USE_XRANDR) + uint8_t count; + screen_data *screens = hook_create_screen_info(&count); + if (count > 1) { + event.data.mouse.x -= screens[0].x; + event.data.mouse.y -= screens[0].y; + } + + if (screens != NULL) { + free(screens); + } + #endif + + logger(LOG_LEVEL_INFO, "%s [%u]: Mouse %s to %i, %i. (%#X)\n", + __FUNCTION__, __LINE__, hook->input.mouse.is_dragged ? "dragged" : "moved", + event.data.mouse.x, event.data.mouse.y, event.mask); + + // Fire mouse move event. + dispatch_event(&event); + } + else { + // In theory this *should* never execute. + logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled X11 event: %#X.\n", + __FUNCTION__, __LINE__, (unsigned int) data->type); + } + } + else { + logger(LOG_LEVEL_WARN, "%s [%u]: Unhandled X11 hook category! (%#X)\n", + __FUNCTION__, __LINE__, recorded_data->category); + } + + // TODO There is no way to consume the XRecord event. + + XRecordFreeData(recorded_data); +} + + +static inline bool enable_key_repeate() { + // Attempt to setup detectable autorepeat. + // NOTE: is_auto_repeat is NOT stdbool! + Bool is_auto_repeat = False; + #ifdef USE_XKB + // Enable detectable auto-repeat. + XkbSetDetectableAutoRepeat(hook->ctrl.display, True, &is_auto_repeat); + #else + XAutoRepeatOn(hook->ctrl.display); + + XKeyboardState kb_state; + XGetKeyboardControl(hook->ctrl.display, &kb_state); + + is_auto_repeat = (kb_state.global_auto_repeat == AutoRepeatModeOn); + #endif + + return is_auto_repeat; +} + + +static inline int xrecord_block() { + int status = IOHOOK_FAILURE; + + // Save the data display associated with this hook so it is passed to each event. + //XPointer closeure = (XPointer) (ctrl_display); + XPointer closeure = NULL; + + #ifdef USE_XRECORD_ASYNC + // Async requires that we loop so that our thread does not return. + if (XRecordEnableContextAsync(hook->data.display, context, hook_event_proc, closeure) != 0) { + // Time in MS to sleep the runloop. + int timesleep = 100; + + // Allow the thread loop to block. + pthread_mutex_lock(&hook_xrecord_mutex); + running = true; + + do { + // Unlock the mutex from the previous iteration. + pthread_mutex_unlock(&hook_xrecord_mutex); + + XRecordProcessReplies(hook->data.display); + + // Prevent 100% CPU utilization. + struct timeval tv; + gettimeofday(&tv, NULL); + + struct timespec ts; + ts.tv_sec = time(NULL) + timesleep / 1000; + ts.tv_nsec = tv.tv_usec * 1000 + 1000 * 1000 * (timesleep % 1000); + ts.tv_sec += ts.tv_nsec / (1000 * 1000 * 1000); + ts.tv_nsec %= (1000 * 1000 * 1000); + + pthread_mutex_lock(&hook_xrecord_mutex); + pthread_cond_timedwait(&hook_xrecord_cond, &hook_xrecord_mutex, &ts); + } while (running); + + // Unlock after loop exit. + pthread_mutex_unlock(&hook_xrecord_mutex); + + // Set the exit status. + status = NULL; + } + #else + // Sync blocks until XRecordDisableContext() is called. + if (XRecordEnableContext(hook->data.display, hook->ctrl.context, hook_event_proc, closeure) != 0) { + status = IOHOOK_SUCCESS; + } + #endif + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordEnableContext failure!\n", + __FUNCTION__, __LINE__); + + #ifdef USE_XRECORD_ASYNC + // Reset the running state. + pthread_mutex_lock(&hook_xrecord_mutex); + running = false; + pthread_mutex_unlock(&hook_xrecord_mutex); + #endif + + // Set the exit status. + status = IOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT; + } + + return status; +} + +static int xrecord_alloc() { + int status = IOHOOK_FAILURE; + + // Make sure the data display is synchronized to prevent late event delivery! + // See Bug 42356 for more information. + // https://bugs.freedesktop.org/show_bug.cgi?id=42356#c4 + XSynchronize(hook->data.display, True); + + // Setup XRecord range. + XRecordClientSpec clients = XRecordAllClients; + + hook->data.range = XRecordAllocRange(); + if (hook->data.range != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: XRecordAllocRange successful.\n", + __FUNCTION__, __LINE__); + + hook->data.range->device_events.first = KeyPress; + hook->data.range->device_events.last = MotionNotify; + + // Note that the documentation for this function is incorrect, + // hook->data.display should be used! + // See: http://www.x.org/releases/X11R7.6/doc/libXtst/recordlib.txt + hook->ctrl.context = XRecordCreateContext(hook->data.display, XRecordFromServerTime, &clients, 1, &hook->data.range, 1); + if (hook->ctrl.context != 0) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: XRecordCreateContext successful.\n", + __FUNCTION__, __LINE__); + + // Block until hook_stop() is called. + status = xrecord_block(); + + // Free up the context if it was set. + XRecordFreeContext(hook->data.display, hook->ctrl.context); + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordCreateContext failure!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_X_RECORD_CREATE_CONTEXT; + } + + // Free the XRecord range. + XFree(hook->data.range); + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordAllocRange failure!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_X_RECORD_ALLOC_RANGE; + } + + return status; +} + +static int xrecord_query() { + int status = IOHOOK_FAILURE; + + // Check to make sure XRecord is installed and enabled. + int major, minor; + if (XRecordQueryVersion(hook->ctrl.display, &major, &minor) != 0) { + logger(LOG_LEVEL_INFO, "%s [%u]: XRecord version: %i.%i.\n", + __FUNCTION__, __LINE__, major, minor); + + status = xrecord_alloc(); + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: XRecord is not currently available!\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_ERROR_X_RECORD_NOT_FOUND; + } + + return status; +} + +static int xrecord_start() { + int status = IOHOOK_FAILURE; + + // Open the control display for XRecord. + hook->ctrl.display = XOpenDisplay(NULL); + + // Open a data display for XRecord. + // NOTE This display must be opened on the same thread as XRecord. + hook->data.display = XOpenDisplay(NULL); + if (hook->ctrl.display != NULL && hook->data.display != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: XOpenDisplay successful.\n", + __FUNCTION__, __LINE__); + + bool is_auto_repeat = enable_key_repeate(); + if (is_auto_repeat) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Successfully enabled detectable autorepeat.\n", + __FUNCTION__, __LINE__); + } + else { + logger(LOG_LEVEL_WARN, "%s [%u]: Could not enable detectable auto-repeat!\n", + __FUNCTION__, __LINE__); + } + + #if defined(USE_XKBCOMMON) + // Open XCB Connection + hook->input.connection = XGetXCBConnection(hook->ctrl.display); + int xcb_status = xcb_connection_has_error(hook->input.connection); + if (xcb_status <= 0) { + // Initialize xkbcommon context. + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + if (context != NULL) { + hook->input.context = xkb_context_ref(context); + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: xkb_context_new failure!\n", + __FUNCTION__, __LINE__); + } + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: xcb_connect failure! (%d)\n", + __FUNCTION__, __LINE__, xcb_status); + } + #endif + + #ifdef USE_XKBCOMMON + state = create_xkb_state(hook->input.context, hook->input.connection); + #endif + + // Initialize starting modifiers. + initialize_modifiers(); + + status = xrecord_query(); + + #ifdef USE_XKBCOMMON + if (state != NULL) { + destroy_xkb_state(state); + } + + if (hook->input.context != NULL) { + xkb_context_unref(hook->input.context); + hook->input.context = NULL; + } + + if (hook->input.connection != NULL) { + xcb_disconnect(hook->input.connection); + hook->input.connection = NULL; + } + #endif + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: XOpenDisplay failure!\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_ERROR_X_OPEN_DISPLAY; + } + + // Close down the XRecord data display. + if (hook->data.display != NULL) { + XCloseDisplay(hook->data.display); + hook->data.display = NULL; + } + + // Close down the XRecord control display. + if (hook->ctrl.display) { + XCloseDisplay(hook->ctrl.display); + hook->ctrl.display = NULL; + } + + return status; +} + +IOHOOK_API int hook_run() { + int status = IOHOOK_FAILURE; + + // Hook data for future cleanup. + hook = malloc(sizeof(hook_info)); + if (hook != NULL) { + hook->input.mask = 0x0000; + hook->input.mouse.is_dragged = false; + hook->input.mouse.click.count = 0; + hook->input.mouse.click.time = 0; + hook->input.mouse.click.button = MOUSE_NOBUTTON; + + status = xrecord_start(); + + // Free data associated with this hook. + free(hook); + hook = NULL; + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for hook structure!\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_ERROR_OUT_OF_MEMORY; + } + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Something, something, something, complete.\n", + __FUNCTION__, __LINE__); + + return status; +} + +IOHOOK_API int hook_stop() { + int status = IOHOOK_FAILURE; + + if (hook != NULL && hook->ctrl.display != NULL && hook->ctrl.context != 0) { + // We need to make sure the context is still valid. + XRecordState *state = malloc(sizeof(XRecordState)); + if (state != NULL) { + if (XRecordGetContext(hook->ctrl.display, hook->ctrl.context, &state) != 0) { + // Try to exit the thread naturally. + if (state->enabled && XRecordDisableContext(hook->ctrl.display, hook->ctrl.context) != 0) { + #ifdef USE_XRECORD_ASYNC + pthread_mutex_lock(&hook_xrecord_mutex); + running = false; + pthread_cond_signal(&hook_xrecord_cond); + pthread_mutex_unlock(&hook_xrecord_mutex); + #endif + + // See Bug 42356 for more information. + // https://bugs.freedesktop.org/show_bug.cgi?id=42356#c4 + //XFlush(hook->ctrl.display); + XSync(hook->ctrl.display, False); + if (hook->ctrl.display) { + XCloseDisplay(hook->ctrl.display); + hook->ctrl.display = NULL; + } + + status = IOHOOK_SUCCESS; + } + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordGetContext failure!\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_ERROR_X_RECORD_GET_CONTEXT; + } + + free(state); + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for XRecordState!\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_ERROR_OUT_OF_MEMORY; + } + + return status; + } + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Status: %#X.\n", + __FUNCTION__, __LINE__, status); + + return status; +} diff --git a/event/hook/x11/input.h b/event/hook/x11/input.h new file mode 100644 index 0000000..905983f --- /dev/null +++ b/event/hook/x11/input.h @@ -0,0 +1,86 @@ + +#define USE_XKBCOMMON 0 +//#define _included_input_helper 0 +#ifndef _included_input_helper +#define _included_input_helper + +#include +#include + +#ifdef USE_XKBCOMMON + #include + #include + #include +#endif + + +// Virtual button codes that are not defined by X11. +#define Button1 1 +#define Button2 2 +#define Button3 3 +#define WheelUp 4 +#define WheelDown 5 +#define WheelLeft 6 +#define WheelRight 7 +#define XButton1 8 +#define XButton2 9 + +/* Converts an X11 key symbol to a single Unicode character. No direct X11 + * functionality exists to provide this information. + */ +extern size_t keysym_to_unicode(KeySym keysym, uint16_t *buffer, size_t size); + +/* Convert a single Unicode character to an X11 key symbol. This function + * provides a better translation than XStringToKeysym() for Unicode characters. + */ +extern KeySym unicode_to_keysym(uint16_t unicode); + +/* Converts an X11 key code to the appropriate keyboard scan code. + */ +extern uint16_t keycode_to_scancode(KeyCode keycode); + +/* Converts a keyboard scan code to the appropriate X11 key code. + */ +extern KeyCode scancode_to_keycode(uint16_t scancode); + + +#ifdef USE_XKBCOMMON + +/* Converts an X11 key code to a Unicode character sequence. libXKBCommon support + * is required for this method. + */ +extern size_t keycode_to_unicode(struct xkb_state* state, KeyCode keycode, uint16_t *buffer, size_t size); + +/* Create a xkb_state structure and return a pointer to it. + */ +extern struct xkb_state * create_xkb_state(struct xkb_context *context, xcb_connection_t *connection); + +/* Release xkb_state structure created by create_xkb_state(). + */ +extern void destroy_xkb_state(struct xkb_state* state); + +#else + +/* Converts an X11 key code and event mask to the appropriate X11 key symbol. + * This functions in much the same way as XKeycodeToKeysym() but allows for a + * faster and more flexible lookup. + */ +extern KeySym keycode_to_keysym(KeyCode keycode, unsigned int modifier_mask); + +#endif + +/* Initialize items required for KeyCodeToKeySym() and KeySymToUnicode() + * functionality. This method is called by OnLibraryLoad() and may need to be + * called in combination with UnloadInputHelper() if the native keyboard layout + * is changed. + */ +extern void load_input_helper(); + +/* De-initialize items required for KeyCodeToKeySym() and KeySymToUnicode() + * functionality. This method is called by OnLibraryUnload() and may need to be + * called in combination with LoadInputHelper() if the native keyboard layout + * is changed. + */ +extern void unload_input_helper(); + +#endif diff --git a/event/hook/x11/input_c.h b/event/hook/x11/input_c.h new file mode 100644 index 0000000..8d7f39d --- /dev/null +++ b/event/hook/x11/input_c.h @@ -0,0 +1,1970 @@ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define USE_XKB 0 +#define USE_XKBCOMMON 0 +#include +#include +#include +#include +#include +#include + +#ifdef USE_XKB +#ifdef USE_EVDEV + #include + static bool is_evdev = false; +#endif + +#include +static XkbDescPtr keyboard_map; +#else + #include + static KeySym *keyboard_map; + static int keysym_per_keycode; + static bool is_caps_lock = false, is_shift_lock = false; +#endif + +#ifdef USE_XKBCOMMON +#include +#include +#include + +#ifdef USE_XKBFILE +#include + +static struct xkb_rule_names xkb_names = { + .rules = "base", + .model = "us", + .layout = "pc105", + .variant = NULL, + .options = NULL +}; +#endif + +#endif + +#include "../logger_c.h" + +/* The follwoing two tables are based on QEMU's x_keymap.c, under the following + * terms: + * + * Copyright (C) 2003 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#if defined(USE_EVDEV) && defined(USE_XKB) +/* This table is generated based off the evdev -> scancode mapping above + * and the keycode mappings in the following files: + * /usr/include/linux/input.h + * /usr/share/X11/xkb/keycodes/evdev + * + * NOTE This table only works for Linux. + */ +static const uint16_t evdev_scancode_table[][2] = { + /* idx { keycode, scancode }, idx evdev code */ + /* 0 */ { VC_UNDEFINED, 0x00 }, /* 0x00 KEY_RESERVED */ + /* 1 */ { VC_UNDEFINED, 0x09 }, /* 0x01 KEY_ESC */ + /* 2 */ { VC_UNDEFINED, 0x0A }, /* 0x02 KEY_1 */ + /* 3 */ { VC_UNDEFINED, 0x0B }, /* 0x03 KEY_2 */ + /* 4 */ { VC_UNDEFINED, 0x0C }, /* 0x04 KEY_3 */ + /* 5 */ { VC_UNDEFINED, 0x0D }, /* 0x05 KEY_4 */ + /* 6 */ { VC_UNDEFINED, 0x0E }, /* 0x06 KEY_5 */ + /* 7 */ { VC_UNDEFINED, 0x0F }, /* 0x07 KEY_6 */ + /* 8 */ { VC_UNDEFINED, 0x10 }, /* 0x08 KEY_7 */ + /* 9 */ { VC_ESCAPE, 0x11 }, /* 0x09 KEY_8 */ + /* 10 */ { VC_1, 0x12 }, /* 0x0A KEY_9 */ + /* 11 */ { VC_2, 0x13 }, /* 0x0B KEY_0 */ + /* 12 */ { VC_3, 0x14 }, /* 0x0C KEY_MINUS */ + /* 13 */ { VC_4, 0x15 }, /* 0x0D KEY_EQUAL */ + /* 14 */ { VC_5, 0x16 }, /* 0x0E KEY_BACKSPACE */ + /* 15 */ { VC_6, 0x17 }, /* 0x0F KEY_TAB */ + /* 16 */ { VC_7, 0x18 }, /* 0x10 KEY_Q */ + /* 17 */ { VC_8, 0x19 }, /* 0x11 KEY_W */ + /* 18 */ { VC_9, 0x1A }, /* 0x12 KEY_E */ + /* 19 */ { VC_0, 0x1B }, /* 0x13 KEY_T */ + /* 20 */ { VC_MINUS, 0x1C }, /* 0x14 KEY_R */ + /* 21 */ { VC_EQUALS, 0x1D }, /* 0x15 KEY_Y */ + /* 22 */ { VC_BACKSPACE, 0x1E }, /* 0x16 KEY_U */ + /* 23 */ { VC_TAB, 0x1F }, /* 0x17 KEY_I */ + /* 24 */ { VC_Q, 0x20 }, /* 0x18 KEY_O */ + /* 25 */ { VC_W, 0x21 }, /* 0x19 KEY_P */ + /* 26 */ { VC_E, 0x22 }, /* 0x1A KEY_LEFTBRACE */ + /* 27 */ { VC_R, 0x23 }, /* 0x1B KEY_RIGHTBRACE */ + /* 28 */ { VC_T, 0x24 }, /* 0x1C KEY_ENTER */ + /* 29 */ { VC_Y, 0x25 }, /* 0x1D KEY_LEFTCTRL */ + /* 30 */ { VC_U, 0x26 }, /* 0x1E KEY_A */ + /* 31 */ { VC_I, 0x27 }, /* 0x1F KEY_S */ + /* 32 */ { VC_O, 0x28 }, /* 0x20 KEY_D */ + /* 33 */ { VC_P, 0x29 }, /* 0x21 KEY_F */ + /* 34 */ { VC_OPEN_BRACKET, 0x2A }, /* 0x22 KEY_G */ + /* 35 */ { VC_CLOSE_BRACKET, 0x2B }, /* 0x23 KEY_H */ + /* 36 */ { VC_ENTER, 0x2C }, /* 0x24 KEY_J */ + /* 37 */ { VC_CONTROL_L, 0x2D }, /* 0x25 KEY_K */ + /* 38 */ { VC_A, 0x2E }, /* 0x26 KEY_L */ + /* 39 */ { VC_S, 0x2F }, /* 0x27 KEY_SEMICOLON */ + /* 40 */ { VC_D, 0x30 }, /* 0x28 KEY_APOSTROPHE */ + /* 41 */ { VC_F, 0x31 }, /* 0x29 KEY_GRAVE */ + /* 42 */ { VC_G, 0x32 }, /* 0x2A KEY_LEFTSHIFT */ + /* 43 */ { VC_H, 0x33 }, /* 0x2B KEY_BACKSLASH */ + /* 44 */ { VC_J, 0x34 }, /* 0x2C KEY_Z */ + /* 45 */ { VC_K, 0x35 }, /* 0x2D KEY_X */ + /* 46 */ { VC_L, 0x36 }, /* 0x2E KEY_C */ + /* 47 */ { VC_SEMICOLON, 0x37 }, /* 0x2F KEY_V */ + /* 48 */ { VC_QUOTE, 0x38 }, /* 0x30 KEY_B */ + /* 49 */ { VC_BACKQUOTE, 0x39 }, /* 0x31 KEY_N */ + /* 50 */ { VC_SHIFT_L, 0x3A }, /* 0x32 KEY_M */ + /* 51 */ { VC_BACK_SLASH, 0x3B }, /* 0x33 KEY_COMMA */ + /* 52 */ { VC_Z, 0x3C }, /* 0x34 KEY_DOT */ + /* 53 */ { VC_X, 0x3D }, /* 0x35 KEY_SLASH */ + /* 54 */ { VC_C, 0x3E }, /* 0x36 KEY_RIGHTSHIFT */ + /* 55 */ { VC_V, 0x3F }, /* 0x37 KEY_KPASTERISK */ + /* 56 */ { VC_B, 0x40 }, /* 0x38 KEY_LEFTALT */ + /* 57 */ { VC_N, 0x41 }, /* 0x39 KEY_SPACE */ + /* 58 */ { VC_M, 0x42 }, /* 0x3A KEY_CAPSLOCK */ + /* 59 */ { VC_COMMA, 0x43 }, /* 0x3B KEY_F1 */ + /* 60 */ { VC_PERIOD, 0x44 }, /* 0x3C KEY_F2 */ + /* 61 */ { VC_SLASH, 0x45 }, /* 0x3D KEY_F3 */ + /* 62 */ { VC_SHIFT_R, 0x46 }, /* 0x3E KEY_F4 */ + /* 63 */ { VC_KP_MULTIPLY, 0x47 }, /* 0x3F KEY_F5 */ + /* 64 */ { VC_ALT_L, 0x48 }, /* 0x40 KEY_F6 */ + /* 65 */ { VC_SPACE, 0x49 }, /* 0x41 KEY_F7 */ + /* 66 */ { VC_CAPS_LOCK, 0x4A }, /* 0x42 KEY_F8 */ + /* 67 */ { VC_F1, 0x4B }, /* 0x43 KEY_F9 */ + /* 68 */ { VC_F2, 0x4C }, /* 0x44 KEY_F10 */ + /* 69 */ { VC_F3, 0x4D }, /* 0x45 KEY_NUMLOCK */ + /* 70 */ { VC_F4, 0x4E }, /* 0x46 KEY_SCROLLLOCK */ + /* 71 */ { VC_F5, 0x4F }, /* 0x47 KEY_KP7 */ + /* 72 */ { VC_F6, 0x50 }, /* 0x48 KEY_KP8 */ + /* 73 */ { VC_F7, 0x51 }, /* 0x49 KEY_KP9 */ + /* 74 */ { VC_F8, 0x52 }, /* 0x4A KEY_KPMINUS */ + /* 75 */ { VC_F9, 0x53 }, /* 0x4B KEY_KP4 */ + /* 76 */ { VC_F10, 0x54 }, /* 0x4C KEY_KP5 */ + /* 77 */ { VC_NUM_LOCK, 0x55 }, /* 0x4D KEY_KP6 */ + /* 78 */ { VC_SCROLL_LOCK, 0x56 }, /* 0x4E KEY_KPPLUS */ + /* 79 */ { VC_KP_7, 0x57 }, /* 0x4F KEY_KP1 */ + /* 80 */ { VC_KP_8, 0x58 }, /* 0x50 KEY_KP2 */ + /* 81 */ { VC_KP_9, 0x59 }, /* 0x51 KEY_KP3 */ + /* 82 */ { VC_KP_SUBTRACT, 0x5A }, /* 0x52 KEY_KP0 */ + /* 83 */ { VC_KP_4, 0x5B }, /* 0x53 KEY_KPDOT */ + /* 84 */ { VC_KP_5, 0x00 }, /* 0x54 */ + /* 85 */ { VC_KP_6, 0x00 }, /* 0x55 TODO [KEY_ZENKAKUHANKAKU][0] == [VC_?][1] */ + /* 86 */ { VC_KP_ADD, 0x00 }, /* 0x56 TODO [KEY_102ND][0] == [VC_?][1] */ + /* 87 */ { VC_KP_1, 0x5F }, /* 0x57 KEY_F11 */ + /* 88 */ { VC_KP_2, 0x60 }, /* 0x58 KEY_F12 */ + /* 89 */ { VC_KP_3, 0x00 }, /* 0x59 TODO [KEY_RO][0] == [VC_?][1] */ + /* 90 */ { VC_KP_0, 0x00 }, /* 0x5A */ + /* 91 */ { VC_KP_SEPARATOR, 0xBF }, /* 0x5B KEY_F13 */ + /* 92 */ { VC_UNDEFINED, 0xC0 }, /* 0x5C KEY_F14 */ + /* 93 */ { VC_UNDEFINED, 0xC1 }, /* 0x5D KEY_F15 */ + /* 94 */ { VC_UNDEFINED, 0x00 }, /* 0x5E TODO [KEY_MUHENKAN][0] == [VC_?][1] */ + /* 95 */ { VC_F11, 0x00 }, /* 0x5F */ + /* 96 */ { VC_F12, 0x00 }, /* 0x60 */ + + /* First 97 chars are identical to XFree86! */ + + /* 97 */ { VC_UNDEFINED, 0x00 }, /* 0x61 */ + /* 98 */ { VC_KATAKANA, 0x00 }, /* 0x62 */ + /* 99 */ { VC_HIRAGANA, 0xC2 }, /* 0x63 KEY_F16 */ + /* 100 */ { VC_KANJI, 0xC3 }, /* 0x64 KEY_F17 */ + /* 101 */ { VC_UNDEFINED, 0xC4 }, /* 0x65 KEY_F18 */ + /* 102 */ { VC_UNDEFINED, 0xC5 }, /* 0x66 KEY_F19 */ + /* 103 */ { VC_KP_COMMA, 0xC6 }, /* 0x67 KEY_F20 */ + /* 104 */ { VC_KP_ENTER, 0xC7 }, /* 0x68 KEY_F21 */ + /* 105 */ { VC_CONTROL_R, 0xC8 }, /* 0x69 KEY_F22 */ + /* 106 */ { VC_KP_DIVIDE, 0xC9 }, /* 0x6A KEY_F23 */ + /* 107 */ { VC_PRINTSCREEN, 0xCA }, /* 0x6B KEY_F24 */ + /* 108 */ { VC_ALT_R, 0x00 }, /* 0x6C */ + /* 109 */ { VC_UNDEFINED, 0x00 }, /* 0x6D */ + /* 110 */ { VC_HOME, 0x00 }, /* 0x6E */ + /* 111 */ { VC_UP, 0x00 }, /* 0x6F */ + /* 112 */ { VC_PAGE_UP, 0x62 }, /* 0x70 KEY_KATAKANA */ + /* 113 */ { VC_LEFT, 0x00 }, /* 0x71 */ + /* 114 */ { VC_RIGHT, 0x00 }, /* 0x72 */ + /* 115 */ { VC_END, 0x00 }, /* 0x73 TODO KEY_? = [VC_UNDERSCORE][1] */ + /* 116 */ { VC_DOWN, 0x00 }, /* 0x74 TODO KEY_? = [VC_FURIGANA][1] */ + /* 117 */ { VC_PAGE_DOWN, 0x00 }, /* 0x75 */ + /* 118 */ { VC_INSERT, 0x00 }, /* 0x76 TODO [KEY_KPPLUSMINUS][0] = [VC_?][1] */ + /* 119 */ { VC_DELETE, 0x00 }, /* 0x77 */ + /* 120 */ { VC_UNDEFINED, 0x00 }, /* 0x78 TODO [KEY_SCALE][0] = [VC_?][1] */ + /* 121 */ { VC_VOLUME_MUTE, 0x64 }, /* 0x79 KEY_HENKAN */ + /* 122 */ { VC_VOLUME_DOWN, 0x00 }, /* 0x7A */ + /* 123 */ { VC_VOLUME_UP, 0x63 }, /* 0x7B KEY_HIRAGANA */ + /* 124 */ { VC_POWER, 0x00 }, /* 0x7C */ + /* 125 */ { VC_KP_EQUALS, 0x84 }, /* 0x7D KEY_YEN */ + /* 126 */ { VC_UNDEFINED, 0x67 }, /* 0x7E KEY_KPJPCOMMA */ + /* 127 */ { VC_PAUSE, 0x00 }, /* 0x7F */ + + /* No Offset Offset (i & 0x007F) + 128 */ + + /* 128 */ { VC_UNDEFINED, 0 }, /* 0x80 */ + /* 129 */ { VC_UNDEFINED, 0 }, /* 0x81 */ + /* 130 */ { VC_UNDEFINED, 0 }, /* 0x82 */ + /* 131 */ { VC_UNDEFINED, 0 }, /* 0x83 */ + /* 132 */ { VC_YEN, 0 }, /* 0x84 */ + /* 133 */ { VC_META_L, 0 }, /* 0x85 */ + /* 134 */ { VC_META_R, 0 }, /* 0x86 */ + /* 135 */ { VC_CONTEXT_MENU, 0 }, /* 0x87 */ + /* 136 */ { VC_SUN_STOP, 0 }, /* 0x88 */ + /* 137 */ { VC_SUN_AGAIN, 0 }, /* 0x89 */ + /* 138 */ { VC_SUN_PROPS, 0 }, /* 0x8A */ + /* 139 */ { VC_SUN_UNDO, 0 }, /* 0x8B */ + /* 140 */ { VC_SUN_FRONT, 0 }, /* 0x8C */ + /* 141 */ { VC_SUN_COPY, 0x7D }, /* 0x8D KEY_KPEQUAL */ + /* 142 */ { VC_SUN_OPEN, 0 }, /* 0x8E */ + /* 143 */ { VC_SUN_INSERT, 0 }, /* 0x8F */ + /* 144 */ { VC_SUN_FIND, 0 }, /* 0x90 */ + /* 145 */ { VC_SUN_CUT, 0 }, /* 0x91 */ + /* 146 */ { VC_SUN_HELP, 0 }, /* 0x92 */ + /* 147 */ { VC_UNDEFINED, 0 }, /* 0x93 */ + /* 148 */ { VC_APP_CALCULATOR, 0 }, /* 0x94 */ + /* 149 */ { VC_UNDEFINED, 0 }, /* 0x95 */ + /* 150 */ { VC_SLEEP, 0 }, /* 0x96 */ + /* 151 */ { VC_UNDEFINED, 0 }, /* 0x97 */ + /* 152 */ { VC_UNDEFINED, 0 }, /* 0x98 */ + /* 153 */ { VC_UNDEFINED, 0 }, /* 0x99 */ + /* 154 */ { VC_UNDEFINED, 0 }, /* 0x9A */ + /* 155 */ { VC_UNDEFINED, 0 }, /* 0x9B */ + /* 156 */ { VC_UNDEFINED, 0x68 }, /* 0x9C KEY_KPENTER */ + /* 157 */ { VC_UNDEFINED, 0x69 }, /* 0x9D KEY_RIGHTCTRL */ + /* 158 */ { VC_UNDEFINED, 0 }, /* 0x9E */ + /* 159 */ { VC_UNDEFINED, 0 }, /* 0x9F */ + /* 160 */ { VC_UNDEFINED, 0x79 }, /* 0xA0 KEY_MUTE */ + /* 161 */ { VC_UNDEFINED, 0x94 }, /* 0xA1 KEY_CALC */ + /* 162 */ { VC_UNDEFINED, 0xA7 }, /* 0xA2 KEY_FORWARD */ + /* 163 */ { VC_UNDEFINED, 0 }, /* 0xA3 */ + /* 164 */ { VC_UNDEFINED, 0 }, /* 0xA4 */ + /* 165 */ { VC_UNDEFINED, 0 }, /* 0xA5 */ + /* 166 */ { VC_APP_MAIL, 0 }, /* 0xA6 */ + /* 167 */ { VC_MEDIA_PLAY, 0 }, /* 0xA7 */ + /* 168 */ { VC_UNDEFINED, 0 }, /* 0xA8 */ + /* 169 */ { VC_UNDEFINED, 0 }, /* 0xA9 */ + /* 170 */ { VC_UNDEFINED, 0 }, /* 0xAA */ + /* 171 */ { VC_UNDEFINED, 0 }, /* 0xAB */ + /* 172 */ { VC_UNDEFINED, 0 }, /* 0xAC */ + /* 173 */ { VC_UNDEFINED, 0 }, /* 0xAD */ + /* 174 */ { VC_UNDEFINED, 0x7A }, /* 0xAE KEY_VOLUMEDOWN */ + /* 175 */ { VC_UNDEFINED, 0 }, /* 0xAF */ + /* 176 */ { VC_UNDEFINED, 0x7B }, /* 0xB0 KEY_VOLUMEUP */ + /* 177 */ { VC_UNDEFINED, 0x00 }, /* 0xB1 */ + /* 178 */ { VC_UNDEFINED, 0xBA }, /* 0xB2 KEY_SCROLLUP */ + /* 179 */ { VC_UNDEFINED, 0x00 }, /* 0xB3 */ + /* 180 */ { VC_UNDEFINED, 0x00 }, /* 0xB4 */ + /* 181 */ { VC_UNDEFINED, 0x6A }, /* 0xB5 KEY_KPSLASH */ + /* 182 */ { VC_UNDEFINED, 0x00 }, /* 0xB6 */ + /* 183 */ { VC_UNDEFINED, 0x6B }, /* 0xB7 KEY_SYSRQ */ + /* 184 */ { VC_UNDEFINED, 0x6C }, /* 0xB8 KEY_RIGHTALT */ + /* 185 */ { VC_UNDEFINED, 0x00 }, /* 0xB9 */ + /* 186 */ { VC_BROWSER_HOME, 0x00 }, /* 0xBA */ + /* 187 */ { VC_UNDEFINED, 0x00 }, /* 0xBB */ + /* 188 */ { VC_UNDEFINED, 0x00 }, /* 0xBC */ + /* 189 */ { VC_UNDEFINED, 0x00 }, /* 0xBD */ + /* 190 */ { VC_UNDEFINED, 0x00 }, /* 0xBE */ + /* 191 */ { VC_F13, 0x00 }, /* 0xBF */ + /* 192 */ { VC_F14, 0x00 }, /* 0xC0 */ + /* 193 */ { VC_F15, 0x00 }, /* 0xC1 */ + /* 194 */ { VC_F16, 0x00 }, /* 0xC2 */ + /* 195 */ { VC_F17, 0x00 }, /* 0xC3 */ + /* 196 */ { VC_F18, 0x00 }, /* 0xC4 */ + /* 197 */ { VC_F19, 0x7F }, /* 0xC5 KEY_PAUSE */ + /* 198 */ { VC_F20, 0x00 }, /* 0xC6 */ + /* 199 */ { VC_F21, 0x6E }, /* 0xC7 KEY_HOME */ + /* 200 */ { VC_F22, 0x6F }, /* 0xC8 KEY_UP */ + /* 201 */ { VC_F23, 0x70 }, /* 0xC9 KEY_PAGEUP */ + /* 202 */ { VC_F24, 0x00 }, /* 0xCA */ + /* 203 */ { VC_UNDEFINED, 0x71 }, /* 0xCB KEY_LEFT */ + /* 204 */ { VC_UNDEFINED, 0x00 }, /* 0xCC */ + /* 205 */ { VC_UNDEFINED, 0x72 }, /* 0xCD KEY_RIGHT */ + /* 206 */ { VC_UNDEFINED, 0x00 }, /* 0xCE */ + /* 207 */ { VC_UNDEFINED, 0x73 }, /* 0xCF KEY_END */ + /* 208 */ { VC_UNDEFINED, 0x74 }, /* 0xD0 KEY_DOWN */ + /* 209 */ { VC_UNDEFINED, 0x75 }, /* 0xD1 KEY_PAGEDOWN */ + /* 210 */ { VC_UNDEFINED, 0x76 }, /* 0xD2 KEY_INSERT */ + /* 211 */ { VC_UNDEFINED, 0x77 }, /* 0xD3 KEY_DELETE */ + /* 212 */ { VC_UNDEFINED, 0x00 }, /* 0xD4 */ + /* 213 */ { VC_UNDEFINED, 0x00 }, /* 0xD5 */ + /* 214 */ { VC_UNDEFINED, 0x00 }, /* 0xD6 */ + /* 215 */ { VC_UNDEFINED, 0x00 }, /* 0xD7 */ + /* 216 */ { VC_UNDEFINED, 0x00 }, /* 0xD8 */ + /* 217 */ { VC_UNDEFINED, 0x00 }, /* 0xD9 */ + /* 218 */ { VC_UNDEFINED, 0x00 }, /* 0xDA */ + /* 219 */ { VC_UNDEFINED, 0x85 }, /* 0xDB KEY_LEFTMETA */ + /* 220 */ { VC_UNDEFINED, 0x86 }, /* 0xDC KEY_RIGHTMETA */ + /* 221 */ { VC_UNDEFINED, 0x87 }, /* 0xDD KEY_COMPOSE */ + /* 222 */ { VC_UNDEFINED, 0x7C }, /* 0xDE KEY_POWER */ + /* 223 */ { VC_UNDEFINED, 0x96 }, /* 0xDF KEY_SLEEP */ + /* 224 */ { VC_UNDEFINED, 0x00 }, /* 0xE0 */ + /* 225 */ { VC_BROWSER_SEARCH, 0x00 }, /* 0xE1 */ + /* 226 */ { VC_UNDEFINED, 0x00 }, /* 0xE2 */ + /* 227 */ { VC_UNDEFINED, 0x00 }, /* 0xE3 */ + /* 228 */ { VC_UNDEFINED, 0x00 }, /* 0xE4 */ + /* 229 */ { VC_UNDEFINED, 0xE1 }, /* 0xE5 KEY_SEARCH */ + /* 230 */ { VC_UNDEFINED, 0x00 }, /* 0xE6 */ + /* 231 */ { VC_UNDEFINED, 0x00 }, /* 0xE7 */ + /* 232 */ { VC_UNDEFINED, 0x00 }, /* 0xE8 */ + /* 233 */ { VC_UNDEFINED, 0x00 }, /* 0xE9 */ + /* 234 */ { VC_UNDEFINED, 0x00 }, /* 0xEA */ + /* 235 */ { VC_UNDEFINED, 0x00 }, /* 0xEB */ + /* 236 */ { VC_UNDEFINED, 0xA6 }, /* 0xEC KEY_BACK */ + /* 237 */ { VC_UNDEFINED, 0x00 }, /* 0xED */ + /* 238 */ { VC_UNDEFINED, 0x00 }, /* 0xEE */ + /* 239 */ { VC_UNDEFINED, 0x00 }, /* 0xEF */ + /* 240 */ { VC_UNDEFINED, 0x00 }, /* 0xF0 */ + /* 241 */ { VC_UNDEFINED, 0x00 }, /* 0xF1 */ + /* 242 */ { VC_UNDEFINED, 0x00 }, /* 0xF2 */ + /* 243 */ { VC_UNDEFINED, 0x00 }, /* 0xF3 */ + /* 244 */ { VC_UNDEFINED, 0x8E }, /* 0xF4 KEY_OPEN */ + /* 245 */ { VC_UNDEFINED, 0x92 }, /* 0xF5 KEY_HELP */ + /* 246 */ { VC_UNDEFINED, 0x8A }, /* 0xF6 KEY_PROPS */ + /* 247 */ { VC_UNDEFINED, 0x8C }, /* 0xF7 KEY_FRONT */ + /* 248 */ { VC_UNDEFINED, 0x88 }, /* 0xF8 KEY_STOP */ + /* 249 */ { VC_UNDEFINED, 0x89 }, /* 0xF9 KEY_AGAIN */ + /* 250 */ { VC_UNDEFINED, 0x8B }, /* 0xFA KEY_UNDO */ + /* 251 */ { VC_UNDEFINED, 0x91 }, /* 0xFB KEY_CUT */ + /* 252 */ { VC_UNDEFINED, 0x8D }, /* 0xFC KEY_COPY */ + /* 253 */ { VC_UNDEFINED, 0x8F }, /* 0xFD KEY_PASTE */ + /* 254 */ { VC_UNDEFINED, 0x90 }, /* 0xFE KEY_FIND */ + /* 255 */ { VC_UNDEFINED, 0x00 }, /* 0xFF */ +}; +#endif + + +/* This table is generated based off the xfree86 -> scancode mapping above + * and the keycode mappings in the following files: + * /usr/share/X11/xkb/keycodes/xfree86 + * + * TODO Everything after 157 needs to be populated with scancodes for media + * controls and internet keyboards. + */ +static const uint16_t xfree86_scancode_table[][2] = { + /* idx { keycode, scancode }, */ + /* 0 */ { VC_UNDEFINED, 0 /* */ }, // Unused + /* 1 */ { VC_UNDEFINED, 9 /* */ }, // + /* 2 */ { VC_UNDEFINED, 10 /* */ }, // + /* 3 */ { VC_UNDEFINED, 11 /* */ }, // + /* 4 */ { VC_UNDEFINED, 12 /* */ }, // + /* 5 */ { VC_UNDEFINED, 13 /* */ }, // + /* 6 */ { VC_UNDEFINED, 14 /* */ }, // + /* 7 */ { VC_UNDEFINED, 15 /* */ }, // + /* 8 */ { VC_UNDEFINED, 16 /* */ }, // + /* 9 */ { VC_ESCAPE, 17 /* */ }, // + /* 10 */ { VC_1, 18 /* */ }, // + /* 11 */ { VC_2, 19 /* */ }, // + /* 12 */ { VC_3, 20 /* */ }, // + /* 13 */ { VC_4, 21 /* */ }, // + /* 14 */ { VC_5, 22 /* */ }, // + /* 15 */ { VC_6, 23 /* */ }, // + /* 16 */ { VC_7, 24 /* */ }, // + /* 17 */ { VC_8, 25 /* */ }, // + /* 18 */ { VC_9, 26 /* */ }, // + /* 19 */ { VC_0, 27 /* */ }, // + /* 20 */ { VC_MINUS, 28 /* */ }, // + /* 21 */ { VC_EQUALS, 29 /* */ }, // + /* 22 */ { VC_BACKSPACE, 30 /* */ }, // + /* 23 */ { VC_TAB, 31 /* */ }, // + /* 24 */ { VC_Q, 32 /* */ }, // + /* 25 */ { VC_W, 33 /* */ }, // + /* 26 */ { VC_E, 34 /* */ }, // + /* 27 */ { VC_R, 35 /* */ }, // + /* 28 */ { VC_T, 36 /* */ }, // + /* 29 */ { VC_Y, 37 /* */ }, // + /* 30 */ { VC_U, 38 /* */ }, // + /* 31 */ { VC_I, 39 /* */ }, // + /* 32 */ { VC_O, 40 /* */ }, // + /* 33 */ { VC_P, 41 /* */ }, // + /* 34 */ { VC_OPEN_BRACKET, 42 /* */ }, // + /* 35 */ { VC_CLOSE_BRACKET, 43 /* */ }, // + /* 36 */ { VC_ENTER, 44 /* */ }, // + /* 37 */ { VC_CONTROL_L, 45 /* */ }, // + /* 38 */ { VC_A, 46 /* */ }, // + /* 39 */ { VC_S, 47 /* */ }, // + /* 40 */ { VC_D, 48 /* */ }, // + /* 41 */ { VC_F, 49 /* */ }, // + /* 42 */ { VC_G, 50 /* */ }, // + /* 43 */ { VC_H, 51 /* */ }, // + /* 44 */ { VC_J, 52 /* */ }, // + /* 45 */ { VC_K, 53 /* */ }, // + /* 46 */ { VC_L, 54 /* */ }, // + /* 47 */ { VC_SEMICOLON, 55 /* */ }, // + /* 48 */ { VC_QUOTE, 56 /* */ }, // + /* 49 */ { VC_BACKQUOTE, 57 /* */ }, // + /* 50 */ { VC_SHIFT_L, 58 /* */ }, // + /* 51 */ { VC_BACK_SLASH, 59 /* */ }, // + /* 52 */ { VC_Z, 60 /* */ }, // + /* 53 */ { VC_X, 61 /* */ }, // + /* 54 */ { VC_C, 62 /* */ }, // + /* 55 */ { VC_V, 63 /* */ }, // + /* 56 */ { VC_B, 64 /* */ }, // + /* 57 */ { VC_N, 65 /* */ }, // + /* 58 */ { VC_M, 66 /* */ }, // + /* 59 */ { VC_COMMA, 67 /* */ }, // + /* 60 */ { VC_PERIOD, 68 /* */ }, // + /* 61 */ { VC_SLASH, 69 /* */ }, // + /* 62 */ { VC_SHIFT_R, 70 /* */ }, // + /* 63 */ { VC_KP_MULTIPLY, 71 /* */ }, // + /* 64 */ { VC_ALT_L, 72 /* */ }, // + /* 65 */ { VC_SPACE, 73 /* */ }, // + /* 66 */ { VC_CAPS_LOCK, 74 /* */ }, // + /* 67 */ { VC_F1, 75 /* */ }, // + /* 68 */ { VC_F2, 76 /* */ }, // + /* 69 */ { VC_F3, 77 /* */ }, // + /* 70 */ { VC_F4, 78 /* */ }, // + /* 71 */ { VC_F5, 79 /* */ }, // + /* 72 */ { VC_F6, 80 /* */ }, // + /* 73 */ { VC_F7, 81 /* */ }, // + /* 74 */ { VC_F8, 82 /* */ }, // + /* 75 */ { VC_F9, 83 /* */ }, // + /* 76 */ { VC_F10, 84 /* */ }, // + /* 77 */ { VC_NUM_LOCK, 85 /* */ }, // + /* 78 */ { VC_SCROLL_LOCK, 86 /* */ }, // + /* 79 */ { VC_KP_7, 87 /* */ }, // + /* 80 */ { VC_KP_8, 88 /* */ }, // + /* 81 */ { VC_KP_9, 89 /* */ }, // + /* 82 */ { VC_KP_SUBTRACT, 90 /* */ }, // + /* 83 */ { VC_KP_4, 91 /* */ }, // + /* 84 */ { VC_KP_5, 0 }, // + /* 85 */ { VC_KP_6, 0 }, // + /* 86 */ { VC_KP_ADD, 0 }, // + /* 87 */ { VC_KP_1, 95 /* */ }, // + /* 88 */ { VC_KP_2, 96 /* */ }, + /* 89 */ { VC_KP_3, 0 }, + /* 90 */ { VC_KP_0, 0 }, + /* 91 */ { VC_KP_SEPARATOR, 118 /* */ }, + /* 92 */ { VC_UNDEFINED, 119 /* */ }, + /* 93 */ { VC_UNDEFINED, 120 /* */ }, + /* 94 */ { VC_UNDEFINED, 0 }, + /* 95 */ { VC_F11, 0 }, + /* 96 */ { VC_F12, 0 }, + + /* First 97 chars are identical to XFree86! */ + + /* 97 */ { VC_HOME, 0 }, + /* 98 */ { VC_UP, 0 }, + /* 99 */ { VC_PAGE_UP, 121 /* */ }, + /* 100 */ { VC_LEFT, 122 /* */ }, + /* 101 */ { VC_UNDEFINED, 0 }, // TODO lower brightness key? + /* 102 */ { VC_RIGHT, 0 }, + /* 103 */ { VC_END, 0 }, + /* 104 */ { VC_DOWN, 0 }, + /* 105 */ { VC_PAGE_DOWN, 0 }, + /* 106 */ { VC_INSERT, 0 }, + /* 107 */ { VC_DELETE, 0 }, + /* 108 */ { VC_KP_ENTER, 0 }, + /* 109 */ { VC_CONTROL_R, 0 }, + /* 110 */ { VC_PAUSE, 0 }, + /* 111 */ { VC_PRINTSCREEN, 0 }, + /* 112 */ { VC_KP_DIVIDE, 0 }, + /* 113 */ { VC_ALT_R, 0 }, + /* 114 */ { VC_UNDEFINED, 0 }, // VC_BREAK? + /* 115 */ { VC_META_L, 0 }, + /* 116 */ { VC_META_R, 0 }, + /* 117 */ { VC_CONTEXT_MENU, 0 }, + /* 118 */ { VC_F13, 0 }, + /* 119 */ { VC_F14, 0 }, + /* 120 */ { VC_F15, 0 }, + /* 121 */ { VC_F16, 0 }, + /* 122 */ { VC_F17, 0 }, + /* 123 */ { VC_UNDEFINED, 0 }, // FIXME What is this key? + /* 124 */ { VC_UNDEFINED, 0 }, // Never Generated + /* 125 */ { VC_UNDEFINED, 133 /* */ }, // Never Generated + /* 126 */ { VC_KP_EQUALS, 0 }, + /* 127 */ { VC_UNDEFINED, 0 }, // Never Generated + /* 128 */ { VC_UNDEFINED, 0 }, // Never Generated + /* 129 */ { VC_UNDEFINED, 0 }, // Henkan + /* 130 */ { VC_UNDEFINED, 0 }, // Some extended Internet key + /* 131 */ { VC_UNDEFINED, 0 }, // Muhenkan + /* 132 */ { VC_UNDEFINED, 0 }, // + /* 133 */ { VC_YEN, 0 }, // + /* 134 */ { VC_UNDEFINED, 0 }, // + /* 135 */ { VC_UNDEFINED, 0 }, // + /* 136 */ { VC_UNDEFINED, 0 }, // + /* 137 */ { VC_UNDEFINED, 0 }, // + /* 138 */ { VC_UNDEFINED, 0 }, // + /* 139 */ { VC_UNDEFINED, 0 }, // + /* 140 */ { VC_UNDEFINED, 0 }, // + /* 141 */ { VC_UNDEFINED, 126 }, // + /* 142 */ { VC_UNDEFINED, 0 }, // + /* 143 */ { VC_UNDEFINED, 0 }, // + /* 144 */ { VC_UNDEFINED, 0 }, // + /* 145 */ { VC_UNDEFINED, 0 }, // + /* 146 */ { VC_UNDEFINED, 0 }, // + /* 147 */ { VC_UNDEFINED, 0 }, // + /* 148 */ { VC_UNDEFINED, 0 }, // + /* 149 */ { VC_UNDEFINED, 0 }, // + /* 150 */ { VC_UNDEFINED, 0 }, // + /* 151 */ { VC_UNDEFINED, 0 }, // + /* 152 */ { VC_UNDEFINED, 0 }, // + /* 153 */ { VC_UNDEFINED, 0 }, // + /* 154 */ { VC_UNDEFINED, 0 }, // + /* 155 */ { VC_UNDEFINED, 0 }, // + /* 156 */ { VC_UNDEFINED, 108 /* */ }, // Never Generated + /* 157 */ { VC_UNDEFINED, 109 /* */ }, // + /* 158 */ { VC_UNDEFINED, 0 }, // + /* 159 */ { VC_UNDEFINED, 0 }, // + /* 160 */ { VC_UNDEFINED, 0 }, // + /* 161 */ { VC_UNDEFINED, 0 }, // + /* 162 */ { VC_UNDEFINED, 0 }, // + /* 163 */ { VC_UNDEFINED, 0 }, // + /* 164 */ { VC_UNDEFINED, 0 }, // + /* 165 */ { VC_UNDEFINED, 0 }, // + /* 166 */ { VC_UNDEFINED, 0 }, // + /* 167 */ { VC_UNDEFINED, 0 }, // + /* 168 */ { VC_UNDEFINED, 0 }, // + /* 169 */ { VC_UNDEFINED, 0 }, // + /* 170 */ { VC_UNDEFINED, 0 }, // + /* 171 */ { VC_UNDEFINED, 0 }, // + /* 172 */ { VC_UNDEFINED, 0 }, // + /* 173 */ { VC_UNDEFINED, 0 }, // + /* 174 */ { VC_UNDEFINED, 0 }, // + /* 175 */ { VC_UNDEFINED, 0 }, // + /* 176 */ { VC_UNDEFINED, 0 }, // + /* 177 */ { VC_UNDEFINED, 0 }, // + /* 178 */ { VC_UNDEFINED, 0 }, // + /* 179 */ { VC_UNDEFINED, 0 }, // + /* 180 */ { VC_UNDEFINED, 0 }, // + /* 181 */ { VC_UNDEFINED, 112 }, // + /* 182 */ { VC_UNDEFINED, 0 }, // + /* 183 */ { VC_UNDEFINED, 111 }, // + /* 184 */ { VC_UNDEFINED, 113 }, // + /* 185 */ { VC_UNDEFINED, 0 }, // + /* 186 */ { VC_UNDEFINED, 0 }, // + /* 187 */ { VC_UNDEFINED, 0 }, // + /* 188 */ { VC_UNDEFINED, 0 }, // + /* 189 */ { VC_UNDEFINED, 0 }, // + /* 190 */ { VC_UNDEFINED, 0 }, // + /* 191 */ { VC_UNDEFINED, 0 }, // + /* 192 */ { VC_UNDEFINED, 0 }, // + /* 193 */ { VC_UNDEFINED, 0 }, // + /* 194 */ { VC_UNDEFINED, 0 }, // + /* 195 */ { VC_UNDEFINED, 0 }, // + /* 196 */ { VC_UNDEFINED, 0 }, // // 114 ? + /* 197 */ { VC_UNDEFINED, 110 }, // + /* 198 */ { VC_UNDEFINED, 0 }, // + /* 199 */ { VC_UNDEFINED, 97 /* */ }, // + /* 200 */ { VC_UNDEFINED, 98 }, // + /* 201 */ { VC_UNDEFINED, 99 }, // + /* 202 */ { VC_UNDEFINED, 0 }, // + /* 204 */ { VC_UNDEFINED, 0 }, // + /* 205 */ { VC_UNDEFINED, 102 }, // + /* 206 */ { VC_UNDEFINED, 0 }, // + /* 207 */ { VC_UNDEFINED, 103 }, // + /* 208 */ { VC_UNDEFINED, 104 }, // + /* 209 */ { VC_UNDEFINED, 105 }, // + /* 210 */ { VC_UNDEFINED, 106 }, // + /* 211 */ { VC_UNDEFINED, 107 }, // + /* 212 */ { VC_UNDEFINED, 0 }, // + /* 213 */ { VC_UNDEFINED, 0 }, // + /* 214 */ { VC_UNDEFINED, 0 }, // + /* 215 */ { VC_UNDEFINED, 0 }, // + /* 216 */ { VC_UNDEFINED, 0 }, // + /* 217 */ { VC_UNDEFINED, 0 }, // + /* 218 */ { VC_UNDEFINED, 0 }, // + /* 219 */ { VC_UNDEFINED, 115 /* */ }, // + /* 220 */ { VC_UNDEFINED, 116 /* */ }, // + /* 221 */ { VC_UNDEFINED, 117 /* */ }, // + /* 222 */ { VC_UNDEFINED, 0 }, // + /* 223 */ { VC_UNDEFINED, 0 }, // + /* 224 */ { VC_UNDEFINED, 0 }, // + /* 225 */ { VC_UNDEFINED, 0 }, // + /* 226 */ { VC_UNDEFINED, 0 }, // + /* 227 */ { VC_UNDEFINED, 0 }, // + /* 228 */ { VC_UNDEFINED, 0 }, // + /* 229 */ { VC_UNDEFINED, 0 }, // + /* 230 */ { VC_UNDEFINED, 0 }, // + /* 231 */ { VC_UNDEFINED, 0 }, // + /* 232 */ { VC_UNDEFINED, 0 }, // + /* 233 */ { VC_UNDEFINED, 0 }, // + /* 234 */ { VC_UNDEFINED, 0 }, // + /* 235 */ { VC_UNDEFINED, 0 }, // + /* 236 */ { VC_UNDEFINED, 0 }, // + /* 237 */ { VC_UNDEFINED, 0 }, // + /* 238 */ { VC_UNDEFINED, 0 }, // + /* 239 */ { VC_UNDEFINED, 0 }, // + /* 240 */ { VC_UNDEFINED, 0 }, // + /* 241 */ { VC_UNDEFINED, 0 }, // + /* 242 */ { VC_UNDEFINED, 0 }, // + /* 243 */ { VC_UNDEFINED, 0 }, // + /* 244 */ { VC_UNDEFINED, 0 }, // + /* 245 */ { VC_UNDEFINED, 0 }, // + /* 246 */ { VC_UNDEFINED, 0 }, // + /* 247 */ { VC_UNDEFINED, 0 }, // + /* 248 */ { VC_UNDEFINED, 0 }, // + /* 249 */ { VC_UNDEFINED, 0 }, // + /* 250 */ { VC_UNDEFINED, 0 }, // + /* 251 */ { VC_UNDEFINED, 0 }, // + /* 252 */ { VC_UNDEFINED, 0 }, // + /* 253 */ { VC_UNDEFINED, 0 }, // + /* 254 */ { VC_UNDEFINED, 0 }, // + /* 255 */ { VC_UNDEFINED, 0 }, // + }; + + +/*********************************************************************** + * The following table contains pairs of X11 keysym values for graphical + * characters and the corresponding Unicode value. The function + * keysym_to_unicode() maps a keysym onto a Unicode value using a binary + * search, therefore keysym_unicode_table[] must remain SORTED by KeySym + * value. + * + * We allow to represent any UCS character in the range U+00000000 to + * U+00FFFFFF by a keysym value in the range 0x01000000 to 0x01FFFFFF. + * This admittedly does not cover the entire 31-bit space of UCS, but + * it does cover all of the characters up to U+10FFFF, which can be + * represented by UTF-16, and more, and it is very unlikely that higher + * UCS codes will ever be assigned by ISO. So to get Unicode character + * U+ABCD you can directly use keysym 0x1000ABCD. + * + * NOTE: The comments in the table below contain the actual character + * encoded in UTF-8, so for viewing and editing best use an editor in + * UTF-8 mode. + * + * Author: Markus G. Kuhn , University of Cambridge, + * June 1999 + * + * Special thanks to Richard Verhoeven for preparing + * an initial draft of the mapping table. + * + * This table is in the public domain. Share and enjoy! + ***********************************************************************/ +static struct codepair { + uint16_t keysym; + uint16_t unicode; +} keysym_unicode_table[] = { + { 0x01A1, 0x0104 }, /* Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */ + { 0x01A2, 0x02D8 }, /* breve ˘ BREVE */ + { 0x01A3, 0x0141 }, /* Lstroke Ł LATIN CAPITAL LETTER L WITH STROKE */ + { 0x01A5, 0x013D }, /* Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */ + { 0x01A6, 0x015A }, /* Sacute Ś LATIN CAPITAL LETTER S WITH ACUTE */ + { 0x01A9, 0x0160 }, /* Scaron Š LATIN CAPITAL LETTER S WITH CARON */ + { 0x01AA, 0x015E }, /* Scedilla Ş LATIN CAPITAL LETTER S WITH CEDILLA */ + { 0x01AB, 0x0164 }, /* Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */ + { 0x01AC, 0x0179 }, /* Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */ + { 0x01AE, 0x017D }, /* Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */ + { 0x01AF, 0x017B }, /* Zabovedot Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE */ + { 0x01B1, 0x0105 }, /* aogonek ą LATIN SMALL LETTER A WITH OGONEK */ + { 0x01B2, 0x02DB }, /* ogonek ˛ OGONEK */ + { 0x01B3, 0x0142 }, /* lstroke ł LATIN SMALL LETTER L WITH STROKE */ + { 0x01B5, 0x013E }, /* lcaron ľ LATIN SMALL LETTER L WITH CARON */ + { 0x01B6, 0x015B }, /* sacute ś LATIN SMALL LETTER S WITH ACUTE */ + { 0x01B7, 0x02C7 }, /* caron ˇ CARON */ + { 0x01B9, 0x0161 }, /* scaron š LATIN SMALL LETTER S WITH CARON */ + { 0x01BA, 0x015F }, /* scedilla ş LATIN SMALL LETTER S WITH CEDILLA */ + { 0x01BB, 0x0165 }, /* tcaron ť LATIN SMALL LETTER T WITH CARON */ + { 0x01BC, 0x017A }, /* zacute ź LATIN SMALL LETTER Z WITH ACUTE */ + { 0x01BD, 0x02DD }, /* doubleacute ˝ DOUBLE ACUTE ACCENT */ + { 0x01BE, 0x017E }, /* zcaron ž LATIN SMALL LETTER Z WITH CARON */ + { 0x01BF, 0x017C }, /* zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */ + { 0x01C0, 0x0154 }, /* Racute Ŕ LATIN CAPITAL LETTER R WITH ACUTE */ + { 0x01C3, 0x0102 }, /* Abreve Ă LATIN CAPITAL LETTER A WITH BREVE */ + { 0x01C5, 0x0139 }, /* Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */ + { 0x01C6, 0x0106 }, /* Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */ + { 0x01C8, 0x010C }, /* Ccaron Č LATIN CAPITAL LETTER C WITH CARON */ + { 0x01CA, 0x0118 }, /* Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */ + { 0x01CC, 0x011A }, /* Ecaron Ě LATIN CAPITAL LETTER E WITH CARON */ + { 0x01CF, 0x010E }, /* Dcaron Ď LATIN CAPITAL LETTER D WITH CARON */ + { 0x01D0, 0x0110 }, /* Dstroke Đ LATIN CAPITAL LETTER D WITH STROKE */ + { 0x01D1, 0x0143 }, /* Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */ + { 0x01D2, 0x0147 }, /* Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */ + { 0x01D5, 0x0150 }, /* Odoubleacute Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */ + { 0x01D8, 0x0158 }, /* Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */ + { 0x01D9, 0x016E }, /* Uring Ů LATIN CAPITAL LETTER U WITH RING ABOVE */ + { 0x01DB, 0x0170 }, /* Udoubleacute Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */ + { 0x01DE, 0x0162 }, /* Tcedilla Ţ LATIN CAPITAL LETTER T WITH CEDILLA */ + { 0x01E0, 0x0155 }, /* racute ŕ LATIN SMALL LETTER R WITH ACUTE */ + { 0x01E3, 0x0103 }, /* abreve ă LATIN SMALL LETTER A WITH BREVE */ + { 0x01E5, 0x013A }, /* lacute ĺ LATIN SMALL LETTER L WITH ACUTE */ + { 0x01E6, 0x0107 }, /* cacute ć LATIN SMALL LETTER C WITH ACUTE */ + { 0x01E8, 0x010D }, /* ccaron č LATIN SMALL LETTER C WITH CARON */ + { 0x01EA, 0x0119 }, /* eogonek ę LATIN SMALL LETTER E WITH OGONEK */ + { 0x01EC, 0x011B }, /* ecaron ě LATIN SMALL LETTER E WITH CARON */ + { 0x01EF, 0x010F }, /* dcaron ď LATIN SMALL LETTER D WITH CARON */ + { 0x01F0, 0x0111 }, /* dstroke đ LATIN SMALL LETTER D WITH STROKE */ + { 0x01F1, 0x0144 }, /* nacute ń LATIN SMALL LETTER N WITH ACUTE */ + { 0x01F2, 0x0148 }, /* ncaron ň LATIN SMALL LETTER N WITH CARON */ + { 0x01F5, 0x0151 }, /* odoubleacute ő LATIN SMALL LETTER O WITH DOUBLE ACUTE */ + { 0x01F8, 0x0159 }, /* rcaron ř LATIN SMALL LETTER R WITH CARON */ + { 0x01F9, 0x016F }, /* uring ů LATIN SMALL LETTER U WITH RING ABOVE */ + { 0x01FB, 0x0171 }, /* udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */ + { 0x01FE, 0x0163 }, /* tcedilla ţ LATIN SMALL LETTER T WITH CEDILLA */ + { 0x01FF, 0x02D9 }, /* abovedot ˙ DOT ABOVE */ + { 0x02A1, 0x0126 }, /* Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */ + { 0x02A6, 0x0124 }, /* Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */ + { 0x02A9, 0x0130 }, /* Iabovedot İ LATIN CAPITAL LETTER I WITH DOT ABOVE */ + { 0x02AB, 0x011E }, /* Gbreve Ğ LATIN CAPITAL LETTER G WITH BREVE */ + { 0x02AC, 0x0134 }, /* Jcircumflex Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */ + { 0x02B1, 0x0127 }, /* hstroke ħ LATIN SMALL LETTER H WITH STROKE */ + { 0x02B6, 0x0125 }, /* hcircumflex ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX */ + { 0x02B9, 0x0131 }, /* idotless ı LATIN SMALL LETTER DOTLESS I */ + { 0x02BB, 0x011F }, /* gbreve ğ LATIN SMALL LETTER G WITH BREVE */ + { 0x02BC, 0x0135 }, /* jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */ + { 0x02C5, 0x010A }, /* Cabovedot Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE */ + { 0x02C6, 0x0108 }, /* Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */ + { 0x02D5, 0x0120 }, /* Gabovedot Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE */ + { 0x02D8, 0x011C }, /* Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */ + { 0x02DD, 0x016C }, /* Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */ + { 0x02DE, 0x015C }, /* Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */ + { 0x02E5, 0x010B }, /* cabovedot ċ LATIN SMALL LETTER C WITH DOT ABOVE */ + { 0x02E6, 0x0109 }, /* ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */ + { 0x02F5, 0x0121 }, /* gabovedot ġ LATIN SMALL LETTER G WITH DOT ABOVE */ + { 0x02F8, 0x011D }, /* gcircumflex ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX */ + { 0x02FD, 0x016D }, /* ubreve ŭ LATIN SMALL LETTER U WITH BREVE */ + { 0x02FE, 0x015D }, /* scircumflex ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX */ + { 0x03A2, 0x0138 }, /* kra ĸ LATIN SMALL LETTER KRA */ + { 0x03A3, 0x0156 }, /* Rcedilla Ŗ LATIN CAPITAL LETTER R WITH CEDILLA */ + { 0x03A5, 0x0128 }, /* Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */ + { 0x03A6, 0x013B }, /* Lcedilla Ļ LATIN CAPITAL LETTER L WITH CEDILLA */ + { 0x03AA, 0x0112 }, /* Emacron Ē LATIN CAPITAL LETTER E WITH MACRON */ + { 0x03AB, 0x0122 }, /* Gcedilla Ģ LATIN CAPITAL LETTER G WITH CEDILLA */ + { 0x03AC, 0x0166 }, /* Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */ + { 0x03B3, 0x0157 }, /* rcedilla ŗ LATIN SMALL LETTER R WITH CEDILLA */ + { 0x03B5, 0x0129 }, /* itilde ĩ LATIN SMALL LETTER I WITH TILDE */ + { 0x03B6, 0x013C }, /* lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */ + { 0x03BA, 0x0113 }, /* emacron ē LATIN SMALL LETTER E WITH MACRON */ + { 0x03BB, 0x0123 }, /* gcedilla ģ LATIN SMALL LETTER G WITH CEDILLA */ + { 0x03BC, 0x0167 }, /* tslash ŧ LATIN SMALL LETTER T WITH STROKE */ + { 0x03BD, 0x014A }, /* ENG Ŋ LATIN CAPITAL LETTER ENG */ + { 0x03BF, 0x014B }, /* eng ŋ LATIN SMALL LETTER ENG */ + { 0x03C0, 0x0100 }, /* Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */ + { 0x03C7, 0x012E }, /* Iogonek Į LATIN CAPITAL LETTER I WITH OGONEK */ + { 0x03CC, 0x0116 }, /* Eabovedot Ė LATIN CAPITAL LETTER E WITH DOT ABOVE */ + { 0x03CF, 0x012A }, /* Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */ + { 0x03D1, 0x0145 }, /* Ncedilla Ņ LATIN CAPITAL LETTER N WITH CEDILLA */ + { 0x03D2, 0x014C }, /* Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */ + { 0x03D3, 0x0136 }, /* Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */ + { 0x03D9, 0x0172 }, /* Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */ + { 0x03DD, 0x0168 }, /* Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */ + { 0x03DE, 0x016A }, /* Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */ + { 0x03E0, 0x0101 }, /* amacron ā LATIN SMALL LETTER A WITH MACRON */ + { 0x03E7, 0x012F }, /* iogonek į LATIN SMALL LETTER I WITH OGONEK */ + { 0x03EC, 0x0117 }, /* eabovedot ė LATIN SMALL LETTER E WITH DOT ABOVE */ + { 0x03EF, 0x012B }, /* imacron ī LATIN SMALL LETTER I WITH MACRON */ + { 0x03F1, 0x0146 }, /* ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */ + { 0x03F2, 0x014D }, /* omacron ō LATIN SMALL LETTER O WITH MACRON */ + { 0x03F3, 0x0137 }, /* kcedilla ķ LATIN SMALL LETTER K WITH CEDILLA */ + { 0x03F9, 0x0173 }, /* uogonek ų LATIN SMALL LETTER U WITH OGONEK */ + { 0x03FD, 0x0169 }, /* utilde ũ LATIN SMALL LETTER U WITH TILDE */ + { 0x03FE, 0x016B }, /* umacron ū LATIN SMALL LETTER U WITH MACRON */ + { 0x047E, 0x203E }, /* overline ‾ OVERLINE */ + { 0x04A1, 0x3002 }, /* kana_fullstop 。 IDEOGRAPHIC FULL STOP */ + { 0x04A2, 0x300C }, /* kana_openingbracket 「 LEFT CORNER BRACKET */ + { 0x04A3, 0x300D }, /* kana_closingbracket 」 RIGHT CORNER BRACKET */ + { 0x04A4, 0x3001 }, /* kana_comma 、 IDEOGRAPHIC COMMA */ + { 0x04A5, 0x30FB }, /* kana_conjunctive ・ KATAKANA MIDDLE DOT */ + { 0x04A6, 0x30F2 }, /* kana_WO ヲ KATAKANA LETTER WO */ + { 0x04A7, 0x30A1 }, /* kana_a ァ KATAKANA LETTER SMALL A */ + { 0x04A8, 0x30A3 }, /* kana_i ィ KATAKANA LETTER SMALL I */ + { 0x04A9, 0x30A5 }, /* kana_u ゥ KATAKANA LETTER SMALL U */ + { 0x04AA, 0x30A7 }, /* kana_e ェ KATAKANA LETTER SMALL E */ + { 0x04AB, 0x30A9 }, /* kana_o ォ KATAKANA LETTER SMALL O */ + { 0x04AC, 0x30E3 }, /* kana_ya ャ KATAKANA LETTER SMALL YA */ + { 0x04AD, 0x30E5 }, /* kana_yu ュ KATAKANA LETTER SMALL YU */ + { 0x04AE, 0x30E7 }, /* kana_yo ョ KATAKANA LETTER SMALL YO */ + { 0x04AF, 0x30C3 }, /* kana_tsu ッ KATAKANA LETTER SMALL TU */ + { 0x04B0, 0x30FC }, /* prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */ + { 0x04B1, 0x30A2 }, /* kana_A ア KATAKANA LETTER A */ + { 0x04B2, 0x30A4 }, /* kana_I イ KATAKANA LETTER I */ + { 0x04B3, 0x30A6 }, /* kana_U ウ KATAKANA LETTER U */ + { 0x04B4, 0x30A8 }, /* kana_E エ KATAKANA LETTER E */ + { 0x04B5, 0x30AA }, /* kana_O オ KATAKANA LETTER O */ + { 0x04B6, 0x30AB }, /* kana_KA カ KATAKANA LETTER KA */ + { 0x04B7, 0x30AD }, /* kana_KI キ KATAKANA LETTER KI */ + { 0x04B8, 0x30AF }, /* kana_KU ク KATAKANA LETTER KU */ + { 0x04B9, 0x30B1 }, /* kana_KE ケ KATAKANA LETTER KE */ + { 0x04BA, 0x30B3 }, /* kana_KO コ KATAKANA LETTER KO */ + { 0x04BB, 0x30B5 }, /* kana_SA サ KATAKANA LETTER SA */ + { 0x04BC, 0x30B7 }, /* kana_SHI シ KATAKANA LETTER SI */ + { 0x04BD, 0x30B9 }, /* kana_SU ス KATAKANA LETTER SU */ + { 0x04BE, 0x30BB }, /* kana_SE セ KATAKANA LETTER SE */ + { 0x04BF, 0x30BD }, /* kana_SO ソ KATAKANA LETTER SO */ + { 0x04C0, 0x30BF }, /* kana_TA タ KATAKANA LETTER TA */ + { 0x04C1, 0x30C1 }, /* kana_CHI チ KATAKANA LETTER TI */ + { 0x04C2, 0x30C4 }, /* kana_TSU ツ KATAKANA LETTER TU */ + { 0x04C3, 0x30C6 }, /* kana_TE テ KATAKANA LETTER TE */ + { 0x04C4, 0x30C8 }, /* kana_TO ト KATAKANA LETTER TO */ + { 0x04C5, 0x30CA }, /* kana_NA ナ KATAKANA LETTER NA */ + { 0x04C6, 0x30CB }, /* kana_NI ニ KATAKANA LETTER NI */ + { 0x04C7, 0x30CC }, /* kana_NU ヌ KATAKANA LETTER NU */ + { 0x04C8, 0x30CD }, /* kana_NE ネ KATAKANA LETTER NE */ + { 0x04C9, 0x30CE }, /* kana_NO ノ KATAKANA LETTER NO */ + { 0x04CA, 0x30CF }, /* kana_HA ハ KATAKANA LETTER HA */ + { 0x04CB, 0x30D2 }, /* kana_HI ヒ KATAKANA LETTER HI */ + { 0x04CC, 0x30D5 }, /* kana_FU フ KATAKANA LETTER HU */ + { 0x04CD, 0x30D8 }, /* kana_HE ヘ KATAKANA LETTER HE */ + { 0x04CE, 0x30DB }, /* kana_HO ホ KATAKANA LETTER HO */ + { 0x04CF, 0x30DE }, /* kana_MA マ KATAKANA LETTER MA */ + { 0x04D0, 0x30DF }, /* kana_MI ミ KATAKANA LETTER MI */ + { 0x04D1, 0x30E0 }, /* kana_MU ム KATAKANA LETTER MU */ + { 0x04D2, 0x30E1 }, /* kana_ME メ KATAKANA LETTER ME */ + { 0x04D3, 0x30E2 }, /* kana_MO モ KATAKANA LETTER MO */ + { 0x04D4, 0x30E4 }, /* kana_YA ヤ KATAKANA LETTER YA */ + { 0x04D5, 0x30E6 }, /* kana_YU ユ KATAKANA LETTER YU */ + { 0x04D6, 0x30E8 }, /* kana_YO ヨ KATAKANA LETTER YO */ + { 0x04D7, 0x30E9 }, /* kana_RA ラ KATAKANA LETTER RA */ + { 0x04D8, 0x30EA }, /* kana_RI リ KATAKANA LETTER RI */ + { 0x04D9, 0x30EB }, /* kana_RU ル KATAKANA LETTER RU */ + { 0x04DA, 0x30EC }, /* kana_RE レ KATAKANA LETTER RE */ + { 0x04DB, 0x30ED }, /* kana_RO ロ KATAKANA LETTER RO */ + { 0x04DC, 0x30EF }, /* kana_WA ワ KATAKANA LETTER WA */ + { 0x04DD, 0x30F3 }, /* kana_N ン KATAKANA LETTER N */ + { 0x04DE, 0x309B }, /* voicedsound ゛ KATAKANA-HIRAGANA VOICED SOUND MARK */ + { 0x04DF, 0x309C }, /* semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ + { 0x05AC, 0x060C }, /* Arabic_comma ، ARABIC COMMA */ + { 0x05BB, 0x061B }, /* Arabic_semicolon ؛ ARABIC SEMICOLON */ + { 0x05BF, 0x061F }, /* Arabic_question_mark ؟ ARABIC QUESTION MARK */ + { 0x05C1, 0x0621 }, /* Arabic_hamza ء ARABIC LETTER HAMZA */ + { 0x05C2, 0x0622 }, /* Arabic_maddaonalef آ ARABIC LETTER ALEF WITH MADDA ABOVE */ + { 0x05C3, 0x0623 }, /* Arabic_hamzaonalef أ ARABIC LETTER ALEF WITH HAMZA ABOVE */ + { 0x05C4, 0x0624 }, /* Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */ + { 0x05C5, 0x0625 }, /* Arabic_hamzaunderalef إ ARABIC LETTER ALEF WITH HAMZA BELOW */ + { 0x05C6, 0x0626 }, /* Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */ + { 0x05C7, 0x0627 }, /* Arabic_alef ا ARABIC LETTER ALEF */ + { 0x05C8, 0x0628 }, /* Arabic_beh ب ARABIC LETTER BEH */ + { 0x05C9, 0x0629 }, /* Arabic_tehmarbuta ة ARABIC LETTER TEH MARBUTA */ + { 0x05CA, 0x062A }, /* Arabic_teh ت ARABIC LETTER TEH */ + { 0x05CB, 0x062B }, /* Arabic_theh ث ARABIC LETTER THEH */ + { 0x05CC, 0x062C }, /* Arabic_jeem ج ARABIC LETTER JEEM */ + { 0x05CD, 0x062D }, /* Arabic_hah ح ARABIC LETTER HAH */ + { 0x05CE, 0x062E }, /* Arabic_khah خ ARABIC LETTER KHAH */ + { 0x05CF, 0x062F }, /* Arabic_dal د ARABIC LETTER DAL */ + { 0x05D0, 0x0630 }, /* Arabic_thal ذ ARABIC LETTER THAL */ + { 0x05D1, 0x0631 }, /* Arabic_ra ر ARABIC LETTER REH */ + { 0x05D2, 0x0632 }, /* Arabic_zain ز ARABIC LETTER ZAIN */ + { 0x05D3, 0x0633 }, /* Arabic_seen س ARABIC LETTER SEEN */ + { 0x05D4, 0x0634 }, /* Arabic_sheen ش ARABIC LETTER SHEEN */ + { 0x05D5, 0x0635 }, /* Arabic_sad ص ARABIC LETTER SAD */ + { 0x05D6, 0x0636 }, /* Arabic_dad ض ARABIC LETTER DAD */ + { 0x05D7, 0x0637 }, /* Arabic_tah ط ARABIC LETTER TAH */ + { 0x05D8, 0x0638 }, /* Arabic_zah ظ ARABIC LETTER ZAH */ + { 0x05D9, 0x0639 }, /* Arabic_ain ع ARABIC LETTER AIN */ + { 0x05DA, 0x063A }, /* Arabic_ghain غ ARABIC LETTER GHAIN */ + { 0x05E0, 0x0640 }, /* Arabic_tatweel ـ ARABIC TATWEEL */ + { 0x05E1, 0x0641 }, /* Arabic_feh ف ARABIC LETTER FEH */ + { 0x05E2, 0x0642 }, /* Arabic_qaf ق ARABIC LETTER QAF */ + { 0x05E3, 0x0643 }, /* Arabic_kaf ك ARABIC LETTER KAF */ + { 0x05E4, 0x0644 }, /* Arabic_lam ل ARABIC LETTER LAM */ + { 0x05E5, 0x0645 }, /* Arabic_meem م ARABIC LETTER MEEM */ + { 0x05E6, 0x0646 }, /* Arabic_noon ن ARABIC LETTER NOON */ + { 0x05E7, 0x0647 }, /* Arabic_ha ه ARABIC LETTER HEH */ + { 0x05E8, 0x0648 }, /* Arabic_waw و ARABIC LETTER WAW */ + { 0x05E9, 0x0649 }, /* Arabic_alefmaksura ى ARABIC LETTER ALEF MAKSURA */ + { 0x05EA, 0x064A }, /* Arabic_yeh ي ARABIC LETTER YEH */ + { 0x05EB, 0x064B }, /* Arabic_fathatan ً ARABIC FATHATAN */ + { 0x05EC, 0x064C }, /* Arabic_dammatan ٌ ARABIC DAMMATAN */ + { 0x05ED, 0x064D }, /* Arabic_kasratan ٍ ARABIC KASRATAN */ + { 0x05EE, 0x064E }, /* Arabic_fatha َ ARABIC FATHA */ + { 0x05EF, 0x064F }, /* Arabic_damma ُ ARABIC DAMMA */ + { 0x05F0, 0x0650 }, /* Arabic_kasra ِ ARABIC KASRA */ + { 0x05F1, 0x0651 }, /* Arabic_shadda ّ ARABIC SHADDA */ + { 0x05F2, 0x0652 }, /* Arabic_sukun ْ ARABIC SUKUN */ + { 0x06A1, 0x0452 }, /* Serbian_dje ђ CYRILLIC SMALL LETTER DJE */ + { 0x06A2, 0x0453 }, /* Macedonia_gje ѓ CYRILLIC SMALL LETTER GJE */ + { 0x06A3, 0x0451 }, /* Cyrillic_io ё CYRILLIC SMALL LETTER IO */ + { 0x06A4, 0x0454 }, /* Ukrainian_ie є CYRILLIC SMALL LETTER UKRAINIAN IE */ + { 0x06A5, 0x0455 }, /* Macedonia_dse ѕ CYRILLIC SMALL LETTER DZE */ + { 0x06A6, 0x0456 }, /* Ukrainian_i і CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */ + { 0x06A7, 0x0457 }, /* Ukrainian_yi ї CYRILLIC SMALL LETTER YI */ + { 0x06A8, 0x0458 }, /* Cyrillic_je ј CYRILLIC SMALL LETTER JE */ + { 0x06A9, 0x0459 }, /* Cyrillic_lje љ CYRILLIC SMALL LETTER LJE */ + { 0x06AA, 0x045A }, /* Cyrillic_nje њ CYRILLIC SMALL LETTER NJE */ + { 0x06AB, 0x045B }, /* Serbian_tshe ћ CYRILLIC SMALL LETTER TSHE */ + { 0x06AC, 0x045C }, /* Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */ + { 0x06AE, 0x045E }, /* Byelorussian_shortu ў CYRILLIC SMALL LETTER SHORT U */ + { 0x06AF, 0x045F }, /* Cyrillic_dzhe џ CYRILLIC SMALL LETTER DZHE */ + { 0x06B0, 0x2116 }, /* numerosign № NUMERO SIGN */ + { 0x06B1, 0x0402 }, /* Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */ + { 0x06B2, 0x0403 }, /* Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */ + { 0x06B3, 0x0401 }, /* Cyrillic_IO Ё CYRILLIC CAPITAL LETTER IO */ + { 0x06B4, 0x0404 }, /* Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */ + { 0x06B5, 0x0405 }, /* Macedonia_DSE Ѕ CYRILLIC CAPITAL LETTER DZE */ + { 0x06B6, 0x0406 }, /* Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */ + { 0x06B7, 0x0407 }, /* Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */ + { 0x06B8, 0x0408 }, /* Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */ + { 0x06B9, 0x0409 }, /* Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */ + { 0x06BA, 0x040A }, /* Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */ + { 0x06BB, 0x040B }, /* Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */ + { 0x06BC, 0x040C }, /* Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */ + { 0x06BE, 0x040E }, /* Byelorussian_SHORTU Ў CYRILLIC CAPITAL LETTER SHORT U */ + { 0x06BF, 0x040F }, /* Cyrillic_DZHE Џ CYRILLIC CAPITAL LETTER DZHE */ + { 0x06C0, 0x044E }, /* Cyrillic_yu ю CYRILLIC SMALL LETTER YU */ + { 0x06C1, 0x0430 }, /* Cyrillic_a а CYRILLIC SMALL LETTER A */ + { 0x06C2, 0x0431 }, /* Cyrillic_be б CYRILLIC SMALL LETTER BE */ + { 0x06C3, 0x0446 }, /* Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */ + { 0x06C4, 0x0434 }, /* Cyrillic_de д CYRILLIC SMALL LETTER DE */ + { 0x06C5, 0x0435 }, /* Cyrillic_ie е CYRILLIC SMALL LETTER IE */ + { 0x06C6, 0x0444 }, /* Cyrillic_ef ф CYRILLIC SMALL LETTER EF */ + { 0x06C7, 0x0433 }, /* Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */ + { 0x06C8, 0x0445 }, /* Cyrillic_ha х CYRILLIC SMALL LETTER HA */ + { 0x06C9, 0x0438 }, /* Cyrillic_i и CYRILLIC SMALL LETTER I */ + { 0x06CA, 0x0439 }, /* Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */ + { 0x06CB, 0x043A }, /* Cyrillic_ka к CYRILLIC SMALL LETTER KA */ + { 0x06CC, 0x043B }, /* Cyrillic_el л CYRILLIC SMALL LETTER EL */ + { 0x06CD, 0x043C }, /* Cyrillic_em м CYRILLIC SMALL LETTER EM */ + { 0x06CE, 0x043D }, /* Cyrillic_en н CYRILLIC SMALL LETTER EN */ + { 0x06CF, 0x043E }, /* Cyrillic_o о CYRILLIC SMALL LETTER O */ + { 0x06D0, 0x043F }, /* Cyrillic_pe п CYRILLIC SMALL LETTER PE */ + { 0x06D1, 0x044F }, /* Cyrillic_ya я CYRILLIC SMALL LETTER YA */ + { 0x06D2, 0x0440 }, /* Cyrillic_er р CYRILLIC SMALL LETTER ER */ + { 0x06D3, 0x0441 }, /* Cyrillic_es с CYRILLIC SMALL LETTER ES */ + { 0x06D4, 0x0442 }, /* Cyrillic_te т CYRILLIC SMALL LETTER TE */ + { 0x06D5, 0x0443 }, /* Cyrillic_u у CYRILLIC SMALL LETTER U */ + { 0x06D6, 0x0436 }, /* Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */ + { 0x06D7, 0x0432 }, /* Cyrillic_ve в CYRILLIC SMALL LETTER VE */ + { 0x06D8, 0x044C }, /* Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */ + { 0x06D9, 0x044B }, /* Cyrillic_yeru ы CYRILLIC SMALL LETTER YERU */ + { 0x06DA, 0x0437 }, /* Cyrillic_ze з CYRILLIC SMALL LETTER ZE */ + { 0x06DB, 0x0448 }, /* Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */ + { 0x06DC, 0x044D }, /* Cyrillic_e э CYRILLIC SMALL LETTER E */ + { 0x06DD, 0x0449 }, /* Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */ + { 0x06DE, 0x0447 }, /* Cyrillic_che ч CYRILLIC SMALL LETTER CHE */ + { 0x06DF, 0x044A }, /* Cyrillic_hardsign ъ CYRILLIC SMALL LETTER HARD SIGN */ + { 0x06E0, 0x042E }, /* Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */ + { 0x06E1, 0x0410 }, /* Cyrillic_A А CYRILLIC CAPITAL LETTER A */ + { 0x06E2, 0x0411 }, /* Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */ + { 0x06E3, 0x0426 }, /* Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */ + { 0x06E4, 0x0414 }, /* Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */ + { 0x06E5, 0x0415 }, /* Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */ + { 0x06E6, 0x0424 }, /* Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */ + { 0x06E7, 0x0413 }, /* Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */ + { 0x06E8, 0x0425 }, /* Cyrillic_HA Х CYRILLIC CAPITAL LETTER HA */ + { 0x06E9, 0x0418 }, /* Cyrillic_I И CYRILLIC CAPITAL LETTER I */ + { 0x06EA, 0x0419 }, /* Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */ + { 0x06EB, 0x041A }, /* Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */ + { 0x06EC, 0x041B }, /* Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */ + { 0x06ED, 0x041C }, /* Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */ + { 0x06EE, 0x041D }, /* Cyrillic_EN Н CYRILLIC CAPITAL LETTER EN */ + { 0x06EF, 0x041E }, /* Cyrillic_O О CYRILLIC CAPITAL LETTER O */ + { 0x06F0, 0x041F }, /* Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */ + { 0x06F1, 0x042F }, /* Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */ + { 0x06F2, 0x0420 }, /* Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */ + { 0x06F3, 0x0421 }, /* Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */ + { 0x06F4, 0x0422 }, /* Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */ + { 0x06F5, 0x0423 }, /* Cyrillic_U У CYRILLIC CAPITAL LETTER U */ + { 0x06F6, 0x0416 }, /* Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */ + { 0x06F7, 0x0412 }, /* Cyrillic_VE В CYRILLIC CAPITAL LETTER VE */ + { 0x06F8, 0x042C }, /* Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */ + { 0x06F9, 0x042B }, /* Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */ + { 0x06FA, 0x0417 }, /* Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */ + { 0x06FB, 0x0428 }, /* Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */ + { 0x06FC, 0x042D }, /* Cyrillic_E Э CYRILLIC CAPITAL LETTER E */ + { 0x06FD, 0x0429 }, /* Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */ + { 0x06FE, 0x0427 }, /* Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */ + { 0x06FF, 0x042A }, /* Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */ + { 0x07A1, 0x0386 }, /* Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */ + { 0x07A2, 0x0388 }, /* Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */ + { 0x07A3, 0x0389 }, /* Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */ + { 0x07A4, 0x038A }, /* Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */ + { 0x07A5, 0x03AA }, /* Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */ + { 0x07A7, 0x038C }, /* Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */ + { 0x07A8, 0x038E }, /* Greek_UPSILONaccent Ύ GREEK CAPITAL LETTER UPSILON WITH TONOS */ + { 0x07A9, 0x03AB }, /* Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */ + { 0x07AB, 0x038F }, /* Greek_OMEGAaccent Ώ GREEK CAPITAL LETTER OMEGA WITH TONOS */ + { 0x07AE, 0x0385 }, /* Greek_accentdieresis ΅ GREEK DIALYTIKA TONOS */ + { 0x07AF, 0x2015 }, /* Greek_horizbar ― HORIZONTAL BAR */ + { 0x07B1, 0x03AC }, /* Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */ + { 0x07B2, 0x03AD }, /* Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */ + { 0x07B3, 0x03AE }, /* Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */ + { 0x07B4, 0x03AF }, /* Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */ + { 0x07B5, 0x03CA }, /* Greek_iotadieresis ϊ GREEK SMALL LETTER IOTA WITH DIALYTIKA */ + { 0x07B6, 0x0390 }, /* Greek_iotaaccentdieresis ΐ GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */ + { 0x07B7, 0x03CC }, /* Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */ + { 0x07B8, 0x03CD }, /* Greek_upsilonaccent ύ GREEK SMALL LETTER UPSILON WITH TONOS */ + { 0x07B9, 0x03CB }, /* Greek_upsilondieresis ϋ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */ + { 0x07BA, 0x03B0 }, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */ + { 0x07BB, 0x03CE }, /* Greek_omegaaccent ώ GREEK SMALL LETTER OMEGA WITH TONOS */ + { 0x07C1, 0x0391 }, /* Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */ + { 0x07C2, 0x0392 }, /* Greek_BETA Β GREEK CAPITAL LETTER BETA */ + { 0x07C3, 0x0393 }, /* Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */ + { 0x07C4, 0x0394 }, /* Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */ + { 0x07C5, 0x0395 }, /* Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */ + { 0x07C6, 0x0396 }, /* Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */ + { 0x07C7, 0x0397 }, /* Greek_ETA Η GREEK CAPITAL LETTER ETA */ + { 0x07C8, 0x0398 }, /* Greek_THETA Θ GREEK CAPITAL LETTER THETA */ + { 0x07C9, 0x0399 }, /* Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */ + { 0x07CA, 0x039A }, /* Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */ + { 0x07CB, 0x039B }, /* Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */ + { 0x07CC, 0x039C }, /* Greek_MU Μ GREEK CAPITAL LETTER MU */ + { 0x07CD, 0x039D }, /* Greek_NU Ν GREEK CAPITAL LETTER NU */ + { 0x07CE, 0x039E }, /* Greek_XI Ξ GREEK CAPITAL LETTER XI */ + { 0x07CF, 0x039F }, /* Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */ + { 0x07D0, 0x03A0 }, /* Greek_PI Π GREEK CAPITAL LETTER PI */ + { 0x07D1, 0x03A1 }, /* Greek_RHO Ρ GREEK CAPITAL LETTER RHO */ + { 0x07D2, 0x03A3 }, /* Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */ + { 0x07D4, 0x03A4 }, /* Greek_TAU Τ GREEK CAPITAL LETTER TAU */ + { 0x07D5, 0x03A5 }, /* Greek_UPSILON Υ GREEK CAPITAL LETTER UPSILON */ + { 0x07D6, 0x03A6 }, /* Greek_PHI Φ GREEK CAPITAL LETTER PHI */ + { 0x07D7, 0x03A7 }, /* Greek_CHI Χ GREEK CAPITAL LETTER CHI */ + { 0x07D8, 0x03A8 }, /* Greek_PSI Ψ GREEK CAPITAL LETTER PSI */ + { 0x07D9, 0x03A9 }, /* Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */ + { 0x07E1, 0x03B1 }, /* Greek_alpha α GREEK SMALL LETTER ALPHA */ + { 0x07E2, 0x03B2 }, /* Greek_beta β GREEK SMALL LETTER BETA */ + { 0x07E3, 0x03B3 }, /* Greek_gamma γ GREEK SMALL LETTER GAMMA */ + { 0x07E4, 0x03B4 }, /* Greek_delta δ GREEK SMALL LETTER DELTA */ + { 0x07E5, 0x03B5 }, /* Greek_epsilon ε GREEK SMALL LETTER EPSILON */ + { 0x07E6, 0x03B6 }, /* Greek_zeta ζ GREEK SMALL LETTER ZETA */ + { 0x07E7, 0x03B7 }, /* Greek_eta η GREEK SMALL LETTER ETA */ + { 0x07E8, 0x03B8 }, /* Greek_theta θ GREEK SMALL LETTER THETA */ + { 0x07E9, 0x03B9 }, /* Greek_iota ι GREEK SMALL LETTER IOTA */ + { 0x07EA, 0x03BA }, /* Greek_kappa κ GREEK SMALL LETTER KAPPA */ + { 0x07EB, 0x03BB }, /* Greek_lambda λ GREEK SMALL LETTER LAMDA */ + { 0x07EC, 0x03BC }, /* Greek_mu μ GREEK SMALL LETTER MU */ + { 0x07ED, 0x03BD }, /* Greek_nu ν GREEK SMALL LETTER NU */ + { 0x07EE, 0x03BE }, /* Greek_xi ξ GREEK SMALL LETTER XI */ + { 0x07EF, 0x03BF }, /* Greek_omicron ο GREEK SMALL LETTER OMICRON */ + { 0x07F0, 0x03C0 }, /* Greek_pi π GREEK SMALL LETTER PI */ + { 0x07F1, 0x03C1 }, /* Greek_rho ρ GREEK SMALL LETTER RHO */ + { 0x07F2, 0x03C3 }, /* Greek_sigma σ GREEK SMALL LETTER SIGMA */ + { 0x07F3, 0x03C2 }, /* Greek_finalsmallsigma ς GREEK SMALL LETTER FINAL SIGMA */ + { 0x07F4, 0x03C4 }, /* Greek_tau τ GREEK SMALL LETTER TAU */ + { 0x07F5, 0x03C5 }, /* Greek_upsilon υ GREEK SMALL LETTER UPSILON */ + { 0x07F6, 0x03C6 }, /* Greek_phi φ GREEK SMALL LETTER PHI */ + { 0x07F7, 0x03C7 }, /* Greek_chi χ GREEK SMALL LETTER CHI */ + { 0x07F8, 0x03C8 }, /* Greek_psi ψ GREEK SMALL LETTER PSI */ + { 0x07F9, 0x03C9 }, /* Greek_omega ω GREEK SMALL LETTER OMEGA */ + { 0x08A1, 0x23B7 }, /* leftradical ⎷ ??? */ + { 0x08A2, 0x250C }, /* topleftradical ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */ + { 0x08A3, 0x2500 }, /* horizconnector ─ BOX DRAWINGS LIGHT HORIZONTAL */ + { 0x08A4, 0x2320 }, /* topintegral ⌠ TOP HALF INTEGRAL */ + { 0x08A5, 0x2321 }, /* botintegral ⌡ BOTTOM HALF INTEGRAL */ + { 0x08A6, 0x2502 }, /* vertconnector │ BOX DRAWINGS LIGHT VERTICAL */ + { 0x08A7, 0x23A1 }, /* topleftsqbracket ⎡ ??? */ + { 0x08A8, 0x23A3 }, /* botleftsqbracket ⎣ ??? */ + { 0x08A9, 0x23A4 }, /* toprightsqbracket ⎤ ??? */ + { 0x08AA, 0x23A6 }, /* botrightsqbracket ⎦ ??? */ + { 0x08AB, 0x239B }, /* topleftparens ⎛ ??? */ + { 0x08AC, 0x239D }, /* botleftparens ⎝ ??? */ + { 0x08AD, 0x239E }, /* toprightparens ⎞ ??? */ + { 0x08AE, 0x23A0 }, /* botrightparens ⎠ ??? */ + { 0x08AF, 0x23A8 }, /* leftmiddlecurlybrace ⎨ ??? */ + { 0x08B0, 0x23AC }, /* rightmiddlecurlybrace ⎬ ??? */ +/* 0x08B1 topleftsummation ? ??? */ +/* 0x08B2 botleftsummation ? ??? */ +/* 0x08B3 topvertsummationconnector ? ??? */ +/* 0x08B4 botvertsummationconnector ? ??? */ +/* 0x08B5 toprightsummation ? ??? */ +/* 0x08B6 botrightsummation ? ??? */ +/* 0x08B7 rightmiddlesummation ? ??? */ + { 0x08BC, 0x2264 }, /* lessthanequal ≤ LESS-THAN OR EQUAL TO */ + { 0x08BD, 0x2260 }, /* notequal ≠ NOT EQUAL TO */ + { 0x08BE, 0x2265 }, /* greaterthanequal ≥ GREATER-THAN OR EQUAL TO */ + { 0x08BF, 0x222B }, /* integral ∫ INTEGRAL */ + { 0x08C0, 0x2234 }, /* therefore ∴ THEREFORE */ + { 0x08C1, 0x221D }, /* variation ∝ PROPORTIONAL TO */ + { 0x08C2, 0x221E }, /* infinity ∞ INFINITY */ + { 0x08C5, 0x2207 }, /* nabla ∇ NABLA */ + { 0x08C8, 0x223C }, /* approximate ∼ TILDE OPERATOR */ + { 0x08C9, 0x2243 }, /* similarequal ≃ ASYMPTOTICALLY EQUAL TO */ + { 0x08CD, 0x21D4 }, /* ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */ + { 0x08CE, 0x21D2 }, /* implies ⇒ RIGHTWARDS DOUBLE ARROW */ + { 0x08CF, 0x2261 }, /* identical ≡ IDENTICAL TO */ + { 0x08D6, 0x221A }, /* radical √ SQUARE ROOT */ + { 0x08DA, 0x2282 }, /* includedin ⊂ SUBSET OF */ + { 0x08DB, 0x2283 }, /* includes ⊃ SUPERSET OF */ + { 0x08DC, 0x2229 }, /* intersection ∩ INTERSECTION */ + { 0x08DD, 0x222A }, /* union ∪ UNION */ + { 0x08DE, 0x2227 }, /* logicaland ∧ LOGICAL AND */ + { 0x08DF, 0x2228 }, /* logicalor ∨ LOGICAL OR */ + { 0x08EF, 0x2202 }, /* partialderivative ∂ PARTIAL DIFFERENTIAL */ + { 0x08F6, 0x0192 }, /* function ƒ LATIN SMALL LETTER F WITH HOOK */ + { 0x08FB, 0x2190 }, /* leftarrow ← LEFTWARDS ARROW */ + { 0x08FC, 0x2191 }, /* uparrow ↑ UPWARDS ARROW */ + { 0x08FD, 0x2192 }, /* rightarrow → RIGHTWARDS ARROW */ + { 0x08FE, 0x2193 }, /* downarrow ↓ DOWNWARDS ARROW */ +/* 0x09DF blank ? ??? */ + { 0x09E0, 0x25C6 }, /* soliddiamond ◆ BLACK DIAMOND */ + { 0x09E1, 0x2592 }, /* checkerboard ▒ MEDIUM SHADE */ + { 0x09E2, 0x2409 }, /* ht ␉ SYMBOL FOR HORIZONTAL TABULATION */ + { 0x09E3, 0x240C }, /* ff ␌ SYMBOL FOR FORM FEED */ + { 0x09E4, 0x240D }, /* cr ␍ SYMBOL FOR CARRIAGE RETURN */ + { 0x09E5, 0x240A }, /* lf ␊ SYMBOL FOR LINE FEED */ + { 0x09E8, 0x2424 }, /* nl ␤ SYMBOL FOR NEWLINE */ + { 0x09E9, 0x240B }, /* vt ␋ SYMBOL FOR VERTICAL TABULATION */ + { 0x09EA, 0x2518 }, /* lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */ + { 0x09EB, 0x2510 }, /* uprightcorner ┐ BOX DRAWINGS LIGHT DOWN AND LEFT */ + { 0x09EC, 0x250C }, /* upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */ + { 0x09ED, 0x2514 }, /* lowleftcorner └ BOX DRAWINGS LIGHT UP AND RIGHT */ + { 0x09EE, 0x253C }, /* crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ + { 0x09EF, 0x23BA }, /* horizlinescan1 ⎺ HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */ + { 0x09F0, 0x23BB }, /* horizlinescan3 ⎻ HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */ + { 0x09F1, 0x2500 }, /* horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */ + { 0x09F2, 0x23BC }, /* horizlinescan7 ⎼ HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */ + { 0x09F3, 0x23BD }, /* horizlinescan9 ⎽ HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */ + { 0x09F4, 0x251C }, /* leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ + { 0x09F5, 0x2524 }, /* rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */ + { 0x09F6, 0x2534 }, /* bott ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL */ + { 0x09F7, 0x252C }, /* topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ + { 0x09F8, 0x2502 }, /* vertbar │ BOX DRAWINGS LIGHT VERTICAL */ + { 0x0AA1, 0x2003 }, /* emspace   EM SPACE */ + { 0x0AA2, 0x2002 }, /* enspace   EN SPACE */ + { 0x0AA3, 0x2004 }, /* em3space   THREE-PER-EM SPACE */ + { 0x0AA4, 0x2005 }, /* em4space   FOUR-PER-EM SPACE */ + { 0x0AA5, 0x2007 }, /* digitspace   FIGURE SPACE */ + { 0x0AA6, 0x2008 }, /* punctspace   PUNCTUATION SPACE */ + { 0x0AA7, 0x2009 }, /* thinspace   THIN SPACE */ + { 0x0AA8, 0x200A }, /* hairspace   HAIR SPACE */ + { 0x0AA9, 0x2014 }, /* emdash — EM DASH */ + { 0x0AAA, 0x2013 }, /* endash – EN DASH */ +/* 0x0AAC signifblank ? ??? */ + { 0x0AAE, 0x2026 }, /* ellipsis … HORIZONTAL ELLIPSIS */ + { 0x0AAF, 0x2025 }, /* doubbaselinedot ‥ TWO DOT LEADER */ + { 0x0AB0, 0x2153 }, /* onethird ⅓ VULGAR FRACTION ONE THIRD */ + { 0x0AB1, 0x2154 }, /* twothirds ⅔ VULGAR FRACTION TWO THIRDS */ + { 0x0AB2, 0x2155 }, /* onefifth ⅕ VULGAR FRACTION ONE FIFTH */ + { 0x0AB3, 0x2156 }, /* twofifths ⅖ VULGAR FRACTION TWO FIFTHS */ + { 0x0AB4, 0x2157 }, /* threefifths ⅗ VULGAR FRACTION THREE FIFTHS */ + { 0x0AB5, 0x2158 }, /* fourfifths ⅘ VULGAR FRACTION FOUR FIFTHS */ + { 0x0AB6, 0x2159 }, /* onesixth ⅙ VULGAR FRACTION ONE SIXTH */ + { 0x0AB7, 0x215A }, /* fivesixths ⅚ VULGAR FRACTION FIVE SIXTHS */ + { 0x0AB8, 0x2105 }, /* careof ℅ CARE OF */ + { 0x0ABB, 0x2012 }, /* figdash ‒ FIGURE DASH */ + { 0x0ABC, 0x2329 }, /* leftanglebracket 〈 LEFT-POINTING ANGLE BRACKET */ +/* 0x0ABD decimalpoint ? ??? */ + { 0x0ABE, 0x232A }, /* rightanglebracket 〉 RIGHT-POINTING ANGLE BRACKET */ +/* 0x0ABF marker ? ??? */ + { 0x0AC3, 0x215B }, /* oneeighth ⅛ VULGAR FRACTION ONE EIGHTH */ + { 0x0AC4, 0x215C }, /* threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */ + { 0x0AC5, 0x215D }, /* fiveeighths ⅝ VULGAR FRACTION FIVE EIGHTHS */ + { 0x0AC6, 0x215E }, /* seveneighths ⅞ VULGAR FRACTION SEVEN EIGHTHS */ + { 0x0AC9, 0x2122 }, /* trademark ™ TRADE MARK SIGN */ + { 0x0ACA, 0x2613 }, /* signaturemark ☓ SALTIRE */ +/* 0x0ACB trademarkincircle ? ??? */ + { 0x0ACC, 0x25C1 }, /* leftopentriangle ◁ WHITE LEFT-POINTING TRIANGLE */ + { 0x0ACD, 0x25B7 }, /* rightopentriangle ▷ WHITE RIGHT-POINTING TRIANGLE */ + { 0x0ACE, 0x25CB }, /* emopencircle ○ WHITE CIRCLE */ + { 0x0ACF, 0x25AF }, /* emopenrectangle ▯ WHITE VERTICAL RECTANGLE */ + { 0x0AD0, 0x2018 }, /* leftsinglequotemark ‘ LEFT SINGLE QUOTATION MARK */ + { 0x0AD1, 0x2019 }, /* rightsinglequotemark ’ RIGHT SINGLE QUOTATION MARK */ + { 0x0AD2, 0x201C }, /* leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */ + { 0x0AD3, 0x201D }, /* rightdoublequotemark ” RIGHT DOUBLE QUOTATION MARK */ + { 0x0AD4, 0x211E }, /* prescription ℞ PRESCRIPTION TAKE */ + { 0x0AD6, 0x2032 }, /* minutes ′ PRIME */ + { 0x0AD7, 0x2033 }, /* seconds ″ DOUBLE PRIME */ + { 0x0AD9, 0x271D }, /* latincross ✝ LATIN CROSS */ +/* 0x0ADA hexagram ? ??? */ + { 0x0ADB, 0x25AC }, /* filledrectbullet ▬ BLACK RECTANGLE */ + { 0x0ADC, 0x25C0 }, /* filledlefttribullet ◀ BLACK LEFT-POINTING TRIANGLE */ + { 0x0ADD, 0x25B6 }, /* filledrighttribullet ▶ BLACK RIGHT-POINTING TRIANGLE */ + { 0x0ADE, 0x25CF }, /* emfilledcircle ● BLACK CIRCLE */ + { 0x0ADF, 0x25AE }, /* emfilledrect ▮ BLACK VERTICAL RECTANGLE */ + { 0x0AE0, 0x25E6 }, /* enopencircbullet ◦ WHITE BULLET */ + { 0x0AE1, 0x25AB }, /* enopensquarebullet ▫ WHITE SMALL SQUARE */ + { 0x0AE2, 0x25AD }, /* openrectbullet ▭ WHITE RECTANGLE */ + { 0x0AE3, 0x25B3 }, /* opentribulletup △ WHITE UP-POINTING TRIANGLE */ + { 0x0AE4, 0x25BD }, /* opentribulletdown ▽ WHITE DOWN-POINTING TRIANGLE */ + { 0x0AE5, 0x2606 }, /* openstar ☆ WHITE STAR */ + { 0x0AE6, 0x2022 }, /* enfilledcircbullet • BULLET */ + { 0x0AE7, 0x25AA }, /* enfilledsqbullet ▪ BLACK SMALL SQUARE */ + { 0x0AE8, 0x25B2 }, /* filledtribulletup ▲ BLACK UP-POINTING TRIANGLE */ + { 0x0AE9, 0x25BC }, /* filledtribulletdown ▼ BLACK DOWN-POINTING TRIANGLE */ + { 0x0AEA, 0x261C }, /* leftpointer ☜ WHITE LEFT POINTING INDEX */ + { 0x0AEB, 0x261E }, /* rightpointer ☞ WHITE RIGHT POINTING INDEX */ + { 0x0AEC, 0x2663 }, /* club ♣ BLACK CLUB SUIT */ + { 0x0AED, 0x2666 }, /* diamond ♦ BLACK DIAMOND SUIT */ + { 0x0AEE, 0x2665 }, /* heart ♥ BLACK HEART SUIT */ + { 0x0AF0, 0x2720 }, /* maltesecross ✠ MALTESE CROSS */ + { 0x0AF1, 0x2020 }, /* dagger † DAGGER */ + { 0x0AF2, 0x2021 }, /* doubledagger ‡ DOUBLE DAGGER */ + { 0x0AF3, 0x2713 }, /* checkmark ✓ CHECK MARK */ + { 0x0AF4, 0x2717 }, /* ballotcross ✗ BALLOT X */ + { 0x0AF5, 0x266F }, /* musicalsharp ♯ MUSIC SHARP SIGN */ + { 0x0AF6, 0x266D }, /* musicalflat ♭ MUSIC FLAT SIGN */ + { 0x0AF7, 0x2642 }, /* malesymbol ♂ MALE SIGN */ + { 0x0AF8, 0x2640 }, /* femalesymbol ♀ FEMALE SIGN */ + { 0x0AF9, 0x260E }, /* telephone ☎ BLACK TELEPHONE */ + { 0x0AFA, 0x2315 }, /* telephonerecorder ⌕ TELEPHONE RECORDER */ + { 0x0AFB, 0x2117 }, /* phonographcopyright ℗ SOUND RECORDING COPYRIGHT */ + { 0x0AFC, 0x2038 }, /* caret ‸ CARET */ + { 0x0AFD, 0x201A }, /* singlelowquotemark ‚ SINGLE LOW-9 QUOTATION MARK */ + { 0x0AFE, 0x201E }, /* doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */ +/* 0x0AFF cursor ? ??? */ + { 0x0BA3, 0x003C }, /* leftcaret < LESS-THAN SIGN */ + { 0x0BA6, 0x003E }, /* rightcaret > GREATER-THAN SIGN */ + { 0x0BA8, 0x2228 }, /* downcaret ∨ LOGICAL OR */ + { 0x0BA9, 0x2227 }, /* upcaret ∧ LOGICAL AND */ + { 0x0BC0, 0x00AF }, /* overbar ¯ MACRON */ + { 0x0BC2, 0x22A5 }, /* downtack ⊥ UP TACK */ + { 0x0BC3, 0x2229 }, /* upshoe ∩ INTERSECTION */ + { 0x0BC4, 0x230A }, /* downstile ⌊ LEFT FLOOR */ + { 0x0BC6, 0x005F }, /* underbar _ LOW LINE */ + { 0x0BCA, 0x2218 }, /* jot ∘ RING OPERATOR */ + { 0x0BCC, 0x2395 }, /* quad ⎕ APL FUNCTIONAL SYMBOL QUAD */ + { 0x0BCE, 0x22A4 }, /* uptack ⊤ DOWN TACK */ + { 0x0BCF, 0x25CB }, /* circle ○ WHITE CIRCLE */ + { 0x0BD3, 0x2308 }, /* upstile ⌈ LEFT CEILING */ + { 0x0BD6, 0x222A }, /* downshoe ∪ UNION */ + { 0x0BD8, 0x2283 }, /* rightshoe ⊃ SUPERSET OF */ + { 0x0BDA, 0x2282 }, /* leftshoe ⊂ SUBSET OF */ + { 0x0BDC, 0x22A2 }, /* lefttack ⊢ RIGHT TACK */ + { 0x0BFC, 0x22A3 }, /* righttack ⊣ LEFT TACK */ + { 0x0CDF, 0x2017 }, /* hebrew_doublelowline ‗ DOUBLE LOW LINE */ + { 0x0CE0, 0x05D0 }, /* hebrew_aleph א HEBREW LETTER ALEF */ + { 0x0CE1, 0x05D1 }, /* hebrew_bet ב HEBREW LETTER BET */ + { 0x0CE2, 0x05D2 }, /* hebrew_gimel ג HEBREW LETTER GIMEL */ + { 0x0CE3, 0x05D3 }, /* hebrew_dalet ד HEBREW LETTER DALET */ + { 0x0CE4, 0x05D4 }, /* hebrew_he ה HEBREW LETTER HE */ + { 0x0CE5, 0x05D5 }, /* hebrew_waw ו HEBREW LETTER VAV */ + { 0x0CE6, 0x05D6 }, /* hebrew_zain ז HEBREW LETTER ZAYIN */ + { 0x0CE7, 0x05D7 }, /* hebrew_chet ח HEBREW LETTER HET */ + { 0x0CE8, 0x05D8 }, /* hebrew_tet ט HEBREW LETTER TET */ + { 0x0CE9, 0x05D9 }, /* hebrew_yod י HEBREW LETTER YOD */ + { 0x0CEA, 0x05DA }, /* hebrew_finalkaph ך HEBREW LETTER FINAL KAF */ + { 0x0CEB, 0x05DB }, /* hebrew_kaph כ HEBREW LETTER KAF */ + { 0x0CEC, 0x05DC }, /* hebrew_lamed ל HEBREW LETTER LAMED */ + { 0x0CED, 0x05DD }, /* hebrew_finalmem ם HEBREW LETTER FINAL MEM */ + { 0x0CEE, 0x05DE }, /* hebrew_mem מ HEBREW LETTER MEM */ + { 0x0CEF, 0x05DF }, /* hebrew_finalnun ן HEBREW LETTER FINAL NUN */ + { 0x0CF0, 0x05E0 }, /* hebrew_nun נ HEBREW LETTER NUN */ + { 0x0CF1, 0x05E1 }, /* hebrew_samech ס HEBREW LETTER SAMEKH */ + { 0x0CF2, 0x05E2 }, /* hebrew_ayin ע HEBREW LETTER AYIN */ + { 0x0CF3, 0x05E3 }, /* hebrew_finalpe ף HEBREW LETTER FINAL PE */ + { 0x0CF4, 0x05E4 }, /* hebrew_pe פ HEBREW LETTER PE */ + { 0x0CF5, 0x05E5 }, /* hebrew_finalzade ץ HEBREW LETTER FINAL TSADI */ + { 0x0CF6, 0x05E6 }, /* hebrew_zade צ HEBREW LETTER TSADI */ + { 0x0CF7, 0x05E7 }, /* hebrew_qoph ק HEBREW LETTER QOF */ + { 0x0CF8, 0x05E8 }, /* hebrew_resh ר HEBREW LETTER RESH */ + { 0x0CF9, 0x05E9 }, /* hebrew_shin ש HEBREW LETTER SHIN */ + { 0x0CFA, 0x05EA }, /* hebrew_taw ת HEBREW LETTER TAV */ + { 0x0DA1, 0x0E01 }, /* Thai_kokai ก THAI CHARACTER KO KAI */ + { 0x0DA2, 0x0E02 }, /* Thai_khokhai ข THAI CHARACTER KHO KHAI */ + { 0x0DA3, 0x0E03 }, /* Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */ + { 0x0DA4, 0x0E04 }, /* Thai_khokhwai ค THAI CHARACTER KHO KHWAI */ + { 0x0DA5, 0x0E05 }, /* Thai_khokhon ฅ THAI CHARACTER KHO KHON */ + { 0x0DA6, 0x0E06 }, /* Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */ + { 0x0DA7, 0x0E07 }, /* Thai_ngongu ง THAI CHARACTER NGO NGU */ + { 0x0DA8, 0x0E08 }, /* Thai_chochan จ THAI CHARACTER CHO CHAN */ + { 0x0DA9, 0x0E09 }, /* Thai_choching ฉ THAI CHARACTER CHO CHING */ + { 0x0DAA, 0x0E0A }, /* Thai_chochang ช THAI CHARACTER CHO CHANG */ + { 0x0DAB, 0x0E0B }, /* Thai_soso ซ THAI CHARACTER SO SO */ + { 0x0DAC, 0x0E0C }, /* Thai_chochoe ฌ THAI CHARACTER CHO CHOE */ + { 0x0DAD, 0x0E0D }, /* Thai_yoying ญ THAI CHARACTER YO YING */ + { 0x0DAE, 0x0E0E }, /* Thai_dochada ฎ THAI CHARACTER DO CHADA */ + { 0x0DAF, 0x0E0F }, /* Thai_topatak ฏ THAI CHARACTER TO PATAK */ + { 0x0DB0, 0x0E10 }, /* Thai_thothan ฐ THAI CHARACTER THO THAN */ + { 0x0DB1, 0x0E11 }, /* Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */ + { 0x0DB2, 0x0E12 }, /* Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */ + { 0x0DB3, 0x0E13 }, /* Thai_nonen ณ THAI CHARACTER NO NEN */ + { 0x0DB4, 0x0E14 }, /* Thai_dodek ด THAI CHARACTER DO DEK */ + { 0x0DB5, 0x0E15 }, /* Thai_totao ต THAI CHARACTER TO TAO */ + { 0x0DB6, 0x0E16 }, /* Thai_thothung ถ THAI CHARACTER THO THUNG */ + { 0x0DB7, 0x0E17 }, /* Thai_thothahan ท THAI CHARACTER THO THAHAN */ + { 0x0DB8, 0x0E18 }, /* Thai_thothong ธ THAI CHARACTER THO THONG */ + { 0x0DB9, 0x0E19 }, /* Thai_nonu น THAI CHARACTER NO NU */ + { 0x0DBA, 0x0E1A }, /* Thai_bobaimai บ THAI CHARACTER BO BAIMAI */ + { 0x0DBB, 0x0E1B }, /* Thai_popla ป THAI CHARACTER PO PLA */ + { 0x0DBC, 0x0E1C }, /* Thai_phophung ผ THAI CHARACTER PHO PHUNG */ + { 0x0DBD, 0x0E1D }, /* Thai_fofa ฝ THAI CHARACTER FO FA */ + { 0x0DBE, 0x0E1E }, /* Thai_phophan พ THAI CHARACTER PHO PHAN */ + { 0x0DBF, 0x0E1F }, /* Thai_fofan ฟ THAI CHARACTER FO FAN */ + { 0x0DC0, 0x0E20 }, /* Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */ + { 0x0DC1, 0x0E21 }, /* Thai_moma ม THAI CHARACTER MO MA */ + { 0x0DC2, 0x0E22 }, /* Thai_yoyak ย THAI CHARACTER YO YAK */ + { 0x0DC3, 0x0E23 }, /* Thai_rorua ร THAI CHARACTER RO RUA */ + { 0x0DC4, 0x0E24 }, /* Thai_ru ฤ THAI CHARACTER RU */ + { 0x0DC5, 0x0E25 }, /* Thai_loling ล THAI CHARACTER LO LING */ + { 0x0DC6, 0x0E26 }, /* Thai_lu ฦ THAI CHARACTER LU */ + { 0x0DC7, 0x0E27 }, /* Thai_wowaen ว THAI CHARACTER WO WAEN */ + { 0x0DC8, 0x0E28 }, /* Thai_sosala ศ THAI CHARACTER SO SALA */ + { 0x0DC9, 0x0E29 }, /* Thai_sorusi ษ THAI CHARACTER SO RUSI */ + { 0x0DCA, 0x0E2A }, /* Thai_sosua ส THAI CHARACTER SO SUA */ + { 0x0DCB, 0x0E2B }, /* Thai_hohip ห THAI CHARACTER HO HIP */ + { 0x0DCC, 0x0E2C }, /* Thai_lochula ฬ THAI CHARACTER LO CHULA */ + { 0x0DCD, 0x0E2D }, /* Thai_oang อ THAI CHARACTER O ANG */ + { 0x0DCE, 0x0E2E }, /* Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */ + { 0x0DCF, 0x0E2F }, /* Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */ + { 0x0DD0, 0x0E30 }, /* Thai_saraa ะ THAI CHARACTER SARA A */ + { 0x0DD1, 0x0E31 }, /* Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */ + { 0x0DD2, 0x0E32 }, /* Thai_saraaa า THAI CHARACTER SARA AA */ + { 0x0DD3, 0x0E33 }, /* Thai_saraam ำ THAI CHARACTER SARA AM */ + { 0x0DD4, 0x0E34 }, /* Thai_sarai ิ THAI CHARACTER SARA I */ + { 0x0DD5, 0x0E35 }, /* Thai_saraii ี THAI CHARACTER SARA II */ + { 0x0DD6, 0x0E36 }, /* Thai_saraue ึ THAI CHARACTER SARA UE */ + { 0x0DD7, 0x0E37 }, /* Thai_sarauee ื THAI CHARACTER SARA UEE */ + { 0x0DD8, 0x0E38 }, /* Thai_sarau ุ THAI CHARACTER SARA U */ + { 0x0DD9, 0x0E39 }, /* Thai_sarauu ู THAI CHARACTER SARA UU */ + { 0x0DDA, 0x0E3A }, /* Thai_phinthu ฺ THAI CHARACTER PHINTHU */ +/* 0x0DDE Thai_maihanakat_maitho ? ??? */ + { 0x0DDF, 0x0E3F }, /* Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */ + { 0x0DE0, 0x0E40 }, /* Thai_sarae เ THAI CHARACTER SARA E */ + { 0x0DE1, 0x0E41 }, /* Thai_saraae แ THAI CHARACTER SARA AE */ + { 0x0DE2, 0x0E42 }, /* Thai_sarao โ THAI CHARACTER SARA O */ + { 0x0DE3, 0x0E43 }, /* Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */ + { 0x0DE4, 0x0E44 }, /* Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */ + { 0x0DE5, 0x0E45 }, /* Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */ + { 0x0DE6, 0x0E46 }, /* Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */ + { 0x0DE7, 0x0E47 }, /* Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */ + { 0x0DE8, 0x0E48 }, /* Thai_maiek ่ THAI CHARACTER MAI EK */ + { 0x0DE9, 0x0E49 }, /* Thai_maitho ้ THAI CHARACTER MAI THO */ + { 0x0DEA, 0x0E4A }, /* Thai_maitri ๊ THAI CHARACTER MAI TRI */ + { 0x0DEB, 0x0E4B }, /* Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */ + { 0x0DEC, 0x0E4C }, /* Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */ + { 0x0DED, 0x0E4D }, /* Thai_nikhahit ํ THAI CHARACTER NIKHAHIT */ + { 0x0DF0, 0x0E50 }, /* Thai_leksun ๐ THAI DIGIT ZERO */ + { 0x0DF1, 0x0E51 }, /* Thai_leknung ๑ THAI DIGIT ONE */ + { 0x0DF2, 0x0E52 }, /* Thai_leksong ๒ THAI DIGIT TWO */ + { 0x0DF3, 0x0E53 }, /* Thai_leksam ๓ THAI DIGIT THREE */ + { 0x0DF4, 0x0E54 }, /* Thai_leksi ๔ THAI DIGIT FOUR */ + { 0x0DF5, 0x0E55 }, /* Thai_lekha ๕ THAI DIGIT FIVE */ + { 0x0DF6, 0x0E56 }, /* Thai_lekhok ๖ THAI DIGIT SIX */ + { 0x0DF7, 0x0E57 }, /* Thai_lekchet ๗ THAI DIGIT SEVEN */ + { 0x0DF8, 0x0E58 }, /* Thai_lekpaet ๘ THAI DIGIT EIGHT */ + { 0x0DF9, 0x0E59 }, /* Thai_lekkao ๙ THAI DIGIT NINE */ + { 0x0EA1, 0x3131 }, /* Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */ + { 0x0EA2, 0x3132 }, /* Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */ + { 0x0EA3, 0x3133 }, /* Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */ + { 0x0EA4, 0x3134 }, /* Hangul_Nieun ㄴ HANGUL LETTER NIEUN */ + { 0x0EA5, 0x3135 }, /* Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */ + { 0x0EA6, 0x3136 }, /* Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */ + { 0x0EA7, 0x3137 }, /* Hangul_Dikeud ㄷ HANGUL LETTER TIKEUT */ + { 0x0EA8, 0x3138 }, /* Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */ + { 0x0EA9, 0x3139 }, /* Hangul_Rieul ㄹ HANGUL LETTER RIEUL */ + { 0x0EAA, 0x313A }, /* Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */ + { 0x0EAB, 0x313B }, /* Hangul_RieulMieum ㄻ HANGUL LETTER RIEUL-MIEUM */ + { 0x0EAC, 0x313C }, /* Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */ + { 0x0EAD, 0x313D }, /* Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */ + { 0x0EAE, 0x313E }, /* Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */ + { 0x0EAF, 0x313F }, /* Hangul_RieulPhieuf ㄿ HANGUL LETTER RIEUL-PHIEUPH */ + { 0x0EB0, 0x3140 }, /* Hangul_RieulHieuh ㅀ HANGUL LETTER RIEUL-HIEUH */ + { 0x0EB1, 0x3141 }, /* Hangul_Mieum ㅁ HANGUL LETTER MIEUM */ + { 0x0EB2, 0x3142 }, /* Hangul_Pieub ㅂ HANGUL LETTER PIEUP */ + { 0x0EB3, 0x3143 }, /* Hangul_SsangPieub ㅃ HANGUL LETTER SSANGPIEUP */ + { 0x0EB4, 0x3144 }, /* Hangul_PieubSios ㅄ HANGUL LETTER PIEUP-SIOS */ + { 0x0EB5, 0x3145 }, /* Hangul_Sios ㅅ HANGUL LETTER SIOS */ + { 0x0EB6, 0x3146 }, /* Hangul_SsangSios ㅆ HANGUL LETTER SSANGSIOS */ + { 0x0EB7, 0x3147 }, /* Hangul_Ieung ㅇ HANGUL LETTER IEUNG */ + { 0x0EB8, 0x3148 }, /* Hangul_Jieuj ㅈ HANGUL LETTER CIEUC */ + { 0x0EB9, 0x3149 }, /* Hangul_SsangJieuj ㅉ HANGUL LETTER SSANGCIEUC */ + { 0x0EBA, 0x314A }, /* Hangul_Cieuc ㅊ HANGUL LETTER CHIEUCH */ + { 0x0EBB, 0x314B }, /* Hangul_Khieuq ㅋ HANGUL LETTER KHIEUKH */ + { 0x0EBC, 0x314C }, /* Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */ + { 0x0EBD, 0x314D }, /* Hangul_Phieuf ㅍ HANGUL LETTER PHIEUPH */ + { 0x0EBE, 0x314E }, /* Hangul_Hieuh ㅎ HANGUL LETTER HIEUH */ + { 0x0EBF, 0x314F }, /* Hangul_A ㅏ HANGUL LETTER A */ + { 0x0EC0, 0x3150 }, /* Hangul_AE ㅐ HANGUL LETTER AE */ + { 0x0EC1, 0x3151 }, /* Hangul_YA ㅑ HANGUL LETTER YA */ + { 0x0EC2, 0x3152 }, /* Hangul_YAE ㅒ HANGUL LETTER YAE */ + { 0x0EC3, 0x3153 }, /* Hangul_EO ㅓ HANGUL LETTER EO */ + { 0x0EC4, 0x3154 }, /* Hangul_E ㅔ HANGUL LETTER E */ + { 0x0EC5, 0x3155 }, /* Hangul_YEO ㅕ HANGUL LETTER YEO */ + { 0x0EC6, 0x3156 }, /* Hangul_YE ㅖ HANGUL LETTER YE */ + { 0x0EC7, 0x3157 }, /* Hangul_O ㅗ HANGUL LETTER O */ + { 0x0EC8, 0x3158 }, /* Hangul_WA ㅘ HANGUL LETTER WA */ + { 0x0EC9, 0x3159 }, /* Hangul_WAE ㅙ HANGUL LETTER WAE */ + { 0x0ECA, 0x315A }, /* Hangul_OE ㅚ HANGUL LETTER OE */ + { 0x0ECB, 0x315B }, /* Hangul_YO ㅛ HANGUL LETTER YO */ + { 0x0ECC, 0x315C }, /* Hangul_U ㅜ HANGUL LETTER U */ + { 0x0ECD, 0x315D }, /* Hangul_WEO ㅝ HANGUL LETTER WEO */ + { 0x0ECE, 0x315E }, /* Hangul_WE ㅞ HANGUL LETTER WE */ + { 0x0ECF, 0x315F }, /* Hangul_WI ㅟ HANGUL LETTER WI */ + { 0x0ED0, 0x3160 }, /* Hangul_YU ㅠ HANGUL LETTER YU */ + { 0x0ED1, 0x3161 }, /* Hangul_EU ㅡ HANGUL LETTER EU */ + { 0x0ED2, 0x3162 }, /* Hangul_YI ㅢ HANGUL LETTER YI */ + { 0x0ED3, 0x3163 }, /* Hangul_I ㅣ HANGUL LETTER I */ + { 0x0ED4, 0x11A8 }, /* Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */ + { 0x0ED5, 0x11A9 }, /* Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */ + { 0x0ED6, 0x11AA }, /* Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */ + { 0x0ED7, 0x11AB }, /* Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */ + { 0x0ED8, 0x11AC }, /* Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */ + { 0x0ED9, 0x11AD }, /* Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */ + { 0x0EDA, 0x11AE }, /* Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */ + { 0x0EDB, 0x11AF }, /* Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */ + { 0x0EDC, 0x11B0 }, /* Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */ + { 0x0EDD, 0x11B1 }, /* Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */ + { 0x0EDE, 0x11B2 }, /* Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */ + { 0x0EDF, 0x11B3 }, /* Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */ + { 0x0EE0, 0x11B4 }, /* Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */ + { 0x0EE1, 0x11B5 }, /* Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */ + { 0x0EE2, 0x11B6 }, /* Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */ + { 0x0EE3, 0x11B7 }, /* Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */ + { 0x0EE4, 0x11B8 }, /* Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */ + { 0x0EE5, 0x11B9 }, /* Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */ + { 0x0EE6, 0x11BA }, /* Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */ + { 0x0EE7, 0x11BB }, /* Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */ + { 0x0EE8, 0x11BC }, /* Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */ + { 0x0EE9, 0x11BD }, /* Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */ + { 0x0EEA, 0x11BE }, /* Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */ + { 0x0EEB, 0x11BF }, /* Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */ + { 0x0EEC, 0x11C0 }, /* Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */ + { 0x0EED, 0x11C1 }, /* Hangul_J_Phieuf ᇁ HANGUL JONGSEONG PHIEUPH */ + { 0x0EEE, 0x11C2 }, /* Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */ + { 0x0EEF, 0x316D }, /* Hangul_RieulYeorinHieuh ㅭ HANGUL LETTER RIEUL-YEORINHIEUH */ + { 0x0EF0, 0x3171 }, /* Hangul_SunkyeongeumMieum ㅱ HANGUL LETTER KAPYEOUNMIEUM */ + { 0x0EF1, 0x3178 }, /* Hangul_SunkyeongeumPieub ㅸ HANGUL LETTER KAPYEOUNPIEUP */ + { 0x0EF2, 0x317F }, /* Hangul_PanSios ㅿ HANGUL LETTER PANSIOS */ + { 0x0EF3, 0x3181 }, /* Hangul_KkogjiDalrinIeung ㆁ HANGUL LETTER YESIEUNG */ + { 0x0EF4, 0x3184 }, /* Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */ + { 0x0EF5, 0x3186 }, /* Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */ + { 0x0EF6, 0x318D }, /* Hangul_AraeA ㆍ HANGUL LETTER ARAEA */ + { 0x0EF7, 0x318E }, /* Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */ + { 0x0EF8, 0x11EB }, /* Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */ + { 0x0EF9, 0x11F0 }, /* Hangul_J_KkogjiDalrinIeung ᇰ HANGUL JONGSEONG YESIEUNG */ + { 0x0EFA, 0x11F9 }, /* Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */ + { 0x0EFF, 0x20A9 }, /* Korean_Won ₩ WON SIGN */ + { 0x13A4, 0x20AC }, /* Euro € EURO SIGN */ + { 0x13BC, 0x0152 }, /* OE Œ LATIN CAPITAL LIGATURE OE */ + { 0x13BD, 0x0153 }, /* oe œ LATIN SMALL LIGATURE OE */ + { 0x13BE, 0x0178 }, /* Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */ + { 0x20AC, 0x20AC }, /* EuroSign € EURO SIGN */ +}; + +/*********************************************************************** + * The following function converts ISO 10646-1 (UCS, Unicode) values to + * their corresponding KeySym values. + * + * The UTF-8 -> keysym conversion will hopefully one day be provided by + * Xlib via XmbLookupString() and should ideally not have to be + * done in X applications. But we are not there yet. + * + * Author: Markus G. Kuhn , University of Cambridge, + * June 1999 + * + * Special thanks to Richard Verhoeven for preparing + * an initial draft of the mapping table. + * + * This software is in the public domain. Share and enjoy! + ***********************************************************************/ +KeySym unicode_to_keysym(uint16_t unicode) { + int min = 0; + int max = sizeof(keysym_unicode_table) / sizeof(struct codepair) - 1; + int mid; + + #ifdef XK_LATIN1 + // First check for Latin-1 characters. (1:1 mapping) + if ((unicode >= 0x0020 && unicode <= 0x007E) || + (unicode >= 0x00A0 && unicode <= 0x00FF)) { + return unicode; + } + #endif + + // Binary search the lookup table. + while (max >= min) { + mid = (min + max) / 2; + if (keysym_unicode_table[mid].unicode < unicode) { + min = mid + 1; + } + else if (keysym_unicode_table[mid].unicode > unicode) { + max = mid - 1; + } + else { + // Found it. + return keysym_unicode_table[mid].keysym; + } + } + + // No matching KeySym value found, return UCS2 with bit set. + return unicode | 0x01000000; +} + + +/*********************************************************************** + * The following function converts KeySym values into the corresponding + * ISO 10646-1 (UCS, Unicode) values. + * + * The keysym -> UTF-8 conversion will hopefully one day be provided by + * Xlib via XLookupKeySym and should ideally not have to be done in X + * applications. But we are not there yet. + * + * Author: Markus G. Kuhn , University of Cambridge, + * June 1999 + * + * Special thanks to Richard Verhoeven for preparing + * an initial draft of the mapping table. + * + * This software is in the public domain. Share and enjoy! + ***********************************************************************/ +size_t keysym_to_unicode(KeySym keysym, uint16_t *buffer, size_t size) { + size_t count = 0; + + int min = 0; + int max = sizeof(keysym_unicode_table) / sizeof(struct codepair) - 1; + int mid; + + #ifdef XK_LATIN1 + // First check for Latin-1 characters. (1:1 mapping) + if ((keysym >= 0x0020 && keysym <= 0x007E) + || (keysym >= 0x00A0 && keysym <= 0x00FF)) { + + if (count < size) { + buffer[count++] = keysym; + } + + return count; + } + #endif + + // Also check for directly encoded 24-bit UCS characters. + #if defined(XK_LATIN8) || defined(XK_ARABIC) || defined(XK_CYRILLIC) || \ + defined(XK_ARMENIAN) || defined(XK_GEORGIAN) || defined(XK_CAUCASUS) || \ + defined(XK_VIETNAMESE) || defined(XK_CURRENCY) || \ + defined(XK_MATHEMATICAL) || defined(XK_BRAILLE) || defined(XK_SINHALA) + if ((keysym & 0xFF000000) == 0x01000000) { + if (count < size) { + buffer[count++] = keysym & 0x00FFFFFF; + } + + return count; + } + #endif + + // Binary search in table. + while (max >= min) { + mid = (min + max) / 2; + if (keysym_unicode_table[mid].keysym < keysym) { + min = mid + 1; + } + else if (keysym_unicode_table[mid].keysym > keysym) { + max = mid - 1; + } + else { + // Found it. + if (count < size) { + buffer[count++] = keysym_unicode_table[mid].unicode; + } + + return count; + } + } + + // No matching Unicode value found! + return count; +} + + +/* The following code is based on vncdisplaykeymap.c under the following terms: + * + * Copyright (C) 2008 Anthony Liguori + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2 as + * published by the Free Software Foundation. + */ +uint16_t keycode_to_scancode(KeyCode keycode) { + uint16_t scancode = VC_UNDEFINED; + + #if defined(USE_EVDEV) && defined(USE_XKB) + // Check to see if evdev is enabled. + if (is_evdev) { + unsigned short evdev_size = sizeof(evdev_scancode_table) / sizeof(evdev_scancode_table[0]); + + // NOTE scancodes < 97 appear to be identical between Evdev and XFree86. + if (keycode < evdev_size) { + // For scancode < 97, a simple scancode - 8 offest could be applied, + // but math is generally slower than memory and we cannot save any + // extra space in the lookup table due to binary padding. + scancode = evdev_scancode_table[keycode][0]; + } + } + else { + // Evdev was disabled, fallback to XFree86. + #endif + unsigned short xfree86_size = sizeof(xfree86_scancode_table) / sizeof(xfree86_scancode_table[0]); + + // NOTE scancodes < 97 appear to be identical between Evdev and XFree86. + if (keycode < xfree86_size) { + // For scancode < 97, a simple scancode - 8 offest could be applied, + // but math is generally slower than memory and we cannot save any + // extra space in the lookup table due to binary padding. + scancode = xfree86_scancode_table[keycode][0]; + } + #if defined(USE_EVDEV) && defined(USE_XKB) + } + #endif + + return scancode; +} + +KeyCode scancode_to_keycode(uint16_t scancode) { + KeyCode keycode = 0x0000; + + #if defined(USE_EVDEV) && defined(USE_XKB) + // Check to see if Evdev is enabled. + if (is_evdev) { + unsigned short evdev_size = sizeof(evdev_scancode_table) / sizeof(evdev_scancode_table[0]); + + // NOTE scancodes < 97 appear to be identical between Evdev and XFree86. + if (scancode < 128) { + // For scancode < 97, a simple scancode + 8 offest could be applied, + // but math is generally slower than memory and we cannot save any + // extra space in the lookup table due to binary padding. + keycode = evdev_scancode_table[scancode][1]; + } + else { + // Offset is the lower order bits + 128 + scancode = (scancode & 0x007F) | 0x80; + + if (scancode < evdev_size) { + keycode = evdev_scancode_table[scancode][1]; + } + } + } + else { + // Evdev was disabled, fallback to XFree86. + #endif + unsigned short xfree86_size = sizeof(xfree86_scancode_table) / sizeof(xfree86_scancode_table[0]); + + // NOTE scancodes < 97 appear to be identical between Evdev and XFree86. + if (scancode < 128) { + // For scancode < 97, a simple scancode + 8 offest could be applied, + // but math is generally slower than memory and we cannot save any + // extra space in the lookup table due to binary padding. + keycode = xfree86_scancode_table[scancode][1]; + } + else { + // Offset: lower order bits + 128 (If no size optimization!) + scancode = (scancode & 0x007F) | 0x80; + + if (scancode < xfree86_size) { + keycode = xfree86_scancode_table[scancode][1]; + } + } + #if defined(USE_EVDEV) && defined(USE_XKB) + } + #endif + + return keycode; +} + +#ifdef USE_XKBCOMMON +struct xkb_state * create_xkb_state(struct xkb_context *context, xcb_connection_t *connection) { + struct xkb_keymap *keymap; + struct xkb_state *state; + + int32_t device_id = xkb_x11_get_core_keyboard_device_id(connection); + if (device_id >= 0) { + keymap = xkb_x11_keymap_new_from_device(context, connection, device_id, XKB_KEYMAP_COMPILE_NO_FLAGS); + state = xkb_x11_state_new_from_device(keymap, connection, device_id); + } + #ifdef USE_XKBFILE + else { + // Evdev fallback, + logger(LOG_LEVEL_WARN, "%s [%u]: Unable to retrieve core keyboard device id! (%d)\n", + __FUNCTION__, __LINE__, device_id); + + keymap = xkb_keymap_new_from_names(context, &xkb_names, XKB_KEYMAP_COMPILE_NO_FLAGS); + state = xkb_state_new(keymap); + } + #endif + + xkb_map_unref(keymap); + return xkb_state_ref(state); +} + +void destroy_xkb_state(struct xkb_state* state) { + xkb_state_unref(state); +} + +size_t keycode_to_unicode(struct xkb_state* state, KeyCode keycode, uint16_t *buffer, size_t length) { + size_t count = 0; + + if (state != NULL) { + uint32_t unicode = xkb_state_key_get_utf32(state, keycode); + + if (unicode <= 0x10FFFF) { + if ((unicode <= 0xD7FF || (unicode >= 0xE000 && unicode <= 0xFFFF)) && length >= 1) { + buffer[0] = unicode; + count = 1; + } + else if (unicode >= 0x10000) { + unsigned int code = (unicode - 0x10000); + buffer[0] = 0xD800 | (code >> 10); + buffer[1] = 0xDC00 | (code & 0x3FF); + count = 2; + } + } + } + + return count; +} +#else +// Faster more flexible alternative to XKeycodeToKeysym... +KeySym keycode_to_keysym(KeyCode keycode, unsigned int modifier_mask) { + KeySym keysym = NoSymbol; + + #ifdef USE_XKB + if (keyboard_map != NULL) { + // Get the range and number of symbols groups bound to the key. + unsigned char info = XkbKeyGroupInfo(keyboard_map, keycode); + unsigned int num_groups = XkbKeyNumGroups(keyboard_map, keycode); + + // Get the group. + unsigned int group = 0x0000; + switch (XkbOutOfRangeGroupAction(info)) { + case XkbRedirectIntoRange: + /* If the RedirectIntoRange flag is set, the four least significant + * bits of the groups wrap control specify the index of a group to + * which all illegal groups correspond. If the specified group is + * also out of range, all illegal groups map to Group1. + */ + group = XkbOutOfRangeGroupInfo(info); + if (group >= num_groups) { + group = 0; + } + break; + + case XkbClampIntoRange: + /* If the ClampIntoRange flag is set, out-of-range groups correspond + * to the nearest legal group. Effective groups larger than the + * highest supported group are mapped to the highest supported group; + * effective groups less than Group1 are mapped to Group1 . For + * example, a key with two groups of symbols uses Group2 type and + * symbols if the global effective group is either Group3 or Group4. + */ + group = num_groups - 1; + break; + + case XkbWrapIntoRange: + /* If neither flag is set, group is wrapped into range using integer + * modulus. For example, a key with two groups of symbols for which + * groups wrap uses Group1 symbols if the global effective group is + * Group3 or Group2 symbols if the global effective group is Group4. + */ + default: + if (num_groups != 0) { + group %= num_groups; + } + break; + } + + XkbKeyTypePtr key_type = XkbKeyKeyType(keyboard_map, keycode, group); + unsigned int active_mods = modifier_mask & key_type->mods.mask; + + int i, level = 0; + for (i = 0; i < key_type->map_count; i++) { + if (key_type->map[i].active && key_type->map[i].mods.mask == active_mods) { + level = key_type->map[i].level; + } + } + + keysym = XkbKeySymEntry(keyboard_map, keycode, level, group); + } + #else + if (keyboard_map != NULL) { + if (modifier_mask & Mod2Mask && + ((keyboard_map[keycode *keysym_per_keycode + 1] >= 0xFF80 && keyboard_map[keycode *keysym_per_keycode + 1] <= 0xFFBD) || + (keyboard_map[keycode *keysym_per_keycode + 1] >= 0x11000000 && keyboard_map[keycode *keysym_per_keycode + 1] <= 0x1100FFFF)) + ) { + + /* If the numlock modifier is on and the second KeySym is a keypad + * KeySym. In this case, if the Shift modifier is on, or if the + * Lock modifier is on and is interpreted as ShiftLock, then the + * first KeySym is used, otherwise the second KeySym is used. + * + * The standard KeySyms with the prefix ``XK_KP_'' in their name are + * called keypad KeySyms; these are KeySyms with numeric value in + * the hexadecimal range 0xFF80 to 0xFFBD inclusive. In addition, + * vendor-specific KeySyms in the hexadecimal range 0x11000000 to + * 0x1100FFFF are also keypad KeySyms. + */ + + + /* The numlock modifier is on and the second KeySym is a keypad + * KeySym. In this case, if the Shift modifier is on, or if the + * Lock modifier is on and is interpreted as ShiftLock, then the + * first KeySym is used, otherwise the second KeySym is used. + */ + if (modifier_mask & ShiftMask || (modifier_mask & LockMask && is_shift_lock)) { + // i = 0 + keysym = keyboard_map[keycode *keysym_per_keycode]; + } + else { + // i = 1 + keysym = keyboard_map[keycode *keysym_per_keycode + 1]; + } + } + else if (modifier_mask ^ ShiftMask && modifier_mask ^ LockMask) { + /* The Shift and Lock modifiers are both off. In this case, + * the first KeySym is used. + */ + // index = 0 + keysym = keyboard_map[keycode *keysym_per_keycode]; + } + else if (modifier_mask ^ ShiftMask && modifier_mask & LockMask && is_caps_lock) { + /* The Shift modifier is off, and the Lock modifier is on + * and is interpreted as CapsLock. In this case, the first + * KeySym is used, but if that KeySym is lowercase + * alphabetic, then the corresponding uppercase KeySym is + * used instead. + */ + // index = 0; + keysym = keyboard_map[keycode *keysym_per_keycode]; + + if (keysym >= 'a' && keysym <= 'z') { + // keysym is an alpha char. + KeySym lower_keysym, upper_keysym; + XConvertCase(keysym, &lower_keysym, &upper_keysym); + keysym = upper_keysym; + } + } + else if (modifier_mask & ShiftMask && modifier_mask & LockMask && is_caps_lock) { + /* The Shift modifier is on, and the Lock modifier is on and + * is interpreted as CapsLock. In this case, the second + * KeySym is used, but if that KeySym is lowercase + * alphabetic, then the corresponding uppercase KeySym is + * used instead. + */ + // index = 1 + keysym = keyboard_map[keycode *keysym_per_keycode + 1]; + + if (keysym >= 'A' && keysym <= 'Z') { + // keysym is an alpha char. + KeySym lower_keysym, upper_keysym; + XConvertCase(keysym, &lower_keysym, &upper_keysym); + keysym = lower_keysym; + } + } + else if (modifier_mask & ShiftMask || (modifier_mask & LockMask && is_shift_lock) || modifier_mask & (ShiftMask + LockMask)) { + /* The Shift modifier is on, or the Lock modifier is on and + * is interpreted as ShiftLock, or both. In this case, the + * second KeySym is used. + */ + // index = 1 + keysym = keyboard_map[keycode *keysym_per_keycode + 1]; + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Unable to determine the KeySym index!\n", + __FUNCTION__, __LINE__); + } + } + #endif + + return keysym; +} +#endif + +void load_input_helper(Display *disp) { + #ifdef USE_XKB + /* The following code block is based on vncdisplaykeymap.c under the terms: + * + * Copyright (C) 2008 Anthony Liguori + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2 as + * published by the Free Software Foundation. + */ + XkbDescPtr desc = XkbGetKeyboard(disp, XkbGBN_AllComponentsMask, XkbUseCoreKbd); + if (desc != NULL && desc->names != NULL) { + const char *layout_name = XGetAtomName(disp, desc->names->keycodes); + logger(LOG_LEVEL_DEBUG, + "%s [%u]: Found keycode atom '%s' (%i)!\n", + __FUNCTION__, __LINE__, layout_name, + (unsigned int) desc->names->keycodes); + + const char *prefix_xfree86 = "xfree86_"; + #if defined(USE_EVDEV) && defined(USE_XKB) + const char *prefix_evdev = "evdev_"; + if (strncmp(layout_name, prefix_evdev, strlen(prefix_evdev)) == 0) { + is_evdev = true; + } else + #endif + if (strncmp(layout_name, prefix_xfree86, strlen(prefix_xfree86)) != 0) { + // logger(LOG_LEVEL_ERROR, + // "%s [%u]: Unknown keycode name '%s', please file a bug report!\n", + // __FUNCTION__, __LINE__, layout_name); + } + else if (layout_name == NULL) { + logger(LOG_LEVEL_ERROR, + "%s [%u]: X atom name failure for desc->names->keycodes!\n", + __FUNCTION__, __LINE__); + } + + XkbFreeClientMap(desc, XkbGBN_AllComponentsMask, True); + } + else { + logger(LOG_LEVEL_ERROR, + "%s [%u]: XkbGetKeyboard failed to locate a valid keyboard!\n", + __FUNCTION__, __LINE__); + } + + // Get the map. + keyboard_map = XkbGetMap(disp, XkbAllClientInfoMask, XkbUseCoreKbd); + #else + // No known alternative to determine scancode mapping, assume XFree86! + // printf("%s\n", "No known alternative to determine scancode mapping, assume XFree86!"); + // #pragma message("*** Warning: XKB support is required to accurately determine keyboard scancodes!") + // #pragma message("... Assuming XFree86 keyboard layout.") + + logger(LOG_LEVEL_WARN, "%s [%u]: Using XFree86 keyboard layout.\n", + __FUNCTION__, __LINE__); + logger(LOG_LEVEL_WARN, "%s [%u]: XKB support is required to accurately determine keyboard characters!\n", + __FUNCTION__, __LINE__); + + int minKeyCode, maxKeyCode; + XDisplayKeycodes(disp, &minKeyCode, &maxKeyCode); + + keyboard_map = XGetKeyboardMapping(disp, minKeyCode, (maxKeyCode - minKeyCode + 1), &keysym_per_keycode); + if (keyboard_map) { + XModifierKeymap *modifierMap = XGetModifierMapping(disp); + + if (modifierMap) { + /* The Lock modifier is interpreted as CapsLock when the KeySym + * named XK_Caps_Lock is attached to some KeyCode and that KeyCode + * is attached to the Lock modifier. The Lock modifier is + * interpreted as ShiftLock when the KeySym named XK_Shift_Lock is + * attached to some KeyCode and that KeyCode is attached to the Lock + * modifier. If the Lock modifier could be interpreted as both + * CapsLock and ShiftLock, the CapsLock interpretation is used. + */ + + KeyCode capsLock = XKeysymToKeycode(disp, XK_Caps_Lock); + KeyCode shiftLock = XKeysymToKeycode(disp, XK_Shift_Lock); + keysym_per_keycode--; + + // Loop over the modifier map to find out if/where shift and caps locks are set. + int i; + for (i = LockMapIndex; i < LockMapIndex + modifierMap->max_keypermod && !is_caps_lock; i++) { + if (capsLock != 0 && modifierMap->modifiermap[i] == capsLock) { + is_caps_lock = true; + is_shift_lock = false; + } + else if (shiftLock != 0 && modifierMap->modifiermap[i] == shiftLock) { + is_shift_lock = true; + } + } + + XFree(modifierMap); + } + else { + XFree(keyboard_map); + + logger(LOG_LEVEL_ERROR, + "%s [%u]: Unable to get modifier mapping table!\n", + __FUNCTION__, __LINE__); + } + } + else { + logger(LOG_LEVEL_ERROR, + "%s [%u]: Unable to get keyboard mapping table!\n", + __FUNCTION__, __LINE__); + } + #endif +} + +void unload_input_helper() { + if (keyboard_map) { + #ifdef USE_XKB + XkbFreeClientMap(keyboard_map, XkbAllClientInfoMask, true); + #if defined(USE_EVDEV) && defined(USE_XKB) + is_evdev = false; + #endif + #else + XFree(keyboard_map); + #endif + } +} diff --git a/event/hook/x11/properties_c.h b/event/hook/x11/properties_c.h new file mode 100644 index 0000000..9358143 --- /dev/null +++ b/event/hook/x11/properties_c.h @@ -0,0 +1,499 @@ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#ifdef USE_XKB + #include +#endif +#ifdef USE_XF86MISC + #include + #include +#endif +#if defined(USE_XINERAMA) && !defined(USE_XRANDR) + #include +#elif defined(USE_XRANDR) +#include + #include +#endif +#ifdef USE_XT + #include + + static XtAppContext xt_context; + static Display *xt_disp; +#endif + +#include "../iohook.h" +#include "input.h" +// #include "../logger.h" + +Display *properties_disp; + +#ifdef USE_XRANDR +static pthread_mutex_t xrandr_mutex = PTHREAD_MUTEX_INITIALIZER; +static XRRScreenResources *xrandr_resources = NULL; + +static void settings_cleanup_proc(void *arg) { + if (pthread_mutex_trylock(&xrandr_mutex) == 0) { + if (xrandr_resources != NULL) { + XRRFreeScreenResources(xrandr_resources); + xrandr_resources = NULL; + } + + if (arg != NULL) { + XCloseDisplay((Display *) arg); + arg = NULL; + } + + pthread_mutex_unlock(&xrandr_mutex); + } +} + +static void *settings_thread_proc(void *arg) { + Display *settings_disp = XOpenDisplay(XDisplayName(NULL));; + if (settings_disp != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay success."); + + pthread_cleanup_push(settings_cleanup_proc, settings_disp); + + int event_base = 0; + int error_base = 0; + if (XRRQueryExtension(settings_disp, &event_base, &error_base)) { + Window root = XDefaultRootWindow(settings_disp); + unsigned long event_mask = RRScreenChangeNotifyMask; + XRRSelectInput(settings_disp, root, event_mask); + + XEvent ev; + + while(settings_disp != NULL) { + XNextEvent(settings_disp, &ev); + + if (ev.type == event_base + RRScreenChangeNotifyMask) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Received XRRScreenChangeNotifyEvent.\n", + __FUNCTION__, __LINE__); + + pthread_mutex_lock(&xrandr_mutex); + if (xrandr_resources != NULL) { + XRRFreeScreenResources(xrandr_resources); + } + + xrandr_resources = XRRGetScreenResources(settings_disp, root); + if (xrandr_resources == NULL) { + logger(LOG_LEVEL_WARN, "%s [%u]: XRandR could not get screen resources!\n", + __FUNCTION__, __LINE__); + } + pthread_mutex_unlock(&xrandr_mutex); + } + else { + logger(LOG_LEVEL_WARN, "%s [%u]: XRandR is not currently available!\n", + __FUNCTION__, __LINE__); + } + } + } + + // Execute the thread cleanup handler. + pthread_cleanup_pop(1); + + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: XOpenDisplay failure!\n", + __FUNCTION__, __LINE__); + } + + return NULL; +} +#endif + +IOHOOK_API screen_data* hook_create_screen_info(unsigned char *count) { + *count = 0; + screen_data *screens = NULL; + + #if defined(USE_XINERAMA) && !defined(USE_XRANDR) + if (XineramaIsActive(properties_disp)) { + int xine_count = 0; + XineramaScreenInfo *xine_info = XineramaQueryScreens(properties_disp, &xine_count); + + if (xine_info != NULL) { + if (xine_count > UINT8_MAX) { + *count = UINT8_MAX; + + logger(LOG_LEVEL_WARN, "%s [%u]: Screen count overflow detected!\n", + __FUNCTION__, __LINE__); + } + else { + *count = (uint8_t) xine_count; + } + + screens = malloc(sizeof(screen_data) * xine_count); + + if (screens != NULL) { + int i; + for (i = 0; i < xine_count; i++) { + screens[i] = (screen_data) { + .number = xine_info[i].screen_number, + .x = xine_info[i].x_org, + .y = xine_info[i].y_org, + .width = xine_info[i].width, + .height = xine_info[i].height + }; + } + } + + XFree(xine_info); + } + } + #elif defined(USE_XRANDR) + pthread_mutex_lock(&xrandr_mutex); + if (xrandr_resources != NULL) { + int xrandr_count = xrandr_resources->ncrtc; + if (xrandr_count > UINT8_MAX) { + *count = UINT8_MAX; + + logger(LOG_LEVEL_WARN, "%s [%u]: Screen count overflow detected!\n", + __FUNCTION__, __LINE__); + } + else { + *count = (uint8_t) xrandr_count; + } + + screens = malloc(sizeof(screen_data) * xrandr_count); + + if (screens != NULL) { + int i; + for (i = 0; i < xrandr_count; i++) { + XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(properties_disp, xrandr_resources, xrandr_resources->crtcs[i]); + + if (crtc_info != NULL) { + screens[i] = (screen_data) { + .number = i + 1, + .x = crtc_info->x, + .y = crtc_info->y, + .width = crtc_info->width, + .height = crtc_info->height + }; + + XRRFreeCrtcInfo(crtc_info); + } + else { + logger(LOG_LEVEL_WARN, "%s [%u]: XRandr failed to return crtc information! (%#X)\n", + __FUNCTION__, __LINE__, xrandr_resources->crtcs[i]); + } + } + } + } + pthread_mutex_unlock(&xrandr_mutex); + #else + Screen* default_screen = DefaultScreenOfDisplay(properties_disp); + + if (default_screen->width > 0 && default_screen->height > 0) { + screens = malloc(sizeof(screen_data)); + + if (screens != NULL) { + *count = 1; + screens[0] = (screen_data) { + .number = 1, + .x = 0, + .y = 0, + .width = default_screen->width, + .height = default_screen->height + }; + } + } + #endif + + return screens; +} + +IOHOOK_API long int hook_get_auto_repeat_rate() { + bool successful = false; + long int value = -1; + unsigned int delay = 0, rate = 0; + + // Check and make sure we could connect to the x server. + if (properties_disp != NULL) { + #ifdef USE_XKB + // Attempt to acquire the keyboard auto repeat rate using the XKB extension. + if (!successful) { + successful = XkbGetAutoRepeatRate(properties_disp, XkbUseCoreKbd, &delay, &rate); + + if (successful) { + logger(LOG_LEVEL_INFO, "%s [%u]: XkbGetAutoRepeatRate: %u.\n", + __FUNCTION__, __LINE__, rate); + } + } + #endif + + #ifdef USE_XF86MISC + // Fallback to the XF86 Misc extension if available and other efforts failed. + if (!successful) { + XF86MiscKbdSettings kb_info; + successful = (bool) XF86MiscGetKbdSettings(properties_disp, &kb_info); + if (successful) { + logger(LOG_LEVEL_INFO, "%s [%u]: XF86MiscGetKbdSettings: %i.\n", + __FUNCTION__, __LINE__, kbdinfo.rate); + + delay = (unsigned int) kbdinfo.delay; + rate = (unsigned int) kbdinfo.rate; + } + } + #endif + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + + if (successful) { + value = (long int) rate; + } + + return value; +} + +IOHOOK_API long int hook_get_auto_repeat_delay() { + bool successful = false; + long int value = -1; + unsigned int delay = 0, rate = 0; + + // Check and make sure we could connect to the x server. + if (properties_disp != NULL) { + #ifdef USE_XKB + // Attempt to acquire the keyboard auto repeat rate using the XKB extension. + if (!successful) { + successful = XkbGetAutoRepeatRate(properties_disp, XkbUseCoreKbd, &delay, &rate); + + if (successful) { + logger(LOG_LEVEL_INFO, "%s [%u]: XkbGetAutoRepeatRate: %u.\n", + __FUNCTION__, __LINE__, delay); + } + } + #endif + + #ifdef USE_XF86MISC + // Fallback to the XF86 Misc extension if available and other efforts failed. + if (!successful) { + XF86MiscKbdSettings kb_info; + successful = (bool) XF86MiscGetKbdSettings(properties_disp, &kb_info); + if (successful) { + logger(LOG_LEVEL_INFO, "%s [%u]: XF86MiscGetKbdSettings: %i.\n", + __FUNCTION__, __LINE__, kbdinfo.delay); + + delay = (unsigned int) kbdinfo.delay; + rate = (unsigned int) kbdinfo.rate; + } + } + #endif + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + + if (successful) { + value = (long int) delay; + } + + return value; +} + +IOHOOK_API long int hook_get_pointer_acceleration_multiplier() { + long int value = -1; + int accel_numerator, accel_denominator, threshold; + + // Check and make sure we could connect to the x server. + if (properties_disp != NULL) { + XGetPointerControl(properties_disp, &accel_numerator, &accel_denominator, &threshold); + if (accel_denominator >= 0) { + logger(LOG_LEVEL_INFO, "%s [%u]: XGetPointerControl: %i.\n", + __FUNCTION__, __LINE__, accel_denominator); + + value = (long int) accel_denominator; + } + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + + return value; +} + +IOHOOK_API long int hook_get_pointer_acceleration_threshold() { + long int value = -1; + int accel_numerator, accel_denominator, threshold; + + // Check and make sure we could connect to the x server. + if (properties_disp != NULL) { + XGetPointerControl(properties_disp, &accel_numerator, &accel_denominator, &threshold); + if (threshold >= 0) { + logger(LOG_LEVEL_INFO, "%s [%u]: XGetPointerControl: %i.\n", + __FUNCTION__, __LINE__, threshold); + + value = (long int) threshold; + } + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + + return value; +} + +IOHOOK_API long int hook_get_pointer_sensitivity() { + long int value = -1; + int accel_numerator, accel_denominator, threshold; + + // Check and make sure we could connect to the x server. + if (properties_disp != NULL) { + XGetPointerControl(properties_disp, &accel_numerator, &accel_denominator, &threshold); + if (accel_numerator >= 0) { + logger(LOG_LEVEL_INFO, "%s [%u]: XGetPointerControl: %i.\n", + __FUNCTION__, __LINE__, accel_numerator); + + value = (long int) accel_numerator; + } + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + + return value; +} + +IOHOOK_API long int hook_get_multi_click_time() { + long int value = 200; + int click_time; + bool successful = false; + + #ifdef USE_XT + // Check and make sure we could connect to the x server. + if (xt_disp != NULL) { + // Try and use the Xt extention to get the current multi-click. + if (!successful) { + // Fall back to the X Toolkit extension if available and other efforts failed. + click_time = XtGetMultiClickTime(xt_disp); + if (click_time >= 0) { + logger(LOG_LEVEL_INFO, "%s [%u]: XtGetMultiClickTime: %i.\n", + __FUNCTION__, __LINE__, click_time); + + successful = true; + } + } + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + #endif + + // Check and make sure we could connect to the x server. + if (properties_disp != NULL) { + // Try and acquire the multi-click time from the user defined X defaults. + if (!successful) { + char *xprop = XGetDefault(properties_disp, "*", "multiClickTime"); + if (xprop != NULL && sscanf(xprop, "%4i", &click_time) != EOF) { + logger(LOG_LEVEL_INFO, "%s [%u]: X default 'multiClickTime' property: %i.\n", + __FUNCTION__, __LINE__, click_time); + + successful = true; + } + } + + if (!successful) { + char *xprop = XGetDefault(properties_disp, "OpenWindows", "MultiClickTimeout"); + if (xprop != NULL && sscanf(xprop, "%4i", &click_time) != EOF) { + logger(LOG_LEVEL_INFO, "%s [%u]: X default 'MultiClickTimeout' property: %i.\n", + __FUNCTION__, __LINE__, click_time); + + successful = true; + } + } + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + + if (successful) { + value = (long int) click_time; + } + + return value; +} + +// Create a shared object constructor. +__attribute__ ((constructor)) +void on_library_load() { + // Make sure we are initialized for threading. + XInitThreads(); + + // Open local display. + properties_disp = XOpenDisplay(XDisplayName(NULL)); + if (properties_disp == NULL) { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + else { + logger(LOG_LEVEL_DEBUG, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay success."); + } + + #ifdef USE_XRANDR + // Create the thread attribute. + pthread_attr_t settings_thread_attr; + pthread_attr_init(&settings_thread_attr); + + pthread_t settings_thread_id; + if (pthread_create(&settings_thread_id, &settings_thread_attr, settings_thread_proc, NULL) == 0) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Successfully created settings thread.\n", + __FUNCTION__, __LINE__); + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to create settings thread!\n", + __FUNCTION__, __LINE__); + } + + // Make sure the thread attribute is removed. + pthread_attr_destroy(&settings_thread_attr); + #endif + + #ifdef USE_XT + XtToolkitInitialize(); + xt_context = XtCreateApplicationContext(); + + int argc = 0; + char ** argv = { NULL }; + xt_disp = XtOpenDisplay(xt_context, NULL, "IOHook", "libIOhook", NULL, 0, &argc, argv); + #endif + + // Initialize. + load_input_helper(properties_disp); +} + +// Create a shared object destructor. +__attribute__ ((destructor)) +void on_library_unload() { + // Disable the event hook. + //hook_stop(); + + // Cleanup. + unload_input_helper(); + + #ifdef USE_XT + XtCloseDisplay(xt_disp); + XtDestroyApplicationContext(xt_context); + #endif + + // Destroy the native displays. + if (properties_disp != NULL) { + XCloseDisplay(properties_disp); + properties_disp = NULL; + } +} diff --git a/hook.go b/hook.go new file mode 100644 index 0000000..e69de29 diff --git a/test/main.go b/test/main.go new file mode 100644 index 0000000..d2207ae --- /dev/null +++ b/test/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + "go-vgo/robotn/gohook" +) + +func main() { + hook.AsyncHook() + veve := hook.AddEvent("v") + if veve == 0 { + fmt.Println("v...") + } +} From 3df4cff48b39188736199c3c05daf908c38fc630 Mon Sep 17 00:00:00 2001 From: vCaesar Date: Sat, 22 Apr 2017 19:50:38 +0800 Subject: [PATCH 003/120] Fix invalid size --- event/hook/x11/hook_c.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/event/hook/x11/hook_c.h b/event/hook/x11/hook_c.h index 5813cc2..4be4b60 100644 --- a/event/hook/x11/hook_c.h +++ b/event/hook/x11/hook_c.h @@ -1019,7 +1019,7 @@ static int xrecord_start() { } if (hook->input.connection != NULL) { - xcb_disconnect(hook->input.connection); + // xcb_disconnect(hook->input.connection); hook->input.connection = NULL; } #endif From 2cc3a5d7cecae6f1c592ff7fb1f97297783ce386 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 4 Sep 2018 15:25:08 -0400 Subject: [PATCH 004/120] Update README.md and move code --- README.md | 20 +++++++++++++++++++- {event/hook => hook}/darwin/event_c.h | 0 {event/hook => hook}/darwin/hook_c.h | 0 {event/hook => hook}/darwin/input.h | 0 {event/hook => hook}/darwin/input_c.h | 0 {event/hook => hook}/darwin/properties_c.h | 0 {event/hook => hook}/iohook.h | 0 {event/hook => hook}/logger.h | 0 {event/hook => hook}/logger_c.h | 0 {event/hook => hook}/windows/event_c.h | 0 {event/hook => hook}/windows/hook_c.h | 0 {event/hook => hook}/windows/input.h | 0 {event/hook => hook}/windows/input_c.h | 0 {event/hook => hook}/windows/properties_c.h | 0 {event/hook => hook}/x11/event_c.h | 0 {event/hook => hook}/x11/hook_c.h | 0 {event/hook => hook}/x11/input.h | 0 {event/hook => hook}/x11/input_c.h | 0 {event/hook => hook}/x11/properties_c.h | 0 test/main.go | 1 + 20 files changed, 20 insertions(+), 1 deletion(-) rename {event/hook => hook}/darwin/event_c.h (100%) rename {event/hook => hook}/darwin/hook_c.h (100%) rename {event/hook => hook}/darwin/input.h (100%) rename {event/hook => hook}/darwin/input_c.h (100%) rename {event/hook => hook}/darwin/properties_c.h (100%) rename {event/hook => hook}/iohook.h (100%) rename {event/hook => hook}/logger.h (100%) rename {event/hook => hook}/logger_c.h (100%) rename {event/hook => hook}/windows/event_c.h (100%) rename {event/hook => hook}/windows/hook_c.h (100%) rename {event/hook => hook}/windows/input.h (100%) rename {event/hook => hook}/windows/input_c.h (100%) rename {event/hook => hook}/windows/properties_c.h (100%) rename {event/hook => hook}/x11/event_c.h (100%) rename {event/hook => hook}/x11/hook_c.h (100%) rename {event/hook => hook}/x11/input.h (100%) rename {event/hook => hook}/x11/input_c.h (100%) rename {event/hook => hook}/x11/properties_c.h (100%) diff --git a/README.md b/README.md index 764879e..5e76383 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,21 @@ # gohook -This is a work in progress. \ No newline at end of file +This is a work in progress. + +```Go +package main + +import ( + "fmt" + + "go-vgo/robotn/gohook" +) + +func main() { + hook.AsyncHook() + veve := hook.AddEvent("v") + if veve == 0 { + fmt.Println("v...") + } +} +``` \ No newline at end of file diff --git a/event/hook/darwin/event_c.h b/hook/darwin/event_c.h similarity index 100% rename from event/hook/darwin/event_c.h rename to hook/darwin/event_c.h diff --git a/event/hook/darwin/hook_c.h b/hook/darwin/hook_c.h similarity index 100% rename from event/hook/darwin/hook_c.h rename to hook/darwin/hook_c.h diff --git a/event/hook/darwin/input.h b/hook/darwin/input.h similarity index 100% rename from event/hook/darwin/input.h rename to hook/darwin/input.h diff --git a/event/hook/darwin/input_c.h b/hook/darwin/input_c.h similarity index 100% rename from event/hook/darwin/input_c.h rename to hook/darwin/input_c.h diff --git a/event/hook/darwin/properties_c.h b/hook/darwin/properties_c.h similarity index 100% rename from event/hook/darwin/properties_c.h rename to hook/darwin/properties_c.h diff --git a/event/hook/iohook.h b/hook/iohook.h similarity index 100% rename from event/hook/iohook.h rename to hook/iohook.h diff --git a/event/hook/logger.h b/hook/logger.h similarity index 100% rename from event/hook/logger.h rename to hook/logger.h diff --git a/event/hook/logger_c.h b/hook/logger_c.h similarity index 100% rename from event/hook/logger_c.h rename to hook/logger_c.h diff --git a/event/hook/windows/event_c.h b/hook/windows/event_c.h similarity index 100% rename from event/hook/windows/event_c.h rename to hook/windows/event_c.h diff --git a/event/hook/windows/hook_c.h b/hook/windows/hook_c.h similarity index 100% rename from event/hook/windows/hook_c.h rename to hook/windows/hook_c.h diff --git a/event/hook/windows/input.h b/hook/windows/input.h similarity index 100% rename from event/hook/windows/input.h rename to hook/windows/input.h diff --git a/event/hook/windows/input_c.h b/hook/windows/input_c.h similarity index 100% rename from event/hook/windows/input_c.h rename to hook/windows/input_c.h diff --git a/event/hook/windows/properties_c.h b/hook/windows/properties_c.h similarity index 100% rename from event/hook/windows/properties_c.h rename to hook/windows/properties_c.h diff --git a/event/hook/x11/event_c.h b/hook/x11/event_c.h similarity index 100% rename from event/hook/x11/event_c.h rename to hook/x11/event_c.h diff --git a/event/hook/x11/hook_c.h b/hook/x11/hook_c.h similarity index 100% rename from event/hook/x11/hook_c.h rename to hook/x11/hook_c.h diff --git a/event/hook/x11/input.h b/hook/x11/input.h similarity index 100% rename from event/hook/x11/input.h rename to hook/x11/input.h diff --git a/event/hook/x11/input_c.h b/hook/x11/input_c.h similarity index 100% rename from event/hook/x11/input_c.h rename to hook/x11/input_c.h diff --git a/event/hook/x11/properties_c.h b/hook/x11/properties_c.h similarity index 100% rename from event/hook/x11/properties_c.h rename to hook/x11/properties_c.h diff --git a/test/main.go b/test/main.go index d2207ae..0fe8d28 100644 --- a/test/main.go +++ b/test/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "go-vgo/robotn/gohook" ) From 5d0c66254917b46608a4540cfaea2ede59c61e74 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 16 Sep 2018 19:25:32 -0400 Subject: [PATCH 005/120] add event c file --- event/goEvent.h | 252 ++++++++++++++++++++++++++++++++++++++++++++++++ event/os.h | 49 ++++++++++ event/pub.h | 95 ++++++++++++++++++ 3 files changed, 396 insertions(+) create mode 100644 event/goEvent.h create mode 100644 event/os.h create mode 100644 event/pub.h diff --git a/event/goEvent.h b/event/goEvent.h new file mode 100644 index 0000000..b3467fb --- /dev/null +++ b/event/goEvent.h @@ -0,0 +1,252 @@ +// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://github.com/go-vgo/robotgo/blob/master/LICENSE +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include "pub.h" + + +void dispatch_proc(iohook_event * const event) { + char buffer[256] = { 0 }; + size_t length = snprintf(buffer, sizeof(buffer), + "id=%i,when=%" PRIu64 ",mask=0x%X", + event->type, event->time, event->mask); + + switch (event->type) { + case EVENT_KEY_PRESSED: + // If the escape key is pressed, naturally terminate the program. + if (event->data.keyboard.keycode == VC_ESCAPE) { + // int status = hook_stop(); + // switch (status) { + // // System level errors. + // case IOHOOK_ERROR_OUT_OF_MEMORY: + // loggerProc(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); + // break; + + // case IOHOOK_ERROR_X_RECORD_GET_CONTEXT: + // // NOTE This is the only platform specific error that occurs on hook_stop(). + // loggerProc(LOG_LEVEL_ERROR, "Failed to get XRecord context. (%#X)", status); + // break; + + // // Default error. + // case IOHOOK_FAILURE: + // default: + // loggerProc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); + // break; + // } + } + case EVENT_KEY_RELEASED: + snprintf(buffer + length, sizeof(buffer) - length, + ",keycode=%u,rawcode=0x%X", + event->data.keyboard.keycode, event->data.keyboard.rawcode); + int akeyCode = (uint16_t) event->data.keyboard.keycode; + + if (event->data.keyboard.keycode == VC_ESCAPE + && atoi(cevent) == 11) { + int stopEvent = stop_event(); + // printf("stop_event%d\n", stopEvent); + cstatus = 0; + } + + // printf("atoi(str)---%d\n", atoi(cevent)); + if (akeyCode == atoi(cevent)) { + int stopEvent = stop_event(); + // printf("%d\n", stopEvent); + cstatus = 0; + } + break; + + case EVENT_KEY_TYPED: + snprintf(buffer + length, sizeof(buffer) - length, + ",keychar=%lc,rawcode=%u", + (uint16_t) event->data.keyboard.keychar, + event->data.keyboard.rawcode); + + #ifdef WE_REALLY_WANT_A_POINTER + char *buf = malloc (6); + #else + char buf[6]; + #endif + + sprintf(buf, "%lc", (uint16_t) event->data.keyboard.keychar); + + #ifdef WE_REALLY_WANT_A_POINTER + free (buf); + #endif + + if (strcmp(buf, cevent) == 0) { + int stopEvent = stop_event(); + // printf("%d\n", stopEvent); + cstatus = 0; + } + // return (char*) event->data.keyboard.keychar; + break; + + case EVENT_MOUSE_PRESSED: + case EVENT_MOUSE_RELEASED: + case EVENT_MOUSE_CLICKED: + case EVENT_MOUSE_MOVED: + case EVENT_MOUSE_DRAGGED: + snprintf(buffer + length, sizeof(buffer) - length, + ",x=%i,y=%i,button=%i,clicks=%i", + event->data.mouse.x, event->data.mouse.y, + event->data.mouse.button, event->data.mouse.clicks); + + int abutton = event->data.mouse.button; + int aclicks = event->data.mouse.clicks; + int amouse = -1; + + if (strcmp(cevent, "mleft") == 0) { + amouse = 1; + } + if (strcmp(cevent, "mright") == 0) { + amouse = 2; + } + if (strcmp(cevent, "wheelDown") == 0) { + amouse = 4; + } + if (strcmp(cevent, "wheelUp") == 0) { + amouse = 5; + } + if (strcmp(cevent, "wheelLeft") == 0) { + amouse = 6; + } + if (strcmp(cevent, "wheelRight") == 0) { + amouse = 7; + } + if (abutton == amouse && aclicks == 1) { + int stopEvent = stop_event(); + cstatus = 0; + } + + break; + + case EVENT_MOUSE_WHEEL: + snprintf(buffer + length, sizeof(buffer) - length, + ",type=%i,amount=%i,rotation=%i", + event->data.wheel.type, event->data.wheel.amount, + event->data.wheel.rotation); + break; + + default: + break; + } + + // fprintf(stdout, "----%s\n", buffer); +} + +int add_event(char *key_event) { + // (uint16_t *) + cevent = key_event; + // Set the logger callback for library output. + hookSetlogger(&loggerProc); + + // Set the event callback for IOhook events. + hook_set_dispatch_proc(&dispatch_proc); + // Start the hook and block. + // NOTE If EVENT_HOOK_ENABLED was delivered, the status will always succeed. + int status = hook_run(); + + switch (status) { + case IOHOOK_SUCCESS: + // Everything is ok. + break; + + // System level errors. + case IOHOOK_ERROR_OUT_OF_MEMORY: + loggerProc(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); + break; + + + // X11 specific errors. + case IOHOOK_ERROR_X_OPEN_DISPLAY: + loggerProc(LOG_LEVEL_ERROR, "Failed to open X11 display. (%#X)", status); + break; + + case IOHOOK_ERROR_X_RECORD_NOT_FOUND: + loggerProc(LOG_LEVEL_ERROR, "Unable to locate XRecord extension. (%#X)", status); + break; + + case IOHOOK_ERROR_X_RECORD_ALLOC_RANGE: + loggerProc(LOG_LEVEL_ERROR, "Unable to allocate XRecord range. (%#X)", status); + break; + + case IOHOOK_ERROR_X_RECORD_CREATE_CONTEXT: + loggerProc(LOG_LEVEL_ERROR, "Unable to allocate XRecord context. (%#X)", status); + break; + + case IOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT: + loggerProc(LOG_LEVEL_ERROR, "Failed to enable XRecord context. (%#X)", status); + break; + + + // Windows specific errors. + case IOHOOK_ERROR_SET_WINDOWS_HOOK_EX: + loggerProc(LOG_LEVEL_ERROR, "Failed to register low level windows hook. (%#X)", status); + break; + + + // Darwin specific errors. + case IOHOOK_ERROR_AXAPI_DISABLED: + loggerProc(LOG_LEVEL_ERROR, "Failed to enable access for assistive devices. (%#X)", status); + break; + + case IOHOOK_ERROR_CREATE_EVENT_PORT: + loggerProc(LOG_LEVEL_ERROR, "Failed to create apple event port. (%#X)", status); + break; + + case IOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE: + loggerProc(LOG_LEVEL_ERROR, "Failed to create apple run loop source. (%#X)", status); + break; + + case IOHOOK_ERROR_GET_RUNLOOP: + loggerProc(LOG_LEVEL_ERROR, "Failed to acquire apple run loop. (%#X)", status); + break; + + case IOHOOK_ERROR_CREATE_OBSERVER: + loggerProc(LOG_LEVEL_ERROR, "Failed to create apple run loop observer. (%#X)", status); + break; + + // Default error. + case IOHOOK_FAILURE: + default: + loggerProc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); + break; + } + + // return status; + // printf("%d\n", status); + return cstatus; +} + +int stop_event(){ + int status = hook_stop(); + switch (status) { + // System level errors. + case IOHOOK_ERROR_OUT_OF_MEMORY: + loggerProc(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); + break; + + case IOHOOK_ERROR_X_RECORD_GET_CONTEXT: + // NOTE This is the only platform specific error that occurs on hook_stop(). + loggerProc(LOG_LEVEL_ERROR, "Failed to get XRecord context. (%#X)", status); + break; + + // Default error. + case IOHOOK_FAILURE: + default: + // loggerProc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); + break; + } + + return status; +} \ No newline at end of file diff --git a/event/os.h b/event/os.h new file mode 100644 index 0000000..ab46989 --- /dev/null +++ b/event/os.h @@ -0,0 +1,49 @@ +#pragma once +#ifndef OS_H +#define OS_H + +/* Python versions under 2.5 don't support this macro, but it's not + * terribly difficult to replicate: */ +#ifndef PyModule_AddIntMacro + #define PyModule_AddIntMacro(module, macro) \ + PyModule_AddIntConstant(module, #macro, macro) +#endif /* PyModule_AddIntMacro */ + +#if !defined(IS_MACOSX) && defined(__APPLE__) && defined(__MACH__) + #define IS_MACOSX +#endif /* IS_MACOSX */ + +#if !defined(IS_WINDOWS) && (defined(WIN32) || defined(_WIN32) || \ + defined(__WIN32__) || defined(__WINDOWS__) || defined(__CYGWIN__)) + #define IS_WINDOWS +#endif /* IS_WINDOWS */ + +#if !defined(USE_X11) && !defined(NUSE_X11) && !defined(IS_MACOSX) && !defined(IS_WINDOWS) + #define USE_X11 +#endif /* USE_X11 */ + +#if defined(IS_WINDOWS) + #define STRICT /* Require use of exact types. */ + #define WIN32_LEAN_AND_MEAN 1 /* Speed up compilation. */ + #include +#elif !defined(IS_MACOSX) && !defined(USE_X11) + #error "Sorry, this platform isn't supported yet!" +#endif + +/* Interval to align by for large buffers (e.g. bitmaps). */ +/* Must be a power of 2. */ +#ifndef BYTE_ALIGN + #define BYTE_ALIGN 4 /* Bytes to align pixel buffers to. */ + /* #include */ + /* #define BYTE_ALIGN (sizeof(size_t)) */ +#endif /* BYTE_ALIGN */ + +#if BYTE_ALIGN == 0 + /* No alignment needed. */ + #define ADD_PADDING(width) (width) +#else + /* Aligns given width to padding. */ + #define ADD_PADDING(width) (BYTE_ALIGN + (((width) - 1) & ~(BYTE_ALIGN - 1))) +#endif + +#endif /* OS_H */ diff --git a/event/pub.h b/event/pub.h new file mode 100644 index 0000000..408f113 --- /dev/null +++ b/event/pub.h @@ -0,0 +1,95 @@ +// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://github.com/go-vgo/robotgo/blob/master/LICENSE +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "os.h" + +#if defined(IS_MACOSX) + #include "../hook/darwin/input_c.h" + #include "../hook/darwin/hook_c.h" + #include "../hook/darwin/event_c.h" + #include "../hook/darwin/properties_c.h" +#elif defined(USE_X11) + //#define USE_XKBCOMMON 0 + #include "../hook/x11/input_c.h" + #include "../hook/x11/hook_c.h" + #include "../hook/x11/event_c.h" + #include "../hook/x11/properties_c.h" +#elif defined(IS_WINDOWS) + #include "../hook/windows/input_c.h" + #include "../hook/windows/hook_c.h" + #include "../hook/windows/event_c.h" + #include "../hook/windows/properties_c.h" +#endif + +#include +#include +#include +#include +#include +#include "../hook/iohook.h" + + +int vccode[100]; +int codesz; + +char *cevent; +int rrevent; +// uint16_t *cevent; +int cstatus = 1; + + +int stop_event(); +int add_event(char *key_event); +// int allEvent(char *key_event); +int allEvent(char *key_event, int vcode[], int size); + +// NOTE: The following callback executes on the same thread that hook_run() is called +// from. + +struct _MEvent { + uint8_t id; + size_t mask; + uint16_t keychar; + // char *keychar; + size_t x; + uint8_t y; + uint8_t bytesPerPixel; +}; + +typedef struct _MEvent MEvent; +// typedef MMBitmap *MMBitmapRef; + +MEvent mEvent; + + +bool loggerProc(unsigned int level, const char *format, ...) { + bool status = false; + + va_list args; + switch (level) { + #ifdef USE_DEBUG + case LOG_LEVEL_DEBUG: + case LOG_LEVEL_INFO: + va_start(args, format); + status = vfprintf(stdout, format, args) >= 0; + va_end(args); + break; + #endif + + case LOG_LEVEL_WARN: + case LOG_LEVEL_ERROR: + va_start(args, format); + status = vfprintf(stderr, format, args) >= 0; + va_end(args); + break; + } + + return status; +} \ No newline at end of file From b0df75125b27b643ae2f85e7327b5037e44c9c80 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 16 Sep 2018 19:29:13 -0400 Subject: [PATCH 006/120] add hook go support --- hook.go | 34 ++++++++++++++++++++++++++++++++++ hook/darwin/input.h | 26 +++++++++++++------------- test/main.go | 4 ++-- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/hook.go b/hook.go index e69de29..04458a2 100644 --- a/hook.go +++ b/hook.go @@ -0,0 +1,34 @@ +package hook + +/* +#cgo darwin CFLAGS: -x objective-c -Wno-deprecated-declarations +#cgo darwin LDFLAGS: -framework Cocoa +#cgo linux CFLAGS:-I/usr/src +#cgo linux LDFLAGS: -lX11-xcb -lxcb -lxcb-xkb -lxkbcommon -lxkbcommon-x11 -lm +#cgo windows LDFLAGS: -lgdi32 -luser32 + +#include "event/goEvent.h" +// #include "event/hook_async.h" +*/ +import "C" + +import( + // "fmt" + "unsafe" +) + +// AddEvent add event listener +func AddEvent(key string) int { + cs := C.CString(key) + + eve := C.add_event(cs) + geve := int(eve) + + defer C.free(unsafe.Pointer(cs)) + return geve +} + +// StopEvent stop event listener +func StopEvent() { + C.stop_event() +} \ No newline at end of file diff --git a/hook/darwin/input.h b/hook/darwin/input.h index 23977e5..a841070 100644 --- a/hook/darwin/input.h +++ b/hook/darwin/input.h @@ -5,16 +5,16 @@ #include #include // For HIToolbox kVK_ keycodes and TIS funcitons. #ifdef USE_IOKIT - #include +#include #endif #include #ifndef USE_IOKIT // Some of the system key codes that are needed from IOKit. -// #define NX_KEYTYPE_SOUND_UP 0x00 -// #define NX_KEYTYPE_SOUND_DOWN 0x01 -// #define NX_KEYTYPE_MUTE 0x07 +#define NX_KEYTYPE_SOUND_UP 0x00 +#define NX_KEYTYPE_SOUND_DOWN 0x01 +#define NX_KEYTYPE_MUTE 0x07 /* Display controls... #define NX_KEYTYPE_BRIGHTNESS_UP 0x02 @@ -26,18 +26,18 @@ #define NX_KEYTYPE_ILLUMINATION_TOGGLE 0x17 */ -// #define NX_KEYTYPE_CAPS_LOCK 0x04 -// #define NX_KEYTYPE_HELP 0x05 -// #define NX_POWER_KEY 0x06 +#define NX_KEYTYPE_CAPS_LOCK 0x04 +//#define NX_KEYTYPE_HELP 0x05 +#define NX_POWER_KEY 0x06 -// #define NX_KEYTYPE_EJECT 0x0E -// #define NX_KEYTYPE_PLAY 0x10 -// #define NX_KEYTYPE_NEXT 0x12 -// #define NX_KEYTYPE_PREVIOUS 0x13 +#define NX_KEYTYPE_EJECT 0x0E +#define NX_KEYTYPE_PLAY 0x10 +#define NX_KEYTYPE_NEXT 0x12 +#define NX_KEYTYPE_PREVIOUS 0x13 /* There is no official fast-forward or rewind scan code support.*/ -// #define NX_KEYTYPE_FAST 0x14 -// #define NX_KEYTYPE_REWIND 0x15 +#define NX_KEYTYPE_FAST 0x14 +#define NX_KEYTYPE_REWIND 0x15 #endif diff --git a/test/main.go b/test/main.go index 0fe8d28..d3fe134 100644 --- a/test/main.go +++ b/test/main.go @@ -3,11 +3,11 @@ package main import ( "fmt" - "go-vgo/robotn/gohook" + "github.com/robotn/gohook" ) func main() { - hook.AsyncHook() + // hook.AsyncHook() veve := hook.AddEvent("v") if veve == 0 { fmt.Println("v...") From 3dccbf0083168096c576bf62abb3d5d4c916e2ea Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 16 Sep 2018 19:43:30 -0400 Subject: [PATCH 007/120] Update README.md and fixed clink --- README.md | 2 +- hook.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5e76383..be006df 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ import ( ) func main() { - hook.AsyncHook() + // hook.AsyncHook() veve := hook.AddEvent("v") if veve == 0 { fmt.Println("v...") diff --git a/hook.go b/hook.go index 04458a2..a5b3b91 100644 --- a/hook.go +++ b/hook.go @@ -4,7 +4,8 @@ package hook #cgo darwin CFLAGS: -x objective-c -Wno-deprecated-declarations #cgo darwin LDFLAGS: -framework Cocoa #cgo linux CFLAGS:-I/usr/src -#cgo linux LDFLAGS: -lX11-xcb -lxcb -lxcb-xkb -lxkbcommon -lxkbcommon-x11 -lm +#cgo linux LDFLAGS: -L/usr/src -lX11 -lXtst +#cgo linux LDFLAGS: -lX11-xcb -lxcb -lxcb-xkb -lxkbcommon -lxkbcommon-x11 #cgo windows LDFLAGS: -lgdi32 -luser32 #include "event/goEvent.h" From 7e281e833f79a131f03279e64b003f61321d2f9e Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 16 Sep 2018 20:17:40 -0400 Subject: [PATCH 008/120] add ci support --- appveyor.yml | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ config.yml | 19 +++++++++++++ hook.go | 2 +- 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 appveyor.yml create mode 100644 config.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..dc8f0fe --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,80 @@ +# version format +version: "{build}" + +# Operating system (build VM template) +# os: Windows Server 2012 R2 +os: Visual Studio 2017 + +# Platform. +# platform: +# - x64 +# - x86 + +clone_folder: c:\gopath\src\github.com\robotn\gohook + +# Environment variables +environment: + global: + GOPATH: C:\gopath + CC: gcc.exe + matrix: + - GOARCH: amd64 + # GOVERSION: 1.9.3 + GETH_ARCH: amd64 + MSYS2_ARCH: x86_64 + MSYS2_BITS: 64 + MSYSTEM: MINGW64 + PATH: C:\msys64\mingw64\bin\;C:\Program Files (x86)\NSIS\;%PATH% + - GOARCH: 386 + # GOVERSION: 1.9.3 + GETH_ARCH: 386 + MSYS2_ARCH: i686 + MSYS2_BITS: 32 + MSYSTEM: MINGW32 + PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% + # - COMPILER: MINGW_W64 + # ARCHITECTURE: x64 + GOVERSION: 1.10.4 + # GOPATH: c:\gopath + +# scripts that run after cloning repository +# install: +# - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% +# - go version +# - go env +# - gcc --version + # - python --version + +install: + - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% + - git submodule update --init + - rmdir C:\go /s /q + - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-%GETH_ARCH%.zip + - 7z x go%GOVERSION%.windows-%GETH_ARCH%.zip -y -oC:\ > NUL + - go version + - go env + - gcc --version + +# To run your custom scripts instead of automatic MSBuild +build_script: + # We need to disable firewall - https://github.com/appveyor/ci/issues/1579#issuecomment-309830648 + - ps: Disable-NetFirewallRule -DisplayName 'File and Printer Sharing (SMB-Out)' + - cd c:\gopath\src\github.com\robotn\gohook + - git branch + - go get -t ./... + +# To run your custom scripts instead of automatic tests +test_script: + # Unit tests + - ps: Add-AppveyorTest "Unit Tests" -Outcome Running + - go test -u github.com/go-vgo/robotgo/... + - ps: Update-AppveyorTest "Unit Tests" -Outcome Passed + +# notifications: +# - provider: Email +# to: +# - .io +# on_build_failure: true +# on_build_status_changed: true +# to disable deployment +deploy: off diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..7570316 --- /dev/null +++ b/config.yml @@ -0,0 +1,19 @@ +version: 2 + +jobs: + build: + docker: + # using custom image, see .circleci/images/primary/Dockerfile + - image: govgo/robotgoci:1.10.3 + working_directory: /gopath/src/github.com/robotn/gohook + steps: + - checkout + # specify any bash command here prefixed with `run: ` + # override: + # './...' is a relative pattern which means all subdirectories + # - run: go get -u golang.org/x/sys/unix + - run: go get -v -t -d ./... + - run: go test -v ./... + # codecov.io + # - run: go test -v -covermode=count -coverprofile=coverage.out + # - run: bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/hook.go b/hook.go index a5b3b91..7984e5e 100644 --- a/hook.go +++ b/hook.go @@ -21,11 +21,11 @@ import( // AddEvent add event listener func AddEvent(key string) int { cs := C.CString(key) + defer C.free(unsafe.Pointer(cs)) eve := C.add_event(cs) geve := int(eve) - defer C.free(unsafe.Pointer(cs)) return geve } From 763a9e4eaea6e808f849eb8268191f6b08e71063 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 16 Sep 2018 20:25:36 -0400 Subject: [PATCH 009/120] rename circle ci --- config.yml => circle.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename config.yml => circle.yml (100%) diff --git a/config.yml b/circle.yml similarity index 100% rename from config.yml rename to circle.yml From 76d1c8b4cda30eb98b1ed2219fa017391856c247 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 17 Sep 2018 13:30:41 -0400 Subject: [PATCH 010/120] remove windows clink --- hook.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hook.go b/hook.go index 7984e5e..7454f10 100644 --- a/hook.go +++ b/hook.go @@ -3,10 +3,11 @@ package hook /* #cgo darwin CFLAGS: -x objective-c -Wno-deprecated-declarations #cgo darwin LDFLAGS: -framework Cocoa + #cgo linux CFLAGS:-I/usr/src #cgo linux LDFLAGS: -L/usr/src -lX11 -lXtst #cgo linux LDFLAGS: -lX11-xcb -lxcb -lxcb-xkb -lxkbcommon -lxkbcommon-x11 -#cgo windows LDFLAGS: -lgdi32 -luser32 +//#cgo windows LDFLAGS: -lgdi32 -luser32 #include "event/goEvent.h" // #include "event/hook_async.h" From 90cff033b9b00a0ed5e6b5574c92532459dc8f80 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 17 Sep 2018 13:36:29 -0400 Subject: [PATCH 011/120] #include --- event/goEvent.h | 1 + 1 file changed, 1 insertion(+) diff --git a/event/goEvent.h b/event/goEvent.h index b3467fb..ecff854 100644 --- a/event/goEvent.h +++ b/event/goEvent.h @@ -12,6 +12,7 @@ #include #endif +#include #include "pub.h" From 37d597ad23ec56acc965f46ac8dd72ee841d4348 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 17 Sep 2018 13:41:48 -0400 Subject: [PATCH 012/120] update appveyor ci --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index dc8f0fe..56f66a2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -67,7 +67,7 @@ build_script: test_script: # Unit tests - ps: Add-AppveyorTest "Unit Tests" -Outcome Running - - go test -u github.com/go-vgo/robotgo/... + - go test -u github.com/robotn/gohook... - ps: Update-AppveyorTest "Unit Tests" -Outcome Passed # notifications: From 863b0f27c49ed818e55de83baf4c543a0dc6770f Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 17 Sep 2018 14:09:59 -0400 Subject: [PATCH 013/120] Update README.md and add head hint --- README.md | 2 +- hook.go | 10 ++++++++++ hook/darwin/event_c.h | 7 +++++++ hook/darwin/hook_c.h | 7 +++++++ hook/darwin/input.h | 7 +++++++ hook/darwin/input_c.h | 7 +++++++ hook/darwin/properties_c.h | 7 +++++++ hook/iohook.h | 7 +++++++ hook/logger_c.h | 7 +++++++ hook/windows/event_c.h | 7 +++++++ hook/windows/hook_c.h | 7 +++++++ hook/windows/input.h | 8 ++++++++ hook/windows/input_c.h | 7 +++++++ hook/windows/properties_c.h | 7 +++++++ hook/x11/event_c.h | 7 +++++++ hook/x11/hook_c.h | 7 +++++++ hook/x11/input.h | 7 +++++++ hook/x11/input_c.h | 7 +++++++ hook/x11/properties_c.h | 7 +++++++ 19 files changed, 131 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index be006df..9107e91 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # gohook -This is a work in progress. + ```Go package main diff --git a/hook.go b/hook.go index 7454f10..217d9bb 100644 --- a/hook.go +++ b/hook.go @@ -1,3 +1,13 @@ +// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://github.com/go-vgo/robotgo/blob/master/LICENSE +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + package hook /* diff --git a/hook/darwin/event_c.h b/hook/darwin/event_c.h index c621b71..0497aec 100644 --- a/hook/darwin/event_c.h +++ b/hook/darwin/event_c.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifdef HAVE_CONFIG_H #include diff --git a/hook/darwin/hook_c.h b/hook/darwin/hook_c.h index cb8b609..474bef0 100644 --- a/hook/darwin/hook_c.h +++ b/hook/darwin/hook_c.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifdef HAVE_CONFIG_H #include diff --git a/hook/darwin/input.h b/hook/darwin/input.h index a841070..9037515 100644 --- a/hook/darwin/input.h +++ b/hook/darwin/input.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifndef _included_input_helper #define _included_input_helper diff --git a/hook/darwin/input_c.h b/hook/darwin/input_c.h index 298f67f..d884381 100644 --- a/hook/darwin/input_c.h +++ b/hook/darwin/input_c.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifdef HAVE_CONFIG_H #include diff --git a/hook/darwin/properties_c.h b/hook/darwin/properties_c.h index 0a7679c..89559e6 100644 --- a/hook/darwin/properties_c.h +++ b/hook/darwin/properties_c.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifdef HAVE_CONFIG_H #include diff --git a/hook/iohook.h b/hook/iohook.h index db0ba8b..022c2a1 100644 --- a/hook/iohook.h +++ b/hook/iohook.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifndef __IOHOOK_H #define __IOHOOK_H diff --git a/hook/logger_c.h b/hook/logger_c.h index 8752245..7ea2c9a 100644 --- a/hook/logger_c.h +++ b/hook/logger_c.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifdef HAVE_CONFIG_H #include diff --git a/hook/windows/event_c.h b/hook/windows/event_c.h index 2eb51f6..c1128fc 100644 --- a/hook/windows/event_c.h +++ b/hook/windows/event_c.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifdef HAVE_CONFIG_H #include diff --git a/hook/windows/hook_c.h b/hook/windows/hook_c.h index ed15515..ec36bc8 100644 --- a/hook/windows/hook_c.h +++ b/hook/windows/hook_c.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifdef HAVE_CONFIG_H #include diff --git a/hook/windows/input.h b/hook/windows/input.h index 6ba9817..8420d9a 100644 --- a/hook/windows/input.h +++ b/hook/windows/input.h @@ -1,3 +1,11 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + /*********************************************************************** Input ***********************************************************************/ diff --git a/hook/windows/input_c.h b/hook/windows/input_c.h index f061b1b..c261c87 100644 --- a/hook/windows/input_c.h +++ b/hook/windows/input_c.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifdef HAVE_CONFIG_H #include diff --git a/hook/windows/properties_c.h b/hook/windows/properties_c.h index 3c8ea2b..c9f2c74 100644 --- a/hook/windows/properties_c.h +++ b/hook/windows/properties_c.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifdef HAVE_CONFIG_H #include diff --git a/hook/x11/event_c.h b/hook/x11/event_c.h index 50e5ede..85d5593 100644 --- a/hook/x11/event_c.h +++ b/hook/x11/event_c.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifdef HAVE_CONFIG_H #include diff --git a/hook/x11/hook_c.h b/hook/x11/hook_c.h index 4be4b60..4733051 100644 --- a/hook/x11/hook_c.h +++ b/hook/x11/hook_c.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifdef HAVE_CONFIG_H #include diff --git a/hook/x11/input.h b/hook/x11/input.h index 905983f..49940de 100644 --- a/hook/x11/input.h +++ b/hook/x11/input.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #define USE_XKBCOMMON 0 //#define _included_input_helper 0 diff --git a/hook/x11/input_c.h b/hook/x11/input_c.h index 8d7f39d..e132d03 100644 --- a/hook/x11/input_c.h +++ b/hook/x11/input_c.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifdef HAVE_CONFIG_H #include diff --git a/hook/x11/properties_c.h b/hook/x11/properties_c.h index 9358143..8a94b92 100644 --- a/hook/x11/properties_c.h +++ b/hook/x11/properties_c.h @@ -1,3 +1,10 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ #ifdef HAVE_CONFIG_H #include From da6e29a7777c264ce35cc4fd2b98a59302a2511b Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 17 Sep 2018 14:15:40 -0400 Subject: [PATCH 014/120] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9107e91..bd46bc6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # gohook +[![CircleCI Status](https://circleci.com/gh/robotn/gohook.svg?style=shield)](https://circleci.com/gh/robotn/gohook) +![Appveyor](https://ci.appveyor.com/api/projects/status/github/robotn/gohook?branch=master&svg=true) +[![Go Report Card](https://goreportcard.com/badge/github.com/robotn/gohook)](https://goreportcard.com/report/github.com/robotn/gohook) +[![GoDoc](https://godoc.org/github.com/robotn/gohook?status.svg)](https://godoc.org/github.com/robotn/gohook) ```Go @@ -8,7 +12,7 @@ package main import ( "fmt" - "go-vgo/robotn/gohook" + "github.com/robotn/gohook" ) func main() { From 40c3386427eb558eb26426d3f56ac766c3e32335 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 17 Sep 2018 14:17:14 -0400 Subject: [PATCH 015/120] gofmt code --- hook.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hook.go b/hook.go index 217d9bb..0613cb0 100644 --- a/hook.go +++ b/hook.go @@ -12,7 +12,7 @@ package hook /* #cgo darwin CFLAGS: -x objective-c -Wno-deprecated-declarations -#cgo darwin LDFLAGS: -framework Cocoa +#cgo darwin LDFLAGS: -framework Cocoa #cgo linux CFLAGS:-I/usr/src #cgo linux LDFLAGS: -L/usr/src -lX11 -lXtst @@ -24,7 +24,7 @@ package hook */ import "C" -import( +import ( // "fmt" "unsafe" ) @@ -33,7 +33,7 @@ import( func AddEvent(key string) int { cs := C.CString(key) defer C.free(unsafe.Pointer(cs)) - + eve := C.add_event(cs) geve := int(eve) @@ -43,4 +43,4 @@ func AddEvent(key string) int { // StopEvent stop event listener func StopEvent() { C.stop_event() -} \ No newline at end of file +} From 6bfbcf567242bb96feb19d0148d42bc2a3c187b4 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 12 Nov 2018 14:40:05 -0400 Subject: [PATCH 016/120] update appveyor to go1.11.2 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 56f66a2..5d7d76f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.10.4 + GOVERSION: 1.11.2 # GOPATH: c:\gopath # scripts that run after cloning repository From 602a6294ff4e52a546b398b91dacbfeb8b5b913d Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 12 Nov 2018 16:39:16 -0400 Subject: [PATCH 017/120] update code style --- event/goEvent.h | 2 +- hook/logger_c.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/event/goEvent.h b/event/goEvent.h index ecff854..0af6237 100644 --- a/event/goEvent.h +++ b/event/goEvent.h @@ -149,7 +149,7 @@ int add_event(char *key_event) { // (uint16_t *) cevent = key_event; // Set the logger callback for library output. - hookSetlogger(&loggerProc); + hook_set_logger(&loggerProc); // Set the event callback for IOhook events. hook_set_dispatch_proc(&dispatch_proc); diff --git a/hook/logger_c.h b/hook/logger_c.h index 7ea2c9a..0c7e648 100644 --- a/hook/logger_c.h +++ b/hook/logger_c.h @@ -50,7 +50,7 @@ static bool default_logger(unsigned int level, const char *format, ...) { logger_t logger = &default_logger; -IOHOOK_API void hookSetlogger(logger_t logger_proc) { +IOHOOK_API void hook_set_logger(logger_t logger_proc) { if (logger_proc == NULL) { logger = &default_logger; } From f27e2e52653b5dc2d7be43ba30de71dcf4c3428c Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 13 Nov 2018 12:43:04 -0400 Subject: [PATCH 018/120] add event status and update code style --- event/goEvent.h | 4 ++-- event/pub.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/event/goEvent.h b/event/goEvent.h index 0af6237..acd41cc 100644 --- a/event/goEvent.h +++ b/event/goEvent.h @@ -49,7 +49,7 @@ void dispatch_proc(iohook_event * const event) { snprintf(buffer + length, sizeof(buffer) - length, ",keycode=%u,rawcode=0x%X", event->data.keyboard.keycode, event->data.keyboard.rawcode); - int akeyCode = (uint16_t) event->data.keyboard.keycode; + int key_code = (uint16_t) event->data.keyboard.keycode; if (event->data.keyboard.keycode == VC_ESCAPE && atoi(cevent) == 11) { @@ -59,7 +59,7 @@ void dispatch_proc(iohook_event * const event) { } // printf("atoi(str)---%d\n", atoi(cevent)); - if (akeyCode == atoi(cevent)) { + if (key_code == atoi(cevent)) { int stopEvent = stop_event(); // printf("%d\n", stopEvent); cstatus = 0; diff --git a/event/pub.h b/event/pub.h index 408f113..92f59a1 100644 --- a/event/pub.h +++ b/event/pub.h @@ -43,6 +43,7 @@ char *cevent; int rrevent; // uint16_t *cevent; int cstatus = 1; +int event_status; int stop_event(); From f842bde28946664db078248ef55e402c181e03e6 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 29 Nov 2018 11:59:32 -0400 Subject: [PATCH 019/120] add go mod support --- go.mod | 1 + 1 file changed, 1 insertion(+) create mode 100644 go.mod diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..214fb13 --- /dev/null +++ b/go.mod @@ -0,0 +1 @@ +module github.com/robotn/gohook From e36d1aac6c1a92555060e80659b6396d98ab4ec3 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sat, 15 Dec 2018 13:33:18 -0400 Subject: [PATCH 020/120] update appveyor to go1.11.4 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 5d7d76f..abe8c2a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.11.2 + GOVERSION: 1.11.4 # GOPATH: c:\gopath # scripts that run after cloning repository From f0ddfd620cc821577586e442a2d857c74cbad4fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Felchar?= <11652381+cauefcr@users.noreply.github.com> Date: Tue, 19 Feb 2019 11:31:25 -0300 Subject: [PATCH 021/120] Made the library Asyncronous (#2) * Part 1 of supporting async events made the dispatch process send events via json through a channel (the c kind), made another function that receives it and sends it trough another channel (the go kind) todo: remove usage of function-local data trough channel todo:find error that is causing the c channel to fill up? * Part 2 of making the lib async - New event struct mimiking C structs - changed c channel library to eb_chan - changed API to something more palatable * General cleanup * updated go.mod oopsie * Probably final touches i swear this time * Changed constants, keychar type and test function now Keychar is a rune, so it can be used as a regular character. * WIP adding better support for conversion between Scancodes and Keychars added table, will add rest of support soon(tm) * finished conversion table * Satisfied stringer interface for easier debug and logging, returned old function what the title says * Satisfied stringer interface for easier debug and logging, returned old function what the title says * forgot to import unsafe --- .gitignore | 1 + README.md | 22 +- chan/eb_chan.h | 1430 ++++++++++++++++++++++++++++++++++++++++ event/goEvent.h | 198 +++--- event/os.h | 2 +- extern.go | 33 + go.mod | 2 +- hook.go | 121 +++- hook/iohook.h | 2 +- hook/windows/input_c.h | 4 +- tables.go | 351 ++++++++++ test/main.go | 32 +- 12 files changed, 2058 insertions(+), 140 deletions(-) create mode 100644 chan/eb_chan.h create mode 100644 extern.go create mode 100644 tables.go diff --git a/.gitignore b/.gitignore index daf913b..e22c771 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ _testmain.go *.exe *.test *.prof +*.idea \ No newline at end of file diff --git a/README.md b/README.md index bd46bc6..80c632b 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,27 @@ # gohook -[![CircleCI Status](https://circleci.com/gh/robotn/gohook.svg?style=shield)](https://circleci.com/gh/robotn/gohook) -![Appveyor](https://ci.appveyor.com/api/projects/status/github/robotn/gohook?branch=master&svg=true) -[![Go Report Card](https://goreportcard.com/badge/github.com/robotn/gohook)](https://goreportcard.com/report/github.com/robotn/gohook) -[![GoDoc](https://godoc.org/github.com/robotn/gohook?status.svg)](https://godoc.org/github.com/robotn/gohook) +[![CircleCI Status](https://circleci.com/gh/cauefcr/gohook.svg?style=shield)](https://circleci.com/gh/cauefcr/gohook) +![Appveyor](https://ci.appveyor.com/api/projects/status/github/cauefcr/gohook?branch=master&svg=true) +[![Go Report Card](https://goreportcard.com/badge/github.com/cauefcr/gohook)](https://goreportcard.com/report/github.com/cauefcr/gohook) +[![GoDoc](https://godoc.org/github.com/cauefcr/gohook?status.svg)](https://godoc.org/github.com/cauefcr/gohook) +Based on [libuiohook](https://github.com/kwhat/libuiohook) + ```Go package main import ( "fmt" - - "github.com/robotn/gohook" + //"github.com/robotn/gohook" + "github.com/cauefcr/gohook" ) func main() { - // hook.AsyncHook() - veve := hook.AddEvent("v") - if veve == 0 { - fmt.Println("v...") + EvChan := hook.Start() + defer hook.End() + for ev := range EvChan { + fmt.Println(ev) } } ``` \ No newline at end of file diff --git a/chan/eb_chan.h b/chan/eb_chan.h new file mode 100644 index 0000000..be02996 --- /dev/null +++ b/chan/eb_chan.h @@ -0,0 +1,1430 @@ +// ####################################################### +// ## Generated by merge_src from the following files: +// ## eb_assert.c +// ## eb_assert.h +// ## eb_atomic.h +// ## eb_chan.c +// ## eb_chan.h +// ## eb_nsec.h +// ## eb_port.c +// ## eb_port.h +// ## eb_spinlock.h +// ## eb_sys.c +// ## eb_sys.h +// ## eb_time.c +// ## eb_time.h +// ####################################################### + +// ####################################################### +// ## eb_chan.h +// ####################################################### + +#ifndef EB_CHAN_H +#define EB_CHAN_H + +#include +#include +// ####################################################### +// ## eb_nsec.h +// ####################################################### + +#include + +typedef uint64_t eb_nsec; /* Units of nanoseconds */ +#define eb_nsec_zero UINT64_C(0) +#define eb_nsec_forever UINT64_MAX +#define eb_nsec_per_sec UINT64_C(1000000000) + +/* ## Types */ +typedef enum { + eb_chan_res_ok, /* Success */ + eb_chan_res_closed, /* Failed because the channel is closed */ + eb_chan_res_stalled, /* Failed because the send/recv couldn't proceed without blocking (applies to _try_send()/_try_recv()) */ +} eb_chan_res; + +typedef struct eb_chan *eb_chan; +typedef struct { + eb_chan chan; /* The applicable channel, where NULL channels block forever */ + bool send; /* True if sending, false if receiving */ + eb_chan_res res; /* _ok if the op completed due to a successful send/recv operation, _closed if the op completed because the channel is closed. */ + const void *val; /* The value to be sent/the value that was received */ +} eb_chan_op; + +/* ## Channel creation/lifecycle */ +eb_chan eb_chan_create(size_t buf_cap); +eb_chan eb_chan_retain(eb_chan c); +void eb_chan_release(eb_chan c); + +/* ## Channel closing */ +/* Returns _ok on success, or _closed if the channel was already closed. */ +eb_chan_res eb_chan_close(eb_chan c); + +/* ## Getters */ +size_t eb_chan_buf_cap(eb_chan c); +size_t eb_chan_buf_len(eb_chan c); + +/* ## Sending/receiving */ +/* Send/receive a value on a channel (where _send()/_recv() are blocking and _try_send()/_try_recv() are non-blocking) */ +eb_chan_res eb_chan_send(eb_chan c, const void *val); +eb_chan_res eb_chan_try_send(eb_chan c, const void *val); +eb_chan_res eb_chan_recv(eb_chan c, const void **val); +eb_chan_res eb_chan_try_recv(eb_chan c, const void **val); + +/* ## Multiplexing */ +/* _select_list() performs at most one of the operations in the supplied list, and returns the one that was performed. + It returns NULL if no operation was performed before the timeout. */ +eb_chan_op *eb_chan_select_list(eb_nsec timeout, eb_chan_op *const ops[], size_t nops); + +/* _select() is a convenience macro that wraps _select_list() to avoid having to manually create an array of ops on the stack. + For example: + eb_chan_op op1 = eb_chan_op_send(c1, NULL); + eb_chan_op op2 = eb_chan_op_recv(c2); + eb_chan_op *result = eb_chan_select(timeout, &op1, &op2); + ... +*/ +#define eb_chan_select(timeout, ...) ({ \ + eb_chan_op *const eb_chan_select_ops[] = {__VA_ARGS__}; \ + eb_chan_select_list(timeout, eb_chan_select_ops, (sizeof(eb_chan_select_ops) / sizeof(*eb_chan_select_ops))); \ +}) + +/* Return initialized send/recv ops for use with _select() */ +static inline eb_chan_op eb_chan_op_send(eb_chan c, const void *val) { + return (eb_chan_op){.chan = c, .send = true, .res = eb_chan_res_closed, .val = val}; +} + +static inline eb_chan_op eb_chan_op_recv(eb_chan c) { + return (eb_chan_op){.chan = c, .send = false, .res = eb_chan_res_closed, .val = NULL}; +} + +// ####################################################### +// ## eb_chan.c +// ####################################################### +#include +#include +#include +#include +#include +// ####################################################### +// ## eb_assert.h +// ####################################################### + +#include +#include + +#define eb_no_op + +#define eb_assert_or_recover(cond, action) ({ \ + if (!(cond)) { \ + eb_assert_print("Assertion failed", #cond, __FILE__, (uintmax_t)__LINE__, __PRETTY_FUNCTION__); \ + action; \ + } \ +}) + +#define eb_assert_or_bail(cond, msg) ({ \ + if (!(cond)) { \ + eb_assert_print(msg, #cond, __FILE__, (uintmax_t)__LINE__, __PRETTY_FUNCTION__); \ + abort(); \ + } \ +}) + +void eb_assert_print(const char *msg, const char *cond, const char *file, uintmax_t line, const char *func); +// ####################################################### +// ## eb_assert.c +// ####################################################### + +#include + +void eb_assert_print(const char *msg, const char *cond, const char *file, uintmax_t line, const char *func) { + fprintf(stderr, "=== %s ===\n" + " Assertion: %s\n" + " File: %s:%ju\n" + " Function: %s\n", msg, cond, file, line, func); +} +// ####################################################### +// ## eb_port.h +// ####################################################### + +#include +#include + +typedef struct eb_port *eb_port; + +eb_port eb_port_create(); +eb_port eb_port_retain(eb_port p); +void eb_port_release(eb_port p); + +void eb_port_signal(eb_port p); +bool eb_port_wait(eb_port p, eb_nsec timeout); +// ####################################################### +// ## eb_port.c +// ####################################################### + +#include +#include +#include +#include +// ####################################################### +// ## eb_sys.h +// ####################################################### + +#include + +#if __MACH__ + #define EB_SYS_DARWIN 1 +#elif __linux__ + #define EB_SYS_LINUX 1 +#else +// #error Unsupported system +#endif + +/* ## Variables */ +/* Returns the number of logical cores on the machine. _init must be called for this to be valid! */ +size_t eb_sys_ncores; + +/* ## Functions */ +void eb_sys_init(); +// ####################################################### +// ## eb_sys.c +// ####################################################### + +// ####################################################### +// ## eb_atomic.h +// ####################################################### + + +#define eb_atomic_add(ptr, delta) __sync_add_and_fetch(ptr, delta) /* Returns the new value */ +#define eb_atomic_compare_and_swap(ptr, old, new) __sync_bool_compare_and_swap(ptr, old, new) +#define eb_atomic_barrier() __sync_synchronize() + +#if EB_SYS_DARWIN + #include +#elif EB_SYS_LINUX + #include +#endif + +size_t ncores() { + #if EB_SYS_DARWIN + host_basic_info_data_t info; + mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; + kern_return_t r = host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)&info, &count); + eb_assert_or_recover(r == KERN_SUCCESS, return 0); + eb_assert_or_recover(count == HOST_BASIC_INFO_COUNT, return 0); + eb_assert_or_recover(info.logical_cpu > 0 && info.logical_cpu <= SIZE_MAX, return 0); + + return (size_t)info.logical_cpu; + #elif EB_SYS_LINUX + long ncores = sysconf(_SC_NPROCESSORS_ONLN); + eb_assert_or_recover(ncores > 0 && ncores <= SIZE_MAX, return 0); + + return (size_t)ncores; + #endif +} + +void eb_sys_init() { + if (!eb_sys_ncores) { + eb_atomic_compare_and_swap(&eb_sys_ncores, 0, ncores()); + } +} +#if EB_SYS_DARWIN + #include +#elif EB_SYS_LINUX + #include + #include +#endif +// ####################################################### +// ## eb_spinlock.h +// ####################################################### + +#include +#include + +/* ## Types */ +typedef int eb_spinlock; +#define EB_SPINLOCK_INIT 0 + +/* ## Functions */ +#define eb_spinlock_try(l) eb_atomic_compare_and_swap(l, 0, 1) + +#define eb_spinlock_lock(l) ({ \ + if (eb_sys_ncores > 1) { \ + while (!eb_spinlock_try(l)); \ + } else { \ + while (!eb_spinlock_try(l)) { \ + sched_yield(); \ + } \ + } \ +}) + +#define eb_spinlock_unlock(l) eb_atomic_compare_and_swap(l, 1, 0) + +//#define eb_spinlock_try(l) __sync_lock_test_and_set(l, 1) == 0 +//#define eb_spinlock_lock(l) while (!eb_spinlock_try(l)) +//#define eb_spinlock_unlock(l) __sync_lock_release(l) +// +//typedef OSSpinLock eb_spinlock; +//#define eb_spinlock_try(l) OSSpinLockTry(l) +//#define eb_spinlock_lock(l) OSSpinLockLock(l) +//#define eb_spinlock_unlock(l) OSSpinLockUnlock(l) +// ####################################################### +// ## eb_time.h +// ####################################################### + + +/* Returns the number of nanoseconds since an arbitrary point in time (usually the machine's boot time) */ +eb_nsec eb_time_now(); +// ####################################################### +// ## eb_time.c +// ####################################################### + +#include +#include +#if EB_SYS_DARWIN + #include +#elif EB_SYS_LINUX + #include +#endif + +eb_nsec eb_time_now() { +#if EB_SYS_DARWIN + /* Initialize k_timebase_info, thread-safely */ + static mach_timebase_info_t k_timebase_info = NULL; + if (!k_timebase_info) { + mach_timebase_info_t timebase_info = malloc(sizeof(*timebase_info)); + kern_return_t r = mach_timebase_info(timebase_info); + eb_assert_or_recover(r == KERN_SUCCESS, return 0); + + /* Make sure the writes to 'timebase_info' are complete before we assign k_timebase_info */ + eb_atomic_barrier(); + + if (!eb_atomic_compare_and_swap(&k_timebase_info, NULL, timebase_info)) { + free(timebase_info); + timebase_info = NULL; + } + } + + return ((mach_absolute_time() * k_timebase_info->numer) / k_timebase_info->denom); +#elif EB_SYS_LINUX + struct timespec ts; + int r = clock_gettime(CLOCK_MONOTONIC, &ts); + eb_assert_or_recover(!r, return 0); + + return ((uint64_t)ts.tv_sec * eb_nsec_per_sec) + ts.tv_nsec; +#endif +} + +#define PORT_POOL_CAP 0x10 +static eb_spinlock g_port_pool_lock = EB_SPINLOCK_INIT; +static eb_port g_port_pool[PORT_POOL_CAP]; +static size_t g_port_pool_len = 0; + +struct eb_port { + unsigned int retain_count; + bool sem_valid; + bool signaled; + #if EB_SYS_DARWIN + semaphore_t sem; + #elif EB_SYS_LINUX + sem_t sem; + #endif +}; + +static void eb_port_free(eb_port p) { + /* Allowing p==NULL so that this function can be called unconditionally on failure from eb_port_create() */ + if (!p) { + return; + } + + bool added_to_pool = false; + if (p->sem_valid) { + /* Determine whether we should clear the reset the port because we're going to try adding the port to our pool. */ + bool reset = false; + eb_spinlock_lock(&g_port_pool_lock); + reset = (g_port_pool_len < PORT_POOL_CAP); + eb_spinlock_unlock(&g_port_pool_lock); + + if (reset) { + eb_port_wait(p, eb_nsec_zero); + } + + /* Now that the port's reset, add it to the pool as long as it'll still fit. */ + eb_spinlock_lock(&g_port_pool_lock); + if (g_port_pool_len < PORT_POOL_CAP) { + g_port_pool[g_port_pool_len] = p; + g_port_pool_len++; + added_to_pool = true; + } + eb_spinlock_unlock(&g_port_pool_lock); + + /* If we couldn't add the port to the pool, destroy the underlying semaphore. */ + if (!added_to_pool) { + #if EB_SYS_DARWIN + kern_return_t r = semaphore_destroy(mach_task_self(), p->sem); + eb_assert_or_recover(r == KERN_SUCCESS, eb_no_op); + #elif EB_SYS_LINUX + int r = sem_destroy(&p->sem); + eb_assert_or_recover(!r, eb_no_op); + #endif + + p->sem_valid = false; + } + } + + if (!added_to_pool) { + free(p); + p = NULL; + } +} + +eb_port eb_port_create() { + eb_port p = NULL; + + /* First try to pop a port out of the pool */ + eb_spinlock_lock(&g_port_pool_lock); + if (g_port_pool_len) { + g_port_pool_len--; + p = g_port_pool[g_port_pool_len]; + } + eb_spinlock_unlock(&g_port_pool_lock); + + if (p) { + /* We successfully popped a port out of the pool */ + eb_assert_or_bail(!p->retain_count, "Sanity-check failed"); + } else { + /* We couldn't get a port out of the pool */ + /* Using calloc so that bytes are zeroed */ + p = calloc(1, sizeof(*p)); + eb_assert_or_recover(p, goto failed); + + /* Create the semaphore */ + #if EB_SYS_DARWIN + kern_return_t r = semaphore_create(mach_task_self(), &p->sem, SYNC_POLICY_FIFO, 0); + eb_assert_or_recover(r == KERN_SUCCESS, goto failed); + #elif EB_SYS_LINUX + int r = sem_init(&p->sem, 0, 0); + eb_assert_or_recover(!r, goto failed); + #endif + } + + p->sem_valid = true; + p->retain_count = 1; + return p; + failed: { + eb_port_free(p); + return NULL; + } +} + +eb_port eb_port_retain(eb_port p) { + assert(p); + eb_atomic_add(&p->retain_count, 1); + return p; +} + +void eb_port_release(eb_port p) { + assert(p); + if (eb_atomic_add(&p->retain_count, -1) == 0) { + eb_port_free(p); + } +} + +void eb_port_signal(eb_port p) { + assert(p); + + if (eb_atomic_compare_and_swap(&p->signaled, false, true)) { + #if EB_SYS_DARWIN + kern_return_t r = semaphore_signal(p->sem); + eb_assert_or_recover(r == KERN_SUCCESS, eb_no_op); + #elif EB_SYS_LINUX + int r = sem_post(&p->sem); + eb_assert_or_recover(!r, eb_no_op); + #endif + } +} + +bool eb_port_wait(eb_port p, eb_nsec timeout) { + assert(p); + + bool result = false; + if (timeout == eb_nsec_zero) { + /* ## Non-blocking */ + #if EB_SYS_DARWIN + kern_return_t r = semaphore_timedwait(p->sem, (mach_timespec_t){0, 0}); + eb_assert_or_recover(r == KERN_SUCCESS || r == KERN_OPERATION_TIMED_OUT, eb_no_op); + + result = (r == KERN_SUCCESS); + #elif EB_SYS_LINUX + int r = 0; + while ((r = sem_trywait(&p->sem)) == -1 && errno == EINTR); + eb_assert_or_recover(!r || (r == -1 && errno == EAGAIN), eb_no_op); + + result = !r; + #endif + } else if (timeout == eb_nsec_forever) { + /* ## Blocking */ + #if EB_SYS_DARWIN + kern_return_t r; + while ((r = semaphore_wait(p->sem)) == KERN_ABORTED); + eb_assert_or_recover(r == KERN_SUCCESS, eb_no_op); + + result = (r == KERN_SUCCESS); + #elif EB_SYS_LINUX + int r; + while ((r = sem_wait(&p->sem)) == -1 && errno == EINTR); + eb_assert_or_recover(!r, eb_no_op); + + result = !r; + #endif + } else { + /* ## Actual timeout */ + eb_nsec start_time = eb_time_now(); + eb_nsec remaining_timeout = timeout; + for (;;) { + #if EB_SYS_DARWIN + /* This needs to be in a loop because semaphore_timedwait() can return KERN_ABORTED, e.g. if the process receives a signal. */ + mach_timespec_t ts = {.tv_sec = (unsigned int)(remaining_timeout / eb_nsec_per_sec), .tv_nsec = (clock_res_t)(remaining_timeout % eb_nsec_per_sec)}; + kern_return_t r = semaphore_timedwait(p->sem, ts); + eb_assert_or_recover(r == KERN_SUCCESS || r == KERN_OPERATION_TIMED_OUT || r == KERN_ABORTED, eb_no_op); + + if (r == KERN_SUCCESS) { + result = true; + break; + } + #elif EB_SYS_LINUX + /* Because sem_timedwait() uses the system's _REALTIME clock instead of the _MONOTONIC clock, we'll time out when + the system's time changes. For that reason, we check for the timeout case ourself (instead of relying on errno + after calling sem_timedwait()) condition ourself, using our own monotonic clock APIs (eb_time_now()), and + restart sem_timedwait() if we determine independently that we haven't timed-out. */ + struct timespec ts; + int r = clock_gettime(CLOCK_REALTIME, &ts); + eb_assert_or_recover(!r, break); + + ts.tv_sec += (remaining_timeout / eb_nsec_per_sec); + ts.tv_nsec += (remaining_timeout % eb_nsec_per_sec); + r = sem_timedwait(&p->sem, &ts); + /* The allowed return cases are: success (r==0), timed-out (r==-1, errno==ETIMEDOUT), (r==-1, errno==EINTR) */ + eb_assert_or_recover(!r || (r == -1 && (errno == ETIMEDOUT || errno == EINTR)), break); + + /* If we acquired the semaphore, set our flag and break! */ + if (!r) { + result = true; + break; + } + #endif + + /* Determine whether we timed-out, and if not, update 'remaining_timeout' with the amount of time to go. */ + eb_nsec elapsed = eb_time_now() - start_time; + if (elapsed < timeout) { + remaining_timeout = timeout - elapsed; + } else { + break; + } + } + } + + if (result) { + assert(eb_atomic_compare_and_swap(&p->signaled, true, false)); + } + + return result; +} + +#pragma mark - Types - +typedef struct { + eb_spinlock lock; + size_t cap; + size_t len; + eb_port *ports; +} *port_list; + +static inline void port_list_free(port_list l); + +/* Creates a new empty list */ +static inline port_list port_list_alloc(size_t cap) { + assert(cap > 0); + + port_list result = malloc(sizeof(*result)); + eb_assert_or_recover(result, goto failed); + + result->lock = EB_SPINLOCK_INIT; + result->cap = cap; + result->len = 0; + result->ports = malloc(cap * sizeof(*(result->ports))); + eb_assert_or_recover(result->ports, goto failed); + + return result; + failed: { + port_list_free(result); + return NULL; + } +} + +/* Releases every port in the list, and frees the list itself */ +static inline void port_list_free(port_list l) { + /* Intentionally allowing l==NULL */ + if (!l) { + return; + } + + /* Release each port in our list */ + for (size_t i = 0; i < l->len; i++) { + eb_port_release(l->ports[i]); + } + + free(l->ports); + l->ports = NULL; + + free(l); + l = NULL; +} + +/* Add a port to the end of the list, expanding the buffer as necessary */ +static inline void port_list_add(port_list l, eb_port p) { + assert(l); + assert(p); + + /* First retain the port! */ + eb_port_retain(p); + + eb_spinlock_lock(&l->lock); + /* Sanity-check that the list's length is less than its capacity */ + eb_assert_or_bail(l->len <= l->cap, "Sanity check failed"); + + /* Expand the list's buffer if it's full */ + if (l->len == l->cap) { + l->cap *= 2; + // TODO: reimplement as a linked list, where the port nodes are just on the stacks of the _select_list() calls. that way the number of ports is unbounded, and we don't have to allocate anything on the heap! + l->ports = realloc(l->ports, l->cap * sizeof(*(l->ports))); + eb_assert_or_bail(l->ports, "Allocation failed"); + } + + l->ports[l->len] = p; + l->len++; + eb_spinlock_unlock(&l->lock); +} + +/* Remove the first occurence of 'p' in the list. Returns whether a port was actually removed. */ +static inline bool port_list_rm(port_list l, eb_port p) { + assert(l); + assert(p); + + bool result = false; + eb_spinlock_lock(&l->lock); + /* Sanity-check that the list's length is less than its capacity */ + eb_assert_or_bail(l->len <= l->cap, "Sanity-check failed"); + + /* Search for first occurence of the given port. If we find it, release it and move the last port in the list into the hole. */ + for (size_t i = 0; i < l->len; i++) { + if (l->ports[i] == p) { + /* Move the last element in the port list into the now-vacant spot */ + l->ports[i] = l->ports[l->len-1]; + /* Decrement the buffer length */ + l->len--; + result = true; + break; + } + } + eb_spinlock_unlock(&l->lock); + + if (result) { + /* Release the port, but do so outside of the spinlock because releasing does some stuff that might not be quick. */ + eb_port_release(p); + } + + return result; +} + +/* Signal the first port in the list that isn't 'ignore' */ +static inline void port_list_signal_first(const port_list l, eb_port ignore) { + assert(l); + + eb_port p = NULL; + eb_spinlock_lock(&l->lock); + for (size_t i = 0; i < l->len; i++) { + if (l->ports[i] != ignore) { + p = eb_port_retain(l->ports[i]); + break; + } + } + eb_spinlock_unlock(&l->lock); + + if (p) { + eb_port_signal(p); + eb_port_release(p); + p = NULL; + } +} + +enum { + /* Buffered/unbuffered channel states */ + chanstate_open, + chanstate_closed, + /* Unbuffered channel states */ + chanstate_send, + chanstate_recv, + chanstate_ack, + chanstate_done, + chanstate_cancelled +}; typedef int32_t chanstate; + +typedef struct { + eb_chan_op *const *ops; + size_t nops; + bool *cleanup_ops; + + eb_nsec timeout; + eb_port port; +} do_state; + +struct eb_chan { + unsigned int retain_count; + eb_spinlock lock; + chanstate state; + + port_list sends; + port_list recvs; + + /* Buffered ivars */ + size_t buf_cap; + size_t buf_len; + size_t buf_idx; + const void **buf; + + /* Unbuffered ivars */ + const do_state *unbuf_state; + eb_chan_op *unbuf_op; + eb_port unbuf_port; +}; + +#pragma mark - Channel creation/lifecycle - +static inline void eb_chan_free(eb_chan c) { + /* Intentionally allowing c==NULL so that this function can be called from eb_chan_create() */ + if (!c) { + return; + } + + if (c->buf_cap) { + /* ## Buffered */ + free(c->buf); + c->buf = NULL; + } + + port_list_free(c->recvs); + c->recvs = NULL; + + port_list_free(c->sends); + c->sends = NULL; + + free(c); + c = NULL; +} + +eb_chan eb_chan_create(size_t buf_cap) { + static const size_t k_init_buf_cap = 16; + + /* Initialize eb_sys so that eb_sys_ncores is valid. */ + eb_sys_init(); + + /* Using calloc so that the bytes are zeroed. */ + eb_chan c = calloc(1, sizeof(*c)); + eb_assert_or_recover(c, goto failed); + + c->retain_count = 1; + c->lock = EB_SPINLOCK_INIT; + c->state = chanstate_open; + + c->sends = port_list_alloc(k_init_buf_cap); + eb_assert_or_recover(c->sends, goto failed); + c->recvs = port_list_alloc(k_init_buf_cap); + eb_assert_or_recover(c->recvs, goto failed); + + if (buf_cap) { + /* ## Buffered */ + c->buf_cap = buf_cap; + c->buf_len = 0; + c->buf_idx = 0; + c->buf = malloc(c->buf_cap * sizeof(*(c->buf))); + eb_assert_or_recover(c->buf, goto failed); + } else { + /* ## Unbuffered */ + c->unbuf_state = NULL; + c->unbuf_op = NULL; + c->unbuf_port = NULL; + } + + /* Issue a memory barrier since we didn't have the lock acquired for our set up (and this channel could theoretically + be passed to another thread without a barrier, and that'd be bad news...) */ + eb_atomic_barrier(); + + return c; + failed: { + eb_chan_free(c); + return NULL; + } +} + +eb_chan eb_chan_retain(eb_chan c) { + assert(c); + eb_atomic_add(&c->retain_count, 1); + return c; +} + +void eb_chan_release(eb_chan c) { + assert(c); + if (eb_atomic_add(&c->retain_count, -1) == 0) { + eb_chan_free(c); + } +} + +#pragma mark - Channel closing - +eb_chan_res eb_chan_close(eb_chan c) { + assert(c); + + eb_chan_res result = eb_chan_res_stalled; + while (result == eb_chan_res_stalled) { + eb_port signal_port = NULL; + eb_spinlock_lock(&c->lock); + if (c->state == chanstate_open) { + c->state = chanstate_closed; + result = eb_chan_res_ok; + } else if (c->state == chanstate_closed) { + result = eb_chan_res_closed; + } else if (c->state == chanstate_send || c->state == chanstate_recv) { + if (c->unbuf_port) { + signal_port = eb_port_retain(c->unbuf_port); + } + c->state = chanstate_closed; + result = eb_chan_res_ok; + } + eb_spinlock_unlock(&c->lock); + + /* Wake up the send/recv */ + if (signal_port) { + eb_port_signal(signal_port); + eb_port_release(signal_port); + signal_port = NULL; + } + } + + if (result == eb_chan_res_ok) { + /* Wake up the sends/recvs so that they see the channel's now closed */ + port_list_signal_first(c->sends, NULL); + port_list_signal_first(c->recvs, NULL); + } + + return result; +} + +#pragma mark - Getters - +size_t eb_chan_buf_cap(eb_chan c) { + assert(c); + return c->buf_cap; +} + +size_t eb_chan_buf_len(eb_chan c) { + assert(c); + + /* buf_len is only valid if the channel's buffered */ + if (!c->buf_cap) { + return 0; + } + + size_t r = 0; + eb_spinlock_lock(&c->lock); + r = c->buf_len; + eb_spinlock_unlock(&c->lock); + return r; +} + +#pragma mark - Performing operations - +enum { + op_result_complete, /* The op completed and the caller should return */ + op_result_next, /* The op couldn't make any progress and the caller should move on to the next op */ + op_result_retry, /* The channel's busy and we should try the op again */ +}; typedef unsigned int op_result; + +static inline void cleanup_ops(const do_state *state) { + assert(state); + + for (size_t i = 0; i < state->nops; i++) { + if (state->cleanup_ops[i]) { + eb_chan_op *op = state->ops[i]; + eb_chan c = op->chan; + bool signal_send = false; + bool signal_recv = false; + eb_spinlock_lock(&c->lock); + if (c->state == chanstate_send && c->unbuf_op == op) { + /* 'op' was in the process of an unbuffered send on the channel, but no recv had arrived + yet, so reset state to _open. */ + c->state = chanstate_open; + signal_send = true; + } else if (c->state == chanstate_recv && c->unbuf_op == op) { + /* 'op' was in the process of an unbuffered recv on the channel, but no send had arrived + yet, so reset state to _open. */ + c->state = chanstate_open; + signal_recv = true; + } else if (c->state == chanstate_ack && c->unbuf_op == op) { + /* A counterpart acknowledged 'op' but, but 'op' isn't the one that completed in our select() call, so we're cancelling. */ + c->state = chanstate_cancelled; + } + eb_spinlock_unlock(&c->lock); + + if (signal_send) { + port_list_signal_first(c->sends, state->port); + } + + if (signal_recv) { + port_list_signal_first(c->recvs, state->port); + } + + state->cleanup_ops[i] = false; + } + } +} + +static inline op_result send_buf(const do_state *state, eb_chan_op *op, size_t op_idx) { + assert(state); + assert(op); + assert(op->chan); + + eb_chan c = op->chan; + op_result result = op_result_next; + + if (c->buf_len < c->buf_cap || c->state == chanstate_closed) { + /* It looks like our channel's in an acceptable state, so try to acquire the lock */ + if (eb_spinlock_try(&c->lock)) { + /* Sanity-check the channel's state */ + eb_assert_or_bail(c->state == chanstate_open || c->state == chanstate_closed, "Invalid channel state"); + + bool signal_recv = false; + if (c->state == chanstate_closed) { + /* ## Sending, buffered, channel closed */ + /* Set our op's state and our return value */ + op->res = eb_chan_res_closed; + result = op_result_complete; + } else if (c->buf_len < c->buf_cap) { + /* ## Sending, buffered, channel open, buffer has space */ + /* Notify the channel's recvs if our buffer is going from empty to non-empty */ + signal_recv = (!c->buf_len); + /* Add the value to the buffer */ + size_t idx = (c->buf_idx + c->buf_len) % c->buf_cap; + c->buf[idx] = op->val; + c->buf_len++; + /* Set our op's state and our return value */ + op->res = eb_chan_res_ok; + result = op_result_complete; + } + + eb_spinlock_unlock(&c->lock); + + if (signal_recv) { + port_list_signal_first(c->recvs, state->port); + } + } else { + result = op_result_retry; + } + } + + return result; +} + +static inline op_result recv_buf(const do_state *state, eb_chan_op *op, size_t op_idx) { + assert(state); + assert(op); + assert(op->chan); + + eb_chan c = op->chan; + op_result result = op_result_next; + + if (c->buf_len || c->state == chanstate_closed) { + if (eb_spinlock_try(&c->lock)) { + /* Sanity-check the channel's state */ + eb_assert_or_bail(c->state == chanstate_open || c->state == chanstate_closed, "Invalid channel state"); + + bool signal_send = false; + if (c->buf_len) { + /* ## Receiving, buffered, buffer non-empty */ + /* Notify the channel's sends if our buffer is going from full to not-full */ + signal_send = (c->buf_len == c->buf_cap); + /* Set our op's state and our return value */ + op->res = eb_chan_res_ok; + op->val = c->buf[c->buf_idx]; + result = op_result_complete; + /* Update chan's buffer. (Updating buf_idx needs to come after we use it!) */ + c->buf_len--; + c->buf_idx = (c->buf_idx + 1) % c->buf_cap; + } else if (c->state == chanstate_closed) { + /* ## Receiving, buffered, buffer empty, channel closed */ + /* Set our op's state and our return value */ + op->res = eb_chan_res_closed; + op->val = NULL; + result = op_result_complete; + } + + eb_spinlock_unlock(&c->lock); + + if (signal_send) { + port_list_signal_first(c->sends, state->port); + } + } else { + result = op_result_retry; + } + } + + return result; +} + +static inline op_result send_unbuf(const do_state *state, eb_chan_op *op, size_t op_idx) { + assert(state); + assert(op); + assert(op->chan); + + eb_chan c = op->chan; + op_result result = op_result_next; + + if ((c->state == chanstate_open && state->timeout != eb_nsec_zero) || + c->state == chanstate_closed || + (c->state == chanstate_send && c->unbuf_op == op) || + (c->state == chanstate_recv && c->unbuf_state != state) || + (c->state == chanstate_ack && c->unbuf_op == op)) { + + /* It looks like our channel's in an acceptable state, so try to acquire the lock */ + if (eb_spinlock_try(&c->lock)) { + /* Reset the cleanup state since we acquired the lock and are actually getting a look at the channel's state */ + state->cleanup_ops[op_idx] = false; + + bool signal_recv = false; + if (c->state == chanstate_open && state->timeout != eb_nsec_zero) { + c->state = chanstate_send; + c->unbuf_state = state; + c->unbuf_op = op; + c->unbuf_port = state->port; + /* We need to cleanup after this since we put it in the _send state! */ + state->cleanup_ops[op_idx] = true; + /* Signal a recv since one of them can continue now */ + signal_recv = true; + } else if (c->state == chanstate_closed) { + /* Set our op's state and our return value */ + op->res = eb_chan_res_closed; + result = op_result_complete; + } else if (c->state == chanstate_send && c->unbuf_op == op) { + /* We own the send op that's in progress, so assign chan's unbuf_port */ + /* Verify that the unbuf_state matches our 'id' parameter. If this assertion fails, it means there's likely + one eb_chan_op being shared by multiple threads, which isn't allowed. */ + eb_assert_or_bail(c->unbuf_state == state, "unbuf_state invalid"); + /* Assign the port */ + c->unbuf_port = state->port; + /* We need to cleanup after this since we put it in the _send state! */ + state->cleanup_ops[op_idx] = true; + } else if (c->state == chanstate_recv && c->unbuf_state != state) { + /* We verified (immediately above) that the recv isn't part of the same op pool (we can't do unbuffered + sends/recvs from the same _do() call) */ + + /* Sanity check -- make sure the op is a recv */ + eb_assert_or_bail(!c->unbuf_op->send, "Op isn't a recv as expected"); + + /* Set the recv op's value. This needs to happen before we transition out of the _recv state, otherwise the unbuf_op may no longer be valid! */ + c->unbuf_op->val = op->val; + /* Acknowledge the receive */ + c->state = chanstate_ack; + /* Get a reference to the unbuf_port that needs to be signaled */ + eb_port signal_port = (c->unbuf_port ? eb_port_retain(c->unbuf_port) : NULL); + eb_spinlock_unlock(&c->lock); + + /* Wake up the recv */ + if (signal_port) { + eb_port_signal(signal_port); + eb_port_release(signal_port); + signal_port = NULL; + } + + /* We have to cleanup all our ops here to cancel any outstanding unbuffered send/recvs, to avoid a deadlock + situation that arises when another _do() is waiting on our _do() to complete, but it never does because + we're about to wait for the other _do() to complete. */ + cleanup_ops(state); + + for (;;) { + if (*((volatile chanstate *)&c->state) != chanstate_ack) { + eb_spinlock_lock(&c->lock); + if (c->state == chanstate_done) { + /* Reset the channel state back to _open */ + c->state = chanstate_open; + /* We reset our state to _open, so signal a send since it can proceed now. */ + signal_recv = true; + /* Set our op's state and our return value */ + op->res = eb_chan_res_ok; + result = op_result_complete; + /* Breaking here so that we skip the _unlock() call, because we unlock the spinlock outside + of our large if-statement. */ + break; + } else if (c->state == chanstate_cancelled) { + /* Reset the channel state back to _open */ + c->state = chanstate_open; + /* As long as we're not polling, we should try the op again */ + if (state->timeout != eb_nsec_zero) { + result = op_result_retry; + } else { + /* We're not telling the caller to retry, so signal a send since it can proceed now. */ + signal_recv = true; + } + /* Breaking here so that we skip the _unlock() call, because we unlock the spinlock outside + of our large if-statement. */ + break; + } + eb_spinlock_unlock(&c->lock); + } else if (eb_sys_ncores == 1) { + /* On uniprocessor machines, yield to the scheduler because we can't continue until another + thread updates the channel's state. */ + sched_yield(); + } + } + } else if (c->state == chanstate_ack && c->unbuf_op == op) { + /* A recv acknowledged our send! */ + /* Verify that the unbuf_state matches our 'id' parameter. If this assertion fails, it means there's likely + one eb_chan_op being shared by multiple threads, which isn't allowed. */ + eb_assert_or_bail(c->unbuf_state == state, "unbuf_state invalid"); + /* A recv is polling for chan's state to change, so update it to signal that we're done sending! */ + c->state = chanstate_done; + /* Set our op's state and our return value */ + op->res = eb_chan_res_ok; + result = op_result_complete; + } + + eb_spinlock_unlock(&c->lock); + + if (signal_recv) { + port_list_signal_first(c->recvs, state->port); + } + } else { + result = op_result_retry; + } + } + + return result; +} + +static inline op_result recv_unbuf(const do_state *state, eb_chan_op *op, size_t op_idx) { + assert(state); + assert(op); + assert(op->chan); + + eb_chan c = op->chan; + op_result result = op_result_next; + + if ((c->state == chanstate_open && state->timeout != eb_nsec_zero) || + c->state == chanstate_closed || + (c->state == chanstate_send && c->unbuf_state != state) || + (c->state == chanstate_recv && c->unbuf_op == op) || + (c->state == chanstate_ack && c->unbuf_op == op)) { + + /* It looks like our channel's in an acceptable state, so try to acquire the lock */ + if (eb_spinlock_try(&c->lock)) { + /* Reset the cleanup state since we acquired the lock and are actually getting a look at the channel's state */ + state->cleanup_ops[op_idx] = false; + + bool signal_send = false; + if (c->state == chanstate_open && state->timeout != eb_nsec_zero) { + c->state = chanstate_recv; + c->unbuf_state = state; + c->unbuf_op = op; + c->unbuf_port = state->port; + /* We need to cleanup after this since we put it in the _send state! */ + state->cleanup_ops[op_idx] = true; + /* Signal a send since one of them can continue now */ + signal_send = true; + } else if (c->state == chanstate_closed) { + /* Set our op's state and our return value */ + op->res = eb_chan_res_closed; + op->val = NULL; + result = op_result_complete; + } else if (c->state == chanstate_send && c->unbuf_state != state) { + /* We verified (immediately above) that the send isn't part of the same op pool (we can't do unbuffered + sends/recvs from the same _do() call) */ + + /* Sanity check -- make sure the op is a send */ + eb_assert_or_bail(c->unbuf_op->send, "Op isn't a send as expected"); + + /* Get the op's value. This needs to happen before we transition out of the _send state, otherwise the unbuf_op may no longer be valid! */ + op->val = c->unbuf_op->val; + /* Acknowledge the send */ + c->state = chanstate_ack; + /* Get a reference to the unbuf_port that needs to be signaled */ + eb_port signal_port = (c->unbuf_port ? eb_port_retain(c->unbuf_port) : NULL); + eb_spinlock_unlock(&c->lock); + + /* Wake up the send */ + if (signal_port) { + eb_port_signal(signal_port); + eb_port_release(signal_port); + signal_port = NULL; + } + + /* We have to cleanup all our ops here to cancel any outstanding unbuffered send/recvs, to avoid a deadlock + situation that arises when another _do() is waiting on our _do() to complete, but it never does because + we're about to wait for the other _do() to complete. */ + cleanup_ops(state); + + for (;;) { + if (*((volatile chanstate *)&c->state) != chanstate_ack) { + eb_spinlock_lock(&c->lock); + if (c->state == chanstate_done) { + /* Reset the channel state back to _open */ + c->state = chanstate_open; + /* We reset our state to _open, so signal a recv since it can proceed now. */ + signal_send = true; + /* Set our op's state and our return value */ + op->res = eb_chan_res_ok; + result = op_result_complete; + /* Breaking here so that we skip the _unlock() call, because we unlock the spinlock outside + of our large if-statement. */ + break; + } else if (c->state == chanstate_cancelled) { + /* Reset the channel state back to _open */ + c->state = chanstate_open; + /* As long as we're not polling, we should try the op again */ + if (state->timeout != eb_nsec_zero) { + result = op_result_retry; + } else { + /* We're not telling the caller to retry, so signal a recv since it can proceed now. */ + signal_send = true; + } + /* Breaking here so that we skip the _unlock() call, because we unlock the spinlock outside + of our large if-statement. */ + break; + } + eb_spinlock_unlock(&c->lock); + } else if (eb_sys_ncores == 1) { + /* On uniprocessor machines, yield to the scheduler because we can't continue until another + thread updates the channel's state. */ + sched_yield(); + } + } + } else if (c->state == chanstate_recv && c->unbuf_op == op) { + /* We own the recv op that's in progress, so assign chan's unbuf_port */ + /* Verify that the _recv_id matches our 'id' parameter. If this assertion fails, it means there's likely + one eb_chan_op being shared by multiple threads, which isn't allowed. */ + eb_assert_or_bail(c->unbuf_state == state, "unbuf_state invalid"); + /* Assign the port */ + c->unbuf_port = state->port; + /* We need to cleanup after this since we put it in the _send state! */ + state->cleanup_ops[op_idx] = true; + } else if (c->state == chanstate_ack && c->unbuf_op == op) { + /* A send acknowledged our recv! */ + /* Verify that the unbuf_state matches our 'id' parameter. If this assertion fails, it means there's likely + one eb_chan_op being shared by multiple threads, which isn't allowed. */ + eb_assert_or_bail(c->unbuf_state == state, "unbuf_state invalid"); + /* A send is polling for chan's state to change, so update it to signal that we're done sending! */ + c->state = chanstate_done; + /* Set our op's state and our return value */ + op->res = eb_chan_res_ok; + result = op_result_complete; + } + + eb_spinlock_unlock(&c->lock); + + if (signal_send) { + port_list_signal_first(c->sends, state->port); + } + } else { + result = op_result_retry; + } + } + + return result; +} + +static inline op_result try_op(const do_state *state, eb_chan_op *op, size_t op_idx) { + assert(state); + assert(op); + + eb_chan c = op->chan; + if (c) { + if (op->send) { + /* ## Send */ + return (c->buf_cap ? send_buf(state, op, op_idx) : send_unbuf(state, op, op_idx)); + } else { + /* ## Receive */ + return (c->buf_cap ? recv_buf(state, op, op_idx) : recv_unbuf(state, op, op_idx)); + } + } + return op_result_next; +} + +eb_chan_res eb_chan_send(eb_chan c, const void *val) { + eb_chan_op op = eb_chan_op_send(c, val); + eb_assert_or_bail(eb_chan_select(eb_nsec_forever, &op) == &op, "Invalid select() return value"); + return op.res; +} + +eb_chan_res eb_chan_try_send(eb_chan c, const void *val) { + eb_chan_op op = eb_chan_op_send(c, val); + eb_chan_op *r = eb_chan_select(eb_nsec_zero, &op); + eb_assert_or_bail(r == NULL || r == &op, "Invalid select() return value"); + return (r ? op.res : eb_chan_res_stalled); +} + +eb_chan_res eb_chan_recv(eb_chan c, const void **val) { + eb_chan_op op = eb_chan_op_recv(c); + eb_assert_or_bail(eb_chan_select(eb_nsec_forever, &op) == &op, "Invalid select() return value"); + if (op.res == eb_chan_res_ok && val) { + *val = op.val; + } + return op.res; +} + +eb_chan_res eb_chan_try_recv(eb_chan c, const void **val) { + eb_chan_op op = eb_chan_op_recv(c); + eb_chan_op *r = eb_chan_select(eb_nsec_zero, &op); + eb_assert_or_bail(r == NULL || r == &op, "Invalid select() return value"); + if (r && op.res == eb_chan_res_ok && val) { + *val = op.val; + } + return (r ? op.res : eb_chan_res_stalled); +} + +#pragma mark - Multiplexing - +#define next_idx(nops, delta, idx) (delta == 1 && idx == nops-1 ? 0 : ((delta == -1 && idx == 0) ? nops-1 : idx+delta)) +eb_chan_op *eb_chan_select_list(eb_nsec timeout, eb_chan_op *const ops[], size_t nops) { + assert(!nops || ops); + + const size_t k_attempt_multiplier = (eb_sys_ncores == 1 ? 1 : 500); + eb_nsec start_time = 0; + size_t idx_start = 0; + int8_t idx_delta = 0; + if (nops > 1) { + /* Assign idx_start/idx_delta, which control the op pseudo-randomization */ + start_time = eb_time_now(); + idx_start = (start_time/1000)%nops; + idx_delta = (!((start_time/10000)%2) ? 1 : -1); + } + + bool co[nops]; + memset(co, 0, sizeof(co)); + + eb_chan_op *result = NULL; + do_state state = { + .ops = ops, + .nops = nops, + .cleanup_ops = co, + .timeout = timeout, + .port = NULL}; + + if (timeout == eb_nsec_zero) { + /* ## timeout == 0: try every op exactly once; if none of them can proceed, return NULL. */ + for (size_t i = 0, idx = idx_start; i < nops; i++, idx = next_idx(nops, idx_delta, idx)) { + eb_chan_op *op = ops[idx]; + op_result r; + while ((r = try_op(&state, op, idx)) == op_result_retry) { + if (eb_sys_ncores == 1) { + /* On uniprocessor machines, yield to the scheduler because we can't continue until another + thread updates the channel's state. */ + sched_yield(); + } + } + + /* If the op completed, we need to exit! */ + if (r == op_result_complete) { + result = op; + goto cleanup; + } + } + } else { + /* ## timeout != 0 */ + if (timeout != eb_nsec_forever && !start_time) { + start_time = eb_time_now(); + } + + for (;;) { + /* ## Fast path: loop over our operations to see if one of them was able to send/receive. (If not, + we'll enter the slow path where we put our thread to sleep until we're signaled.) */ + for (size_t i = 0, idx = idx_start; i < k_attempt_multiplier*nops; i++, idx = next_idx(nops, idx_delta, idx)) { + eb_chan_op *op = ops[idx]; + op_result r = try_op(&state, op, idx); + /* If the op completed, we need to exit! */ + if (r == op_result_complete) { + result = op; + goto cleanup; + } + } + + /* ## Slow path: we weren't able to find an operation that could send/receive, so we'll create a + port to receive notifications on and put this thread to sleep until someone wakes us up. */ + if (!state.port) { + /* Create our port that we'll attach to channels so that we can be notified when events occur. */ + state.port = eb_port_create(); + eb_assert_or_recover(state.port, goto cleanup); + + /* Register our port for the appropriate notifications on every channel. */ + /* This adds 'port' to the channel's sends/recvs (depending on the op), which we clean up at the + end of this function. */ + for (size_t i = 0; i < nops; i++) { + eb_chan_op *op = ops[i]; + eb_chan c = op->chan; + if (c) { + port_list_add((op->send ? c->sends : c->recvs), state.port); + } + } + } + + /* Before we go to sleep, call try_op() for every op until we get a non-busy return value. This way we'll ensure + that no op is actually able to be performed, and we'll also ensure that 'port' is registered as the 'unbuf_port' + for the necessary channels. */ + for (size_t i = 0, idx = idx_start; i < nops; i++, idx = next_idx(nops, idx_delta, idx)) { + eb_chan_op *op = ops[idx]; + op_result r; + while ((r = try_op(&state, op, idx)) == op_result_retry) { + if (eb_sys_ncores == 1) { + /* On uniprocessor machines, yield to the scheduler because we can't continue until another + thread updates the channel's state. */ + sched_yield(); + } + } + + /* If the op completed, we need to exit! */ + if (r == op_result_complete) { + result = op; + goto cleanup; + } + } + + eb_nsec wait_timeout = eb_nsec_forever; + if (timeout != eb_nsec_forever) { + /* If we have a timeout, determine how much time has elapsed, because we may have timed-out. */ + eb_nsec elapsed = eb_time_now() - start_time; + /* Check if we timed-out */ + if (elapsed < timeout) { + wait_timeout = timeout - elapsed; + } else { + goto cleanup; + } + } + + /* Put our thread to sleep until someone alerts us of an event */ + eb_port_wait(state.port, wait_timeout); + } + } + + /* Cleanup! */ + cleanup: { + if (state.port) { + for (size_t i = 0; i < nops; i++) { + eb_chan_op *op = ops[i]; + eb_chan c = op->chan; + if (c) { + port_list ports = (op->send ? c->sends : c->recvs); + port_list_rm(ports, state.port); + port_list_signal_first(ports, state.port); + } + } + } + + cleanup_ops(&state); + + if (state.port) { + eb_port_release(state.port); + state.port = NULL; + } + } + + return result; +} +#endif /* EB_CHAN_H */ \ No newline at end of file diff --git a/event/goEvent.h b/event/goEvent.h index acd41cc..b0574e1 100644 --- a/event/goEvent.h +++ b/event/goEvent.h @@ -7,146 +7,116 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. - +#ifndef goevent_h +#define goevent_h #ifdef HAVE_CONFIG_H #include #endif #include #include "pub.h" +#include "../chan/eb_chan.h" +eb_chan events; +void go_send(char*); +void go_sleep(void); + +bool sending = false; + +void startev(){ + events = eb_chan_create(1024); + eb_chan_retain(events); + sending = true; + add_event("q"); +} + +void pollEv(){ + if(events == NULL) return; + for(;eb_chan_buf_len(events)!=0;){ + char* tmp; + if(eb_chan_try_recv(events,(const void**) &tmp) == eb_chan_res_ok){ + go_send(tmp); + free(tmp); + }else{ + // + } + } +} + +void endPoll(){ + sending = false; + pollEv();//remove last things from channel + eb_chan_release(events); +} void dispatch_proc(iohook_event * const event) { - char buffer[256] = { 0 }; - size_t length = snprintf(buffer, sizeof(buffer), - "id=%i,when=%" PRIu64 ",mask=0x%X", - event->type, event->time, event->mask); + if(!sending) return; +//leaking memory? hope not + char* buffer = calloc(200,sizeof(char)); switch (event->type) { + case EVENT_HOOK_ENABLED: + case EVENT_HOOK_DISABLED: + sprintf(buffer,"{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu}", + event->type, event->time, event->mask,event->reserved); + break;//send it? case EVENT_KEY_PRESSED: - // If the escape key is pressed, naturally terminate the program. - if (event->data.keyboard.keycode == VC_ESCAPE) { - // int status = hook_stop(); - // switch (status) { - // // System level errors. - // case IOHOOK_ERROR_OUT_OF_MEMORY: - // loggerProc(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); - // break; - - // case IOHOOK_ERROR_X_RECORD_GET_CONTEXT: - // // NOTE This is the only platform specific error that occurs on hook_stop(). - // loggerProc(LOG_LEVEL_ERROR, "Failed to get XRecord context. (%#X)", status); - // break; - - // // Default error. - // case IOHOOK_FAILURE: - // default: - // loggerProc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); - // break; - // } - } case EVENT_KEY_RELEASED: - snprintf(buffer + length, sizeof(buffer) - length, - ",keycode=%u,rawcode=0x%X", - event->data.keyboard.keycode, event->data.keyboard.rawcode); - int key_code = (uint16_t) event->data.keyboard.keycode; - - if (event->data.keyboard.keycode == VC_ESCAPE - && atoi(cevent) == 11) { - int stopEvent = stop_event(); - // printf("stop_event%d\n", stopEvent); - cstatus = 0; - } - - // printf("atoi(str)---%d\n", atoi(cevent)); - if (key_code == atoi(cevent)) { - int stopEvent = stop_event(); - // printf("%d\n", stopEvent); - cstatus = 0; - } - break; - case EVENT_KEY_TYPED: - snprintf(buffer + length, sizeof(buffer) - length, - ",keychar=%lc,rawcode=%u", - (uint16_t) event->data.keyboard.keychar, - event->data.keyboard.rawcode); - - #ifdef WE_REALLY_WANT_A_POINTER - char *buf = malloc (6); - #else - char buf[6]; - #endif - - sprintf(buf, "%lc", (uint16_t) event->data.keyboard.keychar); - - #ifdef WE_REALLY_WANT_A_POINTER - free (buf); - #endif - - if (strcmp(buf, cevent) == 0) { - int stopEvent = stop_event(); - // printf("%d\n", stopEvent); - cstatus = 0; - } - // return (char*) event->data.keyboard.keychar; - break; - + sprintf(buffer, + "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"keycode\":%hu,\"rawcode\":%hu,\"keychar\":%hu}", + event->type, event->time, event->mask,event->reserved, + event->data.keyboard.keycode, + event->data.keyboard.rawcode, + event->data.keyboard.keychar); + break; case EVENT_MOUSE_PRESSED: case EVENT_MOUSE_RELEASED: case EVENT_MOUSE_CLICKED: case EVENT_MOUSE_MOVED: case EVENT_MOUSE_DRAGGED: - snprintf(buffer + length, sizeof(buffer) - length, - ",x=%i,y=%i,button=%i,clicks=%i", - event->data.mouse.x, event->data.mouse.y, - event->data.mouse.button, event->data.mouse.clicks); - - int abutton = event->data.mouse.button; - int aclicks = event->data.mouse.clicks; - int amouse = -1; - - if (strcmp(cevent, "mleft") == 0) { - amouse = 1; - } - if (strcmp(cevent, "mright") == 0) { - amouse = 2; - } - if (strcmp(cevent, "wheelDown") == 0) { - amouse = 4; - } - if (strcmp(cevent, "wheelUp") == 0) { - amouse = 5; - } - if (strcmp(cevent, "wheelLeft") == 0) { - amouse = 6; - } - if (strcmp(cevent, "wheelRight") == 0) { - amouse = 7; - } - if (abutton == amouse && aclicks == 1) { - int stopEvent = stop_event(); - cstatus = 0; - } - + sprintf(buffer, + "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"x\":%hd,\"y\":%hd,\"button\":%u,\"clicks\":%u}", + event->type, event->time, event->mask,event->reserved, + event->data.mouse.x, + event->data.mouse.y, + event->data.mouse.button, + event->data.mouse.clicks); break; - case EVENT_MOUSE_WHEEL: - snprintf(buffer + length, sizeof(buffer) - length, - ",type=%i,amount=%i,rotation=%i", - event->data.wheel.type, event->data.wheel.amount, - event->data.wheel.rotation); + sprintf(buffer, + "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"clicks\":%hu,\"x\":%hd,\"y\":%hd,\"type\":%hu,\"ammount\":%hu,\"rotation\":%hd,\"direction\":%hu}", + event->type, event->time, event->mask, event->reserved, + event->data.wheel.clicks, + event->data.wheel.x, + event->data.wheel.y, + event->data.wheel.type, + event->data.wheel.amount, + event->data.wheel.rotation, + event->data.wheel.direction); break; - default: - break; + fprintf(stderr,"\nError on file: %s, unusual event->type: %i\n",__FILE__,event->type); + return; } + //to-do remove this for + for(int i = 0; i < 5; i++){ + switch(eb_chan_try_send(events,buffer)){ //never block the hook callback + case eb_chan_res_ok: + i=5; + break; + default: + if (i == 4) {//let's not leak memory + free(buffer); + } + continue; + } + } // fprintf(stdout, "----%s\n", buffer); } int add_event(char *key_event) { - // (uint16_t *) cevent = key_event; // Set the logger callback for library output. hook_set_logger(&loggerProc); @@ -242,7 +212,7 @@ int stop_event(){ loggerProc(LOG_LEVEL_ERROR, "Failed to get XRecord context. (%#X)", status); break; - // Default error. + // Default error. case IOHOOK_FAILURE: default: // loggerProc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); @@ -250,4 +220,6 @@ int stop_event(){ } return status; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/event/os.h b/event/os.h index ab46989..60a4d86 100644 --- a/event/os.h +++ b/event/os.h @@ -23,7 +23,7 @@ #endif /* USE_X11 */ #if defined(IS_WINDOWS) - #define STRICT /* Require use of exact types. */ +// #define STRICT /* Require use of exact types. */ #define WIN32_LEAN_AND_MEAN 1 /* Speed up compilation. */ #include #elif !defined(IS_MACOSX) && !defined(USE_X11) diff --git a/extern.go b/extern.go new file mode 100644 index 0000000..631aeb2 --- /dev/null +++ b/extern.go @@ -0,0 +1,33 @@ +package hook + +/* + + +// #include "event/hook_async.h" +*/ +import "C" +import ( + "encoding/json" + "log" + "time" +) + +//export go_send +func go_send(s *C.char) { + str := []byte(C.GoString(s)) + out := Event{} + err := json.Unmarshal(str, &out) + if err != nil { + log.Fatal(err) + } + if out.Keychar != CharUndefined { + raw2key[out.Rawcode] = string([]rune{out.Keychar}) + } + //todo bury this deep into the C lib so that the time is correct + out.When = time.Now() //at least it's consistent + if err != nil { + log.Fatal(err) + } + //todo: maybe make non-bloking + ev <- out +} diff --git a/go.mod b/go.mod index 214fb13..aa65700 100644 --- a/go.mod +++ b/go.mod @@ -1 +1 @@ -module github.com/robotn/gohook +module github.com/cauefcr/gohook diff --git a/hook.go b/hook.go index 0613cb0..512123c 100644 --- a/hook.go +++ b/hook.go @@ -20,15 +20,127 @@ package hook //#cgo windows LDFLAGS: -lgdi32 -luser32 #include "event/goEvent.h" -// #include "event/hook_async.h" + */ import "C" import ( - // "fmt" + "fmt" + "time" "unsafe" ) +const ( + HookEnabled = 1 //iota + HookDisabled = 2 + KeyDown = 3 + KeyHold = 4 + KeyUp = 5 + MouseUp = 6 + MouseHold = 7 + MouseDown = 8 + MouseMove = 9 + MouseDrag = 10 + MouseWheel = 11 + FakeEvent = 12 + //Keychar could be v + CharUndefined = 0xFFFF + WheelUp = -1 + WheelDown = 1 +) + +//Holds a system event +//If it's a Keyboard event the relevant fields are: Mask, Keycode, Rawcode, and Keychar, +//Keychar is probably what you want. If it's a Mouse event the relevant fields are: +//Button, Clicks, X, Y, Amount, Rotation and Direction +type Event struct { + Kind uint8 `json:"id"` + When time.Time + Mask uint16 `json:"mask"` + Reserved uint16 `json:"reserved"` + Keycode uint16 `json:"keycode"` + Rawcode uint16 `json:"rawcode"` + Keychar rune `json:"keychar"` + Button uint16 `json:"button"` + Clicks uint16 `json:"clicks"` + X int16 `json:"x"` + Y int16 `json:"y"` + Amount uint16 `json:"amount"` + Rotation int16 `json:"rotation"` + Direction uint8 `json:"direction"` +} + +var ( + ev = make(chan Event, 1024) + asyncon = false +) + +func (e Event) String() string { + switch e.Kind { + case HookEnabled: + return fmt.Sprintf("%v - Event: {Kind: HookEnabled}", e.When) + case HookDisabled: + return fmt.Sprintf("%v - Event: {Kind: HookDisabled}", e.When) + case KeyUp: + return fmt.Sprintf("%v - Event: {Kind: KeyUp, Rawcode: %v, Keychar: %v}", e.When, e.Rawcode, e.Keychar) + case KeyHold: + return fmt.Sprintf("%v - Event: {Kind: KeyHold, Rawcode: %v, Keychar: %v}", e.When, e.Rawcode, e.Keychar) + case KeyDown: + return fmt.Sprintf("%v - Event: {Kind: KeyDown, Rawcode: %v, Keychar: %v}", e.When, e.Rawcode, e.Keychar) + case MouseUp: + return fmt.Sprintf("%v - Event: {Kind: MouseUp, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) + case MouseHold: + return fmt.Sprintf("%v - Event: {Kind: MouseHold, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) + case MouseDown: + return fmt.Sprintf("%v - Event: {Kind: MouseDown, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) + case MouseMove: + return fmt.Sprintf("%v - Event: {Kind: MouseMove, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) + case MouseDrag: + return fmt.Sprintf("%v - Event: {Kind: MouseDrag, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) + case MouseWheel: + return fmt.Sprintf("%v - Event: {Kind: MouseWheel, Amount: %v, Rotation: %v, Direction: %v}", e.When, e.Amount, e.Rotation, e.Direction) + case FakeEvent: + return fmt.Sprintf("%v - Event: {Kind: FakeEvent}", e.When) + } + return "Unknown event, contact the mantainers" +} + +func RawcodetoKeychar(r uint16) string { + return raw2key[r] +} + +func KeychartoRawcode(kc string) uint16 { + return keytoraw[kc] +} + +// Adds global event hook to OS +// returns event channel +func Start() chan Event { + asyncon = true + go C.startev() + go func() { + for { + C.pollEv() + time.Sleep(time.Millisecond * 50) + //todo: find smallest time that does not destroy the cpu utilization + if !asyncon { + return + } + } + }() + return ev +} + +// End removes global event hook +func End() { + C.endPoll() + C.stop_event() + for len(ev) != 0 { + <-ev + } + asyncon = false +} + // AddEvent add event listener func AddEvent(key string) int { cs := C.CString(key) @@ -39,8 +151,3 @@ func AddEvent(key string) int { return geve } - -// StopEvent stop event listener -func StopEvent() { - C.stop_event() -} diff --git a/hook/iohook.h b/hook/iohook.h index 022c2a1..34a1e94 100644 --- a/hook/iohook.h +++ b/hook/iohook.h @@ -79,7 +79,7 @@ typedef struct _screen_data { typedef struct _keyboard_event_data { uint16_t keycode; uint16_t rawcode; - uint16_t keychar; + uint32_t keychar; // char *keychar; } keyboard_event_data, key_pressed_event_data, diff --git a/hook/windows/input_c.h b/hook/windows/input_c.h index c261c87..995101a 100644 --- a/hook/windows/input_c.h +++ b/hook/windows/input_c.h @@ -293,8 +293,8 @@ unsigned short keycode_to_scancode(DWORD vk_code, DWORD flags) { scancode = keycode_scancode_table[vk_code][0]; if (flags & LLKHF_EXTENDED) { - logger(LOG_LEVEL_WARN, "%s [%u]: EXTD2, vk_code %li\n", - __FUNCTION__, __LINE__, vk_code); +// logger(LOG_LEVEL_WARN, "%s [%u]: EXTD2, vk_code %li\n", +// __FUNCTION__, __LINE__, vk_code); switch (vk_code) { case VK_PRIOR: diff --git a/tables.go b/tables.go new file mode 100644 index 0000000..b505f73 --- /dev/null +++ b/tables.go @@ -0,0 +1,351 @@ +package hook + +var ( + raw2key = map[uint16]string{ //https://github.com/wesbos/keycodes + 0: "error", + 3: "break", + 8: "backspace", + 9: "tab", + 12: "clear", + 13: "enter", + 16: "shift", + 17: "ctrl", + 18: "alt", + 19: "pause/break", + 20: "caps lock", + 21: "hangul", + 25: "hanja", + 27: "escape", + 28: "conversion", + 29: "non-conversion", + 32: "spacebar", + 33: "page up", + 34: "page down", + 35: "end", + 36: "home", + 37: "left arrow", + 38: "up arrow", + 39: "right arrow", + 40: "down arrow", + 41: "select", + 42: "print", + 43: "execute", + 44: "Print Screen", + 45: "insert", + 46: "delete", + 47: "help", + 48: "0", + 49: "1", + 50: "2", + 51: "3", + 52: "4", + 53: "5", + 54: "6", + 55: "7", + 56: "8", + 57: "9", + 58: ":", + 59: ";", + 60: "<", + 61: "=", + 63: "ß", + 64: "@", + 65: "a", + 66: "b", + 67: "c", + 68: "d", + 69: "e", + 70: "f", + 71: "g", + 72: "h", + 73: "i", + 74: "j", + 75: "k", + 76: "l", + 77: "m", + 78: "n", + 79: "o", + 80: "p", + 81: "q", + 82: "r", + 83: "s", + 84: "t", + 85: "u", + 86: "v", + 87: "w", + 88: "x", + 89: "y", + 90: "z", + 91: "l-super", + 92: "r-super", + 93: "apps", + 95: "sleep", + 96: "numpad 0", + 97: "numpad 1", + 98: "numpad 2", + 99: "numpad 3", + 100: "numpad 4", + 101: "numpad 5", + 102: "numpad 6", + 103: "numpad 7", + 104: "numpad 8", + 105: "numpad 9", + 106: "multiply", + 107: "add", + 108: "numpad period", + 109: "subtract", + 110: "decimal point", + 111: "divide", + 112: "f1", + 113: "f2", + 114: "f3", + 115: "f4", + 116: "f5", + 117: "f6", + 118: "f7", + 119: "f8", + 120: "f9", + 121: "f10", + 122: "f11", + 123: "f12", + 124: "f13", + 125: "f14", + 126: "f15", + 127: "f16", + 128: "f17", + 129: "f18", + 130: "f19", + 131: "f20", + 132: "f21", + 133: "f22", + 134: "f23", + 135: "f24", + 144: "num lock", + 145: "scroll lock", + 160: "^", + 161: "!", + 162: "؛", + 163: "#", + 164: "$", + 165: "ù", + 166: "page backward", + 167: "page forward", + 168: "refresh", + 169: "closing paren (AZERTY)", + 170: "*", + 171: "~ + * key", + 172: "home key", + 173: "minus (firefox), mute/unmute", + 174: "decrease volume level", + 175: "increase volume level", + 176: "next", + 177: "previous", + 178: "stop", + 179: "play/pause", + 180: "e-mail", + 181: "mute/unmute (firefox)", + 182: "decrease volume level (firefox)", + 183: "increase volume level (firefox)", + 186: "semi-colon / ñ", + 187: "equal sign", + 188: "comma", + 189: "dash", + 190: "period", + 191: "forward slash / ç", + 192: "grave accent / ñ / æ / ö", + 193: "?, / or °", + 194: "numpad period (chrome)", + 219: "open bracket", + 220: "back slash", + 221: "close bracket / å", + 222: "single quote / ø / ä", + 223: "`", + 224: "left or right ⌘ key (firefox)", + 225: "altgr", + 226: "< /git >, left back slash", + 230: "GNOME Compose Key", + 231: "ç", + 233: "XF86Forward", + 234: "XF86Back", + 235: "non-conversion", + 240: "alphanumeric", + 242: "hiragana/katakana", + 243: "half-width/full-width", + 244: "kanji", + 251: "unlock trackpad (Chrome/Edge)", + 255: "toggle touchpad", + } + keytoraw = map[string]uint16{ + "error": 0, + "break": 3, + "backspace": 8, + "tab": 9, + "clear": 12, + "enter": 13, + "shift": 16, + "ctrl": 17, + "alt": 18, + "pause/break": 19, + "caps lock": 20, + "hangul": 21, + "hanja": 25, + "escape": 27, + "conversion": 28, + "non-conversion": 29, + "spacebar": 32, + "page up": 33, + "page down": 34, + "end": 35, + "home": 36, + "left arrow": 37, + "up arrow": 38, + "right arrow": 39, + "down arrow": 40, + "select": 41, + "print": 42, + "execute": 43, + "Print Screen": 44, + "insert": 45, + "delete": 46, + "help": 47, + "0": 48, + "1": 49, + "2": 50, + "3": 51, + "4": 52, + "5": 53, + "6": 54, + "7": 55, + "8": 56, + "9": 57, + ":": 58, + ";": 59, + "<": 60, + "=": 61, + "ß": 63, + "@": 64, + "a": 65, + "b": 66, + "c": 67, + "d": 68, + "e": 69, + "f": 70, + "g": 71, + "h": 72, + "i": 73, + "j": 74, + "k": 75, + "l": 76, + "m": 77, + "n": 78, + "o": 79, + "p": 80, + "q": 81, + "r": 82, + "s": 83, + "t": 84, + "u": 85, + "v": 86, + "w": 87, + "x": 88, + "y": 89, + "z": 90, + "l-super": 91, + "r-super": 92, + "apps": 93, + "sleep": 95, + "numpad 0": 96, + "numpad 1": 97, + "numpad 2": 98, + "numpad 3": 99, + "numpad 4": 100, + "numpad 5": 101, + "numpad 6": 102, + "numpad 7": 103, + "numpad 8": 104, + "numpad 9": 105, + "multiply": 106, + "add": 107, + "numpad period": 108, + "subtract": 109, + "decimal point": 110, + "divide": 111, + "f1": 112, + "f2": 113, + "f3": 114, + "f4": 115, + "f5": 116, + "f6": 117, + "f7": 118, + "f8": 119, + "f9": 120, + "f10": 121, + "f11": 122, + "f12": 123, + "f13": 124, + "f14": 125, + "f15": 126, + "f16": 127, + "f17": 128, + "f18": 129, + "f19": 130, + "f20": 131, + "f21": 132, + "f22": 133, + "f23": 134, + "f24": 135, + "num lock": 144, + "scroll lock": 145, + "^": 160, + "!": 161, + "؛": 162, + "#": 163, + "$": 164, + "ù": 165, + "page backward": 166, + "page forward": 167, + "refresh": 168, + "closing paren (AZERTY)": 169, + "*": 170, + "~ + * key": 171, + "home key": 172, + "minus (firefox), mute/unmute": 173, + "decrease volume level": 174, + "increase volume level": 175, + "next": 176, + "previous": 177, + "stop": 178, + "play/pause": 179, + "e-mail": 180, + "mute/unmute (firefox)": 181, + "decrease volume level (firefox)": 182, + "increase volume level (firefox)": 183, + "semi-colon / ñ": 186, + "equal sign": 187, + "comma": 188, + "dash": 189, + "period": 190, + "forward slash / ç": 191, + "grave accent / ñ / æ / ö": 192, + "?, / or °": 193, + "numpad period (chrome)": 194, + "open bracket": 219, + "back slash": 220, + "close bracket / å": 221, + "single quote / ø / ä": 222, + "`": 223, + "left or right ⌘ key (firefox)": 224, + "altgr": 225, + "< /git >, left back slash": 226, + "GNOME Compose Key": 230, + "ç": 231, + "XF86Forward": 233, + "XF86Back": 234, + "alphanumeric": 240, + "hiragana/katakana": 242, + "half-width/full-width": 243, + "kanji": 244, + "unlock trackpad (Chrome/Edge)": 251, + "toggle touchpad": 255, + } +) diff --git a/test/main.go b/test/main.go index d3fe134..6dec4ca 100644 --- a/test/main.go +++ b/test/main.go @@ -2,14 +2,36 @@ package main import ( "fmt" + "time" - "github.com/robotn/gohook" + "github.com/cauefcr/gohook" ) func main() { - // hook.AsyncHook() - veve := hook.AddEvent("v") - if veve == 0 { - fmt.Println("v...") + s := hook.Start() + defer hook.End() + tout := time.After(time.Hour*2) + done := false + for !done { + select { + case i := <-s: + if i.Kind >= hook.KeyDown && i.Kind <= hook.KeyUp { + if i.Keychar == 'q' { + tout = time.After(0) + } + fmt.Printf("%v key: %c:%v\n",i.Kind, i.Keychar,i.Rawcode) + } else if i.Kind >= hook.MouseDown && i.Kind < hook.MouseWheel { + //fmt.Printf("x: %v, y: %v, button: %v\n", i.X, i.Y, i.Button) + }else if i.Kind == hook.MouseWheel { + //fmt.Printf("x: %v, y: %v, button: %v, wheel: %v, rotation: %v\n", i.X, i.Y, i.Button,i.Amount,i.Rotation) + } else { + fmt.Printf("%+v\n",i) + } + case <-tout: + fmt.Print("Done.") + done = true + break + } } + } From 43543e0cff29eaaca40fa81cac4d2cdbe2afa9e3 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 19 Feb 2019 10:34:55 -0400 Subject: [PATCH 022/120] replace all import and gofmt code --- README.md | 18 +++++++++--------- go.mod | 2 +- hook.go | 5 +++++ test/main.go | 10 +++++----- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 80c632b..584bd89 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,18 @@ # gohook -[![CircleCI Status](https://circleci.com/gh/cauefcr/gohook.svg?style=shield)](https://circleci.com/gh/cauefcr/gohook) -![Appveyor](https://ci.appveyor.com/api/projects/status/github/cauefcr/gohook?branch=master&svg=true) -[![Go Report Card](https://goreportcard.com/badge/github.com/cauefcr/gohook)](https://goreportcard.com/report/github.com/cauefcr/gohook) -[![GoDoc](https://godoc.org/github.com/cauefcr/gohook?status.svg)](https://godoc.org/github.com/cauefcr/gohook) +[![CircleCI Status](https://circleci.com/gh/robotn/gohook.svg?style=shield)](https://circleci.com/gh/robotn/gohook) +![Appveyor](https://ci.appveyor.com/api/projects/status/github/robotn/gohook?branch=master&svg=true) +[![Go Report Card](https://goreportcard.com/badge/github.com/robotn/gohook)](https://goreportcard.com/report/github.com/robotn/gohook) +[![GoDoc](https://godoc.org/github.com/robotn/gohook?status.svg)](https://godoc.org/github.com/robotn/gohook) -Based on [libuiohook](https://github.com/kwhat/libuiohook) - ```Go package main import ( "fmt" - //"github.com/robotn/gohook" - "github.com/cauefcr/gohook" + + "github.com/robotn/gohook" ) func main() { @@ -24,4 +22,6 @@ func main() { fmt.Println(ev) } } -``` \ No newline at end of file +``` + +Based on [libuiohook](https://github.com/kwhat/libuiohook). \ No newline at end of file diff --git a/go.mod b/go.mod index aa65700..214fb13 100644 --- a/go.mod +++ b/go.mod @@ -1 +1 @@ -module github.com/cauefcr/gohook +module github.com/robotn/gohook diff --git a/hook.go b/hook.go index 512123c..180282c 100644 --- a/hook.go +++ b/hook.go @@ -151,3 +151,8 @@ func AddEvent(key string) int { return geve } + +// StopEvent stop event listener +func StopEvent() { + C.stop_event() +} diff --git a/test/main.go b/test/main.go index 6dec4ca..f62df7c 100644 --- a/test/main.go +++ b/test/main.go @@ -4,13 +4,13 @@ import ( "fmt" "time" - "github.com/cauefcr/gohook" + "github.com/robotn/gohook" ) func main() { s := hook.Start() defer hook.End() - tout := time.After(time.Hour*2) + tout := time.After(time.Hour * 2) done := false for !done { select { @@ -19,13 +19,13 @@ func main() { if i.Keychar == 'q' { tout = time.After(0) } - fmt.Printf("%v key: %c:%v\n",i.Kind, i.Keychar,i.Rawcode) + fmt.Printf("%v key: %c:%v\n", i.Kind, i.Keychar, i.Rawcode) } else if i.Kind >= hook.MouseDown && i.Kind < hook.MouseWheel { //fmt.Printf("x: %v, y: %v, button: %v\n", i.X, i.Y, i.Button) - }else if i.Kind == hook.MouseWheel { + } else if i.Kind == hook.MouseWheel { //fmt.Printf("x: %v, y: %v, button: %v, wheel: %v, rotation: %v\n", i.X, i.Y, i.Button,i.Amount,i.Rotation) } else { - fmt.Printf("%+v\n",i) + fmt.Printf("%+v\n", i) } case <-tout: fmt.Print("Done.") From fe1bbb57e6d2a994b5c55c3a0b27de635d33d4bf Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 19 Feb 2019 10:47:03 -0400 Subject: [PATCH 023/120] update godoc and code style --- event/goEvent.h | 20 ++++++------- extern.go | 14 +++++---- hook.go | 76 ++++++++++++++++++++++++++++++------------------- 3 files changed, 66 insertions(+), 44 deletions(-) diff --git a/event/goEvent.h b/event/goEvent.h index b0574e1..f9a768a 100644 --- a/event/goEvent.h +++ b/event/goEvent.h @@ -31,13 +31,13 @@ void startev(){ } void pollEv(){ - if(events == NULL) return; - for(;eb_chan_buf_len(events)!=0;){ + if (events == NULL) return; + for (;eb_chan_buf_len(events)!=0;) { char* tmp; - if(eb_chan_try_recv(events,(const void**) &tmp) == eb_chan_res_ok){ + if( eb_chan_try_recv(events,(const void**) &tmp) == eb_chan_res_ok ){ go_send(tmp); free(tmp); - }else{ + } else { // } } @@ -45,13 +45,13 @@ void pollEv(){ void endPoll(){ sending = false; - pollEv();//remove last things from channel + pollEv(); // remove last things from channel eb_chan_release(events); } void dispatch_proc(iohook_event * const event) { if(!sending) return; -//leaking memory? hope not + // leaking memory? hope not char* buffer = calloc(200,sizeof(char)); switch (event->type) { @@ -59,7 +59,7 @@ void dispatch_proc(iohook_event * const event) { case EVENT_HOOK_DISABLED: sprintf(buffer,"{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu}", event->type, event->time, event->mask,event->reserved); - break;//send it? + break; // send it? case EVENT_KEY_PRESSED: case EVENT_KEY_RELEASED: case EVENT_KEY_TYPED: @@ -99,14 +99,14 @@ void dispatch_proc(iohook_event * const event) { fprintf(stderr,"\nError on file: %s, unusual event->type: %i\n",__FILE__,event->type); return; } - //to-do remove this for + // to-do remove this for for(int i = 0; i < 5; i++){ - switch(eb_chan_try_send(events,buffer)){ //never block the hook callback + switch(eb_chan_try_send(events,buffer)){ // never block the hook callback case eb_chan_res_ok: i=5; break; default: - if (i == 4) {//let's not leak memory + if (i == 4) { // let's not leak memory free(buffer); } continue; diff --git a/extern.go b/extern.go index 631aeb2..6cacb4c 100644 --- a/extern.go +++ b/extern.go @@ -2,32 +2,36 @@ package hook /* - // #include "event/hook_async.h" */ import "C" import ( - "encoding/json" "log" "time" + + "encoding/json" ) //export go_send func go_send(s *C.char) { str := []byte(C.GoString(s)) out := Event{} + err := json.Unmarshal(str, &out) if err != nil { log.Fatal(err) } + if out.Keychar != CharUndefined { raw2key[out.Rawcode] = string([]rune{out.Keychar}) } - //todo bury this deep into the C lib so that the time is correct - out.When = time.Now() //at least it's consistent + + // todo bury this deep into the C lib so that the time is correct + out.When = time.Now() // at least it's consistent if err != nil { log.Fatal(err) } - //todo: maybe make non-bloking + + // todo: maybe make non-bloking ev <- out } diff --git a/hook.go b/hook.go index 180282c..3b5c197 100644 --- a/hook.go +++ b/hook.go @@ -31,40 +31,49 @@ import ( ) const ( - HookEnabled = 1 //iota + HookEnabled = 1 // iota HookDisabled = 2 - KeyDown = 3 - KeyHold = 4 - KeyUp = 5 - MouseUp = 6 - MouseHold = 7 - MouseDown = 8 - MouseMove = 9 - MouseDrag = 10 - MouseWheel = 11 - FakeEvent = 12 - //Keychar could be v + + KeyDown = 3 + KeyHold = 4 + KeyUp = 5 + + MouseUp = 6 + MouseHold = 7 + MouseDown = 8 + MouseMove = 9 + MouseDrag = 10 + MouseWheel = 11 + + FakeEvent = 12 + //Keychar could be v CharUndefined = 0xFFFF WheelUp = -1 WheelDown = 1 ) -//Holds a system event -//If it's a Keyboard event the relevant fields are: Mask, Keycode, Rawcode, and Keychar, -//Keychar is probably what you want. If it's a Mouse event the relevant fields are: -//Button, Clicks, X, Y, Amount, Rotation and Direction +// Event Holds a system event +// If it's a Keyboard event the relevant fields are: +// Mask, Keycode, Rawcode, and Keychar, +// Keychar is probably what you want. +// If it's a Mouse event the relevant fields are: +// Button, Clicks, X, Y, Amount, Rotation and Direction type Event struct { - Kind uint8 `json:"id"` - When time.Time - Mask uint16 `json:"mask"` - Reserved uint16 `json:"reserved"` - Keycode uint16 `json:"keycode"` - Rawcode uint16 `json:"rawcode"` - Keychar rune `json:"keychar"` - Button uint16 `json:"button"` - Clicks uint16 `json:"clicks"` - X int16 `json:"x"` - Y int16 `json:"y"` + Kind uint8 `json:"id"` + When time.Time + Mask uint16 `json:"mask"` + Reserved uint16 `json:"reserved"` + + Keycode uint16 `json:"keycode"` + Rawcode uint16 `json:"rawcode"` + Keychar rune `json:"keychar"` + + Button uint16 `json:"button"` + Clicks uint16 `json:"clicks"` + + X int16 `json:"x"` + Y int16 `json:"y"` + Amount uint16 `json:"amount"` Rotation int16 `json:"rotation"` Direction uint8 `json:"direction"` @@ -75,6 +84,7 @@ var ( asyncon = false ) +// String return hook kind string func (e Event) String() string { switch e.Kind { case HookEnabled: @@ -102,32 +112,38 @@ func (e Event) String() string { case FakeEvent: return fmt.Sprintf("%v - Event: {Kind: FakeEvent}", e.When) } + return "Unknown event, contact the mantainers" } +// RawcodetoKeychar rawcode to keychar func RawcodetoKeychar(r uint16) string { return raw2key[r] } +// KeychartoiRawcode key char to rawcode func KeychartoRawcode(kc string) uint16 { return keytoraw[kc] } -// Adds global event hook to OS +// Start Adds global event hook to OS // returns event channel func Start() chan Event { asyncon = true go C.startev() + go func() { for { C.pollEv() time.Sleep(time.Millisecond * 50) - //todo: find smallest time that does not destroy the cpu utilization + + // todo: find smallest time that does not destroy the cpu utilization if !asyncon { return } } }() + return ev } @@ -135,9 +151,11 @@ func Start() chan Event { func End() { C.endPoll() C.stop_event() + for len(ev) != 0 { <-ev } + asyncon = false } From 1da30b1d41ae3132d15f54b47ff49f048500761c Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 19 Feb 2019 11:03:09 -0400 Subject: [PATCH 024/120] update code use c99 and update code style --- chan/eb_chan.h | 27 ++++++++++++++++++--------- event/goEvent.h | 20 +++++++++++--------- event/pub.h | 2 +- hook.go | 5 +++-- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/chan/eb_chan.h b/chan/eb_chan.h index be02996..e4855b4 100644 --- a/chan/eb_chan.h +++ b/chan/eb_chan.h @@ -566,7 +566,8 @@ static inline void port_list_free(port_list l) { } /* Release each port in our list */ - for (size_t i = 0; i < l->len; i++) { + size_t i; + for (i = 0; i < l->len; i++) { eb_port_release(l->ports[i]); } @@ -613,7 +614,8 @@ static inline bool port_list_rm(port_list l, eb_port p) { eb_assert_or_bail(l->len <= l->cap, "Sanity-check failed"); /* Search for first occurence of the given port. If we find it, release it and move the last port in the list into the hole. */ - for (size_t i = 0; i < l->len; i++) { + size_t i; + for (i = 0; i < l->len; i++) { if (l->ports[i] == p) { /* Move the last element in the port list into the now-vacant spot */ l->ports[i] = l->ports[l->len-1]; @@ -639,7 +641,8 @@ static inline void port_list_signal_first(const port_list l, eb_port ignore) { eb_port p = NULL; eb_spinlock_lock(&l->lock); - for (size_t i = 0; i < l->len; i++) { + size_t i; + for (i = 0; i < l->len; i++) { if (l->ports[i] != ignore) { p = eb_port_retain(l->ports[i]); break; @@ -845,7 +848,8 @@ enum { static inline void cleanup_ops(const do_state *state) { assert(state); - for (size_t i = 0; i < state->nops; i++) { + size_t i; + for (i = 0; i < state->nops; i++) { if (state->cleanup_ops[i]) { eb_chan_op *op = state->ops[i]; eb_chan c = op->chan; @@ -1310,7 +1314,8 @@ eb_chan_op *eb_chan_select_list(eb_nsec timeout, eb_chan_op *const ops[], size_t if (timeout == eb_nsec_zero) { /* ## timeout == 0: try every op exactly once; if none of them can proceed, return NULL. */ - for (size_t i = 0, idx = idx_start; i < nops; i++, idx = next_idx(nops, idx_delta, idx)) { + size_t i, idx; + for (i = 0, idx = idx_start; i < nops; i++, idx = next_idx(nops, idx_delta, idx)) { eb_chan_op *op = ops[idx]; op_result r; while ((r = try_op(&state, op, idx)) == op_result_retry) { @@ -1336,7 +1341,8 @@ eb_chan_op *eb_chan_select_list(eb_nsec timeout, eb_chan_op *const ops[], size_t for (;;) { /* ## Fast path: loop over our operations to see if one of them was able to send/receive. (If not, we'll enter the slow path where we put our thread to sleep until we're signaled.) */ - for (size_t i = 0, idx = idx_start; i < k_attempt_multiplier*nops; i++, idx = next_idx(nops, idx_delta, idx)) { + size_t i, idx; + for (i = 0, idx = idx_start; i < k_attempt_multiplier*nops; i++, idx = next_idx(nops, idx_delta, idx)) { eb_chan_op *op = ops[idx]; op_result r = try_op(&state, op, idx); /* If the op completed, we need to exit! */ @@ -1356,7 +1362,8 @@ eb_chan_op *eb_chan_select_list(eb_nsec timeout, eb_chan_op *const ops[], size_t /* Register our port for the appropriate notifications on every channel. */ /* This adds 'port' to the channel's sends/recvs (depending on the op), which we clean up at the end of this function. */ - for (size_t i = 0; i < nops; i++) { + size_t i; + for (i = 0; i < nops; i++) { eb_chan_op *op = ops[i]; eb_chan c = op->chan; if (c) { @@ -1368,7 +1375,8 @@ eb_chan_op *eb_chan_select_list(eb_nsec timeout, eb_chan_op *const ops[], size_t /* Before we go to sleep, call try_op() for every op until we get a non-busy return value. This way we'll ensure that no op is actually able to be performed, and we'll also ensure that 'port' is registered as the 'unbuf_port' for the necessary channels. */ - for (size_t i = 0, idx = idx_start; i < nops; i++, idx = next_idx(nops, idx_delta, idx)) { + // size_t i, idx; + for (i = 0, idx = idx_start; i < nops; i++, idx = next_idx(nops, idx_delta, idx)) { eb_chan_op *op = ops[idx]; op_result r; while ((r = try_op(&state, op, idx)) == op_result_retry) { @@ -1406,7 +1414,8 @@ eb_chan_op *eb_chan_select_list(eb_nsec timeout, eb_chan_op *const ops[], size_t /* Cleanup! */ cleanup: { if (state.port) { - for (size_t i = 0; i < nops; i++) { + size_t i; + for (i = 0; i < nops; i++) { eb_chan_op *op = ops[i]; eb_chan c = op->chan; if (c) { diff --git a/event/goEvent.h b/event/goEvent.h index f9a768a..a5ca1e0 100644 --- a/event/goEvent.h +++ b/event/goEvent.h @@ -52,7 +52,7 @@ void endPoll(){ void dispatch_proc(iohook_event * const event) { if(!sending) return; // leaking memory? hope not - char* buffer = calloc(200,sizeof(char)); + char* buffer = calloc(200, sizeof(char)); switch (event->type) { case EVENT_HOOK_ENABLED: @@ -99,17 +99,19 @@ void dispatch_proc(iohook_event * const event) { fprintf(stderr,"\nError on file: %s, unusual event->type: %i\n",__FILE__,event->type); return; } + // to-do remove this for - for(int i = 0; i < 5; i++){ - switch(eb_chan_try_send(events,buffer)){ // never block the hook callback + int i; + for (i = 0; i < 5; i++) { + switch (eb_chan_try_send(events,buffer)) { // never block the hook callback case eb_chan_res_ok: - i=5; - break; + i=5; + break; default: - if (i == 4) { // let's not leak memory - free(buffer); - } - continue; + if (i == 4) { // let's not leak memory + free(buffer); + } + continue; } } diff --git a/event/pub.h b/event/pub.h index 92f59a1..08d1df3 100644 --- a/event/pub.h +++ b/event/pub.h @@ -37,7 +37,7 @@ int vccode[100]; -int codesz; +int codesz; char *cevent; int rrevent; diff --git a/hook.go b/hook.go index 3b5c197..0a32ec8 100644 --- a/hook.go +++ b/hook.go @@ -31,6 +31,7 @@ import ( ) const ( + // HookEnabled honk enable status HookEnabled = 1 // iota HookDisabled = 2 @@ -46,7 +47,7 @@ const ( MouseWheel = 11 FakeEvent = 12 - //Keychar could be v + // Keychar could be v CharUndefined = 0xFFFF WheelUp = -1 WheelDown = 1 @@ -121,7 +122,7 @@ func RawcodetoKeychar(r uint16) string { return raw2key[r] } -// KeychartoiRawcode key char to rawcode +// KeychartoRawcode key char to rawcode func KeychartoRawcode(kc string) uint16 { return keytoraw[kc] } From 9fa8b6c40885703e368d1b9e270efc1ddc8b88a0 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 19 Feb 2019 11:32:48 -0400 Subject: [PATCH 025/120] add example and update code style --- README.md | 1 + appveyor.yml | 2 +- event/goEvent.h | 4 +++- example/main.go | 40 ++++++++++++++++++++++++++++++++++++++++ tables.go | 1 + test/main.go | 3 +++ 6 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 example/main.go diff --git a/README.md b/README.md index 584bd89..251729c 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ import ( func main() { EvChan := hook.Start() defer hook.End() + for ev := range EvChan { fmt.Println(ev) } diff --git a/appveyor.yml b/appveyor.yml index abe8c2a..7d8bfb8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.11.4 + GOVERSION: 1.11.5 # GOPATH: c:\gopath # scripts that run after cloning repository diff --git a/event/goEvent.h b/event/goEvent.h index a5ca1e0..21ec2ae 100644 --- a/event/goEvent.h +++ b/event/goEvent.h @@ -34,7 +34,9 @@ void pollEv(){ if (events == NULL) return; for (;eb_chan_buf_len(events)!=0;) { char* tmp; - if( eb_chan_try_recv(events,(const void**) &tmp) == eb_chan_res_ok ){ + if (eb_chan_try_recv(events, (const void**) &tmp) + == eb_chan_res_ok) { + // send a char go_send(tmp); free(tmp); } else { diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..9b1a7e8 --- /dev/null +++ b/example/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + + "github.com/robotn/gohook" +) + +func add() { + s := hook.Start() + defer hook.End() + + ct := false + for { + i := <-s + + if i.Kind == hook.KeyHold && i.Rawcode == 59 { + ct = true + } + + if ct && i.Rawcode == 12 { + break + } + } +} + +func base() { + EvChan := hook.Start() + defer hook.End() + + for ev := range EvChan { + fmt.Println(ev) + } +} + +func main() { + base() + + add() +} diff --git a/tables.go b/tables.go index b505f73..c2e3829 100644 --- a/tables.go +++ b/tables.go @@ -175,6 +175,7 @@ var ( 251: "unlock trackpad (Chrome/Edge)", 255: "toggle touchpad", } + keytoraw = map[string]uint16{ "error": 0, "break": 3, diff --git a/test/main.go b/test/main.go index f62df7c..a0359d5 100644 --- a/test/main.go +++ b/test/main.go @@ -10,6 +10,7 @@ import ( func main() { s := hook.Start() defer hook.End() + tout := time.After(time.Hour * 2) done := false for !done { @@ -19,6 +20,7 @@ func main() { if i.Keychar == 'q' { tout = time.After(0) } + fmt.Printf("%v key: %c:%v\n", i.Kind, i.Keychar, i.Rawcode) } else if i.Kind >= hook.MouseDown && i.Kind < hook.MouseWheel { //fmt.Printf("x: %v, y: %v, button: %v\n", i.X, i.Y, i.Button) @@ -27,6 +29,7 @@ func main() { } else { fmt.Printf("%+v\n", i) } + case <-tout: fmt.Print("Done.") done = true From 3f023c292fb417affd402b79e372470c8d13a89c Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 19 Feb 2019 11:47:56 -0400 Subject: [PATCH 026/120] optimize darwin check accessibility not loop --- hook/darwin/hook_c.h | 347 +++++++++++++++++++++---------------------- 1 file changed, 173 insertions(+), 174 deletions(-) diff --git a/hook/darwin/hook_c.h b/hook/darwin/hook_c.h index 474bef0..b717bcf 100644 --- a/hook/darwin/hook_c.h +++ b/hook/darwin/hook_c.h @@ -1110,235 +1110,234 @@ CGEventRef hook_event_proc(CGEventTapProxy tap_proxy, CGEventType type, CGEventR IOHOOK_API int hook_run() { int status = IOHOOK_SUCCESS; - do { - // Reset the restart flag... - restart_tap = false; + // Check for accessibility each time we start the loop. + if (is_accessibility_enabled()) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Accessibility API is enabled.\n", + __FUNCTION__, __LINE__); - // Check for accessibility each time we start the loop. - if (is_accessibility_enabled()) { - logger(LOG_LEVEL_DEBUG, "%s [%u]: Accessibility API is enabled.\n", - __FUNCTION__, __LINE__); + do { + // Reset the restart flag... + restart_tap = false; - // Initialize starting modifiers. - initialize_modifiers(); + // Initialize starting modifiers. + initialize_modifiers(); - // Try and allocate memory for hook_info. - hook_info *hook = malloc(sizeof(hook_info)); - if (hook != NULL) { - // Setup the event mask to listen for. - #ifdef USE_DEBUG - CGEventMask event_mask = kCGEventMaskForAllEvents; - #else - CGEventMask event_mask = CGEventMaskBit(kCGEventKeyDown) | - CGEventMaskBit(kCGEventKeyUp) | - CGEventMaskBit(kCGEventFlagsChanged) | + // Try and allocate memory for hook_info. + hook_info *hook = malloc(sizeof(hook_info)); + if (hook != NULL) { + // Setup the event mask to listen for. + #ifdef USE_DEBUG + CGEventMask event_mask = kCGEventMaskForAllEvents; + #else + CGEventMask event_mask = CGEventMaskBit(kCGEventKeyDown) | + CGEventMaskBit(kCGEventKeyUp) | + CGEventMaskBit(kCGEventFlagsChanged) | - CGEventMaskBit(kCGEventLeftMouseDown) | - CGEventMaskBit(kCGEventLeftMouseUp) | - CGEventMaskBit(kCGEventLeftMouseDragged) | + CGEventMaskBit(kCGEventLeftMouseDown) | + CGEventMaskBit(kCGEventLeftMouseUp) | + CGEventMaskBit(kCGEventLeftMouseDragged) | - CGEventMaskBit(kCGEventRightMouseDown) | - CGEventMaskBit(kCGEventRightMouseUp) | - CGEventMaskBit(kCGEventRightMouseDragged) | + CGEventMaskBit(kCGEventRightMouseDown) | + CGEventMaskBit(kCGEventRightMouseUp) | + CGEventMaskBit(kCGEventRightMouseDragged) | - CGEventMaskBit(kCGEventOtherMouseDown) | - CGEventMaskBit(kCGEventOtherMouseUp) | - CGEventMaskBit(kCGEventOtherMouseDragged) | + CGEventMaskBit(kCGEventOtherMouseDown) | + CGEventMaskBit(kCGEventOtherMouseUp) | + CGEventMaskBit(kCGEventOtherMouseDragged) | - CGEventMaskBit(kCGEventMouseMoved) | - CGEventMaskBit(kCGEventScrollWheel) | + CGEventMaskBit(kCGEventMouseMoved) | + CGEventMaskBit(kCGEventScrollWheel) | - // NOTE This event is undocumented and used - // for caps-lock release and multi-media keys. - CGEventMaskBit(NX_SYSDEFINED); - #endif + // NOTE This event is undocumented and used + // for caps-lock release and multi-media keys. + CGEventMaskBit(NX_SYSDEFINED); + #endif - // Create the event tap. - hook->port = CGEventTapCreate( - kCGSessionEventTap, // kCGHIDEventTap - kCGHeadInsertEventTap, // kCGTailAppendEventTap - kCGEventTapOptionDefault, // kCGEventTapOptionListenOnly See Bug #22 - event_mask, - hook_event_proc, - NULL); + // Create the event tap. + hook->port = CGEventTapCreate( + kCGSessionEventTap, // kCGHIDEventTap + kCGHeadInsertEventTap, // kCGTailAppendEventTap + kCGEventTapOptionDefault, // kCGEventTapOptionListenOnly See Bug #22 + event_mask, + hook_event_proc, + NULL); - if (hook->port != NULL) { - logger(LOG_LEVEL_DEBUG, "%s [%u]: CGEventTapCreate Successful.\n", - __FUNCTION__, __LINE__); - - // Create the runloop event source from the event tap. - hook->source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, hook->port, 0); - if (hook->source != NULL) { - logger(LOG_LEVEL_DEBUG, "%s [%u]: CFMachPortCreateRunLoopSource successful.\n", + if (hook->port != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: CGEventTapCreate Successful.\n", __FUNCTION__, __LINE__); - event_loop = CFRunLoopGetCurrent(); - if (event_loop != NULL) { - logger(LOG_LEVEL_DEBUG, "%s [%u]: CFRunLoopGetCurrent successful.\n", + // Create the runloop event source from the event tap. + hook->source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, hook->port, 0); + if (hook->source != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: CFMachPortCreateRunLoopSource successful.\n", __FUNCTION__, __LINE__); - // Create run loop observers. - hook->observer = CFRunLoopObserverCreate( - kCFAllocatorDefault, - kCFRunLoopEntry | kCFRunLoopExit, //kCFRunLoopAllActivities, - true, - 0, - hook_status_proc, - NULL); - - if (hook->observer != NULL) { - logger(LOG_LEVEL_DEBUG, "%s [%u]: CFRunLoopObserverCreate successful.\n", + event_loop = CFRunLoopGetCurrent(); + if (event_loop != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: CFRunLoopGetCurrent successful.\n", __FUNCTION__, __LINE__); - tis_message = (TISMessage *) calloc(1, sizeof(TISMessage)); - if (tis_message != NULL) { - if (! CFEqual(event_loop, CFRunLoopGetMain())) { - #ifdef USE_WEAK_IMPORT - if (dispatch_sync_f == NULL || dispatch_get_main_queue == NULL) { - #else - *(void **) (&dispatch_sync_f_f) = dlsym(RTLD_DEFAULT, "dispatch_sync_f"); - const char *dlError = dlerror(); - if (dlError != NULL) { - logger(LOG_LEVEL_DEBUG, "%s [%u]: %s.\n", - __FUNCTION__, __LINE__, dlError); - } + // Create run loop observers. + hook->observer = CFRunLoopObserverCreate( + kCFAllocatorDefault, + kCFRunLoopEntry | kCFRunLoopExit, //kCFRunLoopAllActivities, + true, + 0, + hook_status_proc, + NULL); - *(void **) (&dispatch_get_main_queue_f) = dlsym(RTLD_DEFAULT, "dispatch_get_main_queue"); - dlError = dlerror(); - if (dlError != NULL) { - logger(LOG_LEVEL_DEBUG, "%s [%u]: %s.\n", - __FUNCTION__, __LINE__, dlError); - } + if (hook->observer != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: CFRunLoopObserverCreate successful.\n", + __FUNCTION__, __LINE__); - if (dispatch_sync_f_f == NULL || dispatch_get_main_queue_f == NULL) { - #endif - logger(LOG_LEVEL_DEBUG, "%s [%u]: Failed to locate dispatch_sync_f() or dispatch_get_main_queue()!\n", - __FUNCTION__, __LINE__); - - #if ! defined(USE_CARBON_LEGACY) && defined(USE_COREFOUNDATION) - logger(LOG_LEVEL_DEBUG, "%s [%u]: Falling back to runloop signaling.\n", - __FUNCTION__, __LINE__); - - int runloop_status = start_message_port_runloop(); - if (runloop_status != IOHOOK_SUCCESS) { - return runloop_status; + tis_message = (TISMessage *) calloc(1, sizeof(TISMessage)); + if (tis_message != NULL) { + if (! CFEqual(event_loop, CFRunLoopGetMain())) { + #ifdef USE_WEAK_IMPORT + if (dispatch_sync_f == NULL || dispatch_get_main_queue == NULL) { + #else + *(void **) (&dispatch_sync_f_f) = dlsym(RTLD_DEFAULT, "dispatch_sync_f"); + const char *dlError = dlerror(); + if (dlError != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: %s.\n", + __FUNCTION__, __LINE__, dlError); } + + *(void **) (&dispatch_get_main_queue_f) = dlsym(RTLD_DEFAULT, "dispatch_get_main_queue"); + dlError = dlerror(); + if (dlError != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: %s.\n", + __FUNCTION__, __LINE__, dlError); + } + + if (dispatch_sync_f_f == NULL || dispatch_get_main_queue_f == NULL) { #endif + logger(LOG_LEVEL_DEBUG, "%s [%u]: Failed to locate dispatch_sync_f() or dispatch_get_main_queue()!\n", + __FUNCTION__, __LINE__); + + #if ! defined(USE_CARBON_LEGACY) && defined(USE_COREFOUNDATION) + logger(LOG_LEVEL_DEBUG, "%s [%u]: Falling back to runloop signaling.\n", + __FUNCTION__, __LINE__); + + int runloop_status = start_message_port_runloop(); + if (runloop_status != IOHOOK_SUCCESS) { + return runloop_status; + } + #endif + } } - } - // Add the event source and observer to the runloop mode. - CFRunLoopAddSource(event_loop, hook->source, kCFRunLoopDefaultMode); - CFRunLoopAddObserver(event_loop, hook->observer, kCFRunLoopDefaultMode); + // Add the event source and observer to the runloop mode. + CFRunLoopAddSource(event_loop, hook->source, kCFRunLoopDefaultMode); + CFRunLoopAddObserver(event_loop, hook->observer, kCFRunLoopDefaultMode); - #ifdef USE_OBJC - // Create a garbage collector to handle Cocoa events correctly. - Class NSAutoreleasePool_class = (Class) objc_getClass("NSAutoreleasePool"); - id pool = class_createInstance(NSAutoreleasePool_class, 0); - auto_release_pool = objc_msgSend(pool, sel_registerName("init")); - #endif - - // Start the hook thread runloop. - CFRunLoopRun(); - - - #ifdef USE_OBJC - //objc_msgSend(auto_release_pool, sel_registerName("drain")); - objc_msgSend(auto_release_pool, sel_registerName("release")); - #endif - - // Lock back up until we are done processing the exit. - if (CFRunLoopContainsObserver(event_loop, hook->observer, kCFRunLoopDefaultMode)) { - CFRunLoopRemoveObserver(event_loop, hook->observer, kCFRunLoopDefaultMode); - } - - if (CFRunLoopContainsSource(event_loop, hook->source, kCFRunLoopDefaultMode)) { - CFRunLoopRemoveSource(event_loop, hook->source, kCFRunLoopDefaultMode); - } - - #if ! defined(USE_CARBON_LEGACY) && defined(USE_COREFOUNDATION) - if (! CFEqual(event_loop, CFRunLoopGetMain())) { - #ifdef USE_WEAK_IMPORT - if (dispatch_sync_f == NULL || dispatch_get_main_queue == NULL) { - #else - if (dispatch_sync_f_f == NULL || dispatch_get_main_queue_f == NULL) { + #ifdef USE_OBJC + // Create a garbage collector to handle Cocoa events correctly. + Class NSAutoreleasePool_class = (Class) objc_getClass("NSAutoreleasePool"); + id pool = class_createInstance(NSAutoreleasePool_class, 0); + auto_release_pool = objc_msgSend(pool, sel_registerName("init")); #endif - stop_message_port_runloop(); - } - } - #endif - // Free the TIS Message. - free(tis_message); + // Start the hook thread runloop. + CFRunLoopRun(); + + + #ifdef USE_OBJC + //objc_msgSend(auto_release_pool, sel_registerName("drain")); + objc_msgSend(auto_release_pool, sel_registerName("release")); + #endif + + // Lock back up until we are done processing the exit. + if (CFRunLoopContainsObserver(event_loop, hook->observer, kCFRunLoopDefaultMode)) { + CFRunLoopRemoveObserver(event_loop, hook->observer, kCFRunLoopDefaultMode); + } + + if (CFRunLoopContainsSource(event_loop, hook->source, kCFRunLoopDefaultMode)) { + CFRunLoopRemoveSource(event_loop, hook->source, kCFRunLoopDefaultMode); + } + + #if ! defined(USE_CARBON_LEGACY) && defined(USE_COREFOUNDATION) + if (! CFEqual(event_loop, CFRunLoopGetMain())) { + #ifdef USE_WEAK_IMPORT + if (dispatch_sync_f == NULL || dispatch_get_main_queue == NULL) { + #else + if (dispatch_sync_f_f == NULL || dispatch_get_main_queue_f == NULL) { + #endif + stop_message_port_runloop(); + } + } + #endif + + // Free the TIS Message. + free(tis_message); + } + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for TIS message structure!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_OUT_OF_MEMORY; + } + + // Invalidate and free hook observer. + CFRunLoopObserverInvalidate(hook->observer); + CFRelease(hook->observer); } else { - logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for TIS message structure!\n", + // We cant do a whole lot of anything if we cant + // create run loop observer. + logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopObserverCreate failure!\n", __FUNCTION__, __LINE__); // Set the exit status. - status = IOHOOK_ERROR_OUT_OF_MEMORY; + status = IOHOOK_ERROR_CREATE_OBSERVER; } - - // Invalidate and free hook observer. - CFRunLoopObserverInvalidate(hook->observer); - CFRelease(hook->observer); } else { - // We cant do a whole lot of anything if we cant - // create run loop observer. - logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopObserverCreate failure!\n", + logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopGetCurrent failure!\n", __FUNCTION__, __LINE__); // Set the exit status. - status = IOHOOK_ERROR_CREATE_OBSERVER; + status = IOHOOK_ERROR_GET_RUNLOOP; } + + // Clean up the event source. + CFRelease(hook->source); } else { - logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopGetCurrent failure!\n", + logger(LOG_LEVEL_ERROR, "%s [%u]: CFMachPortCreateRunLoopSource failure!\n", __FUNCTION__, __LINE__); // Set the exit status. - status = IOHOOK_ERROR_GET_RUNLOOP; + status = IOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE; } - // Clean up the event source. - CFRelease(hook->source); + // Stop the CFMachPort from receiving any more messages. + CFMachPortInvalidate(hook->port); + CFRelease(hook->port); } else { - logger(LOG_LEVEL_ERROR, "%s [%u]: CFMachPortCreateRunLoopSource failure!\n", + logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to create event port!\n", __FUNCTION__, __LINE__); // Set the exit status. - status = IOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE; + status = IOHOOK_ERROR_CREATE_EVENT_PORT; } - // Stop the CFMachPort from receiving any more messages. - CFMachPortInvalidate(hook->port); - CFRelease(hook->port); + // Free the hook structure. + free(hook); } else { - logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to create event port!\n", - __FUNCTION__, __LINE__); - - // Set the exit status. - status = IOHOOK_ERROR_CREATE_EVENT_PORT; + status = IOHOOK_ERROR_OUT_OF_MEMORY; } + } while (restart_tap); + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Accessibility API is disabled!\n", + __FUNCTION__, __LINE__); - // Free the hook structure. - free(hook); - } - else { - status = IOHOOK_ERROR_OUT_OF_MEMORY; - } - } - else { - logger(LOG_LEVEL_ERROR, "%s [%u]: Accessibility API is disabled!\n", - __FUNCTION__, __LINE__); - - // Set the exit status. - status = IOHOOK_ERROR_AXAPI_DISABLED; - } - } while (restart_tap); + // Set the exit status. + status = IOHOOK_ERROR_AXAPI_DISABLED; + } logger(LOG_LEVEL_DEBUG, "%s [%u]: Something, something, something, complete.\n", __FUNCTION__, __LINE__); From b5ca357c14ff4f25a4112036c20ae33a71b388d3 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Wed, 20 Feb 2019 11:07:25 -0400 Subject: [PATCH 027/120] refactor code, fixed bug and api --- event/dispatch_proc.h | 217 ++++++++++++++++++++++++++++++++++++++++++ event/goEvent.h | 100 +++++-------------- event/pub.h | 15 ++- hook.go | 2 +- tables.go | 2 +- 5 files changed, 252 insertions(+), 84 deletions(-) create mode 100644 event/dispatch_proc.h diff --git a/event/dispatch_proc.h b/event/dispatch_proc.h new file mode 100644 index 0000000..fddf9c0 --- /dev/null +++ b/event/dispatch_proc.h @@ -0,0 +1,217 @@ +// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://github.com/go-vgo/robotgo/blob/master/LICENSE +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#ifndef dispatch_proc_h +#define dispatch_proc_h + +// #include "pub.h" +// #include "../chan/eb_chan.h" + +void dispatch_proc(iohook_event * const event) { + if (!sending) { return; } + + // leaking memory? hope not + char* buffer = calloc(200, sizeof(char)); + + switch (event->type) { + case EVENT_HOOK_ENABLED: + case EVENT_HOOK_DISABLED: + sprintf(buffer, + "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu}", + event->type, event->time, event->mask,event->reserved); + break; // send it? + case EVENT_KEY_PRESSED: + case EVENT_KEY_RELEASED: + case EVENT_KEY_TYPED: + sprintf(buffer, + "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"keycode\":%hu,\"rawcode\":%hu,\"keychar\":%hu}", + event->type, event->time, event->mask,event->reserved, + event->data.keyboard.keycode, + event->data.keyboard.rawcode, + event->data.keyboard.keychar); + break; + case EVENT_MOUSE_PRESSED: + case EVENT_MOUSE_RELEASED: + case EVENT_MOUSE_CLICKED: + case EVENT_MOUSE_MOVED: + case EVENT_MOUSE_DRAGGED: + sprintf(buffer, + "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"x\":%hd,\"y\":%hd,\"button\":%u,\"clicks\":%u}", + event->type, event->time, event->mask,event->reserved, + event->data.mouse.x, + event->data.mouse.y, + event->data.mouse.button, + event->data.mouse.clicks); + break; + case EVENT_MOUSE_WHEEL: + sprintf(buffer, + "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"clicks\":%hu,\"x\":%hd,\"y\":%hd,\"type\":%hu,\"ammount\":%hu,\"rotation\":%hd,\"direction\":%hu}", + event->type, event->time, event->mask, event->reserved, + event->data.wheel.clicks, + event->data.wheel.x, + event->data.wheel.y, + event->data.wheel.type, + event->data.wheel.amount, + event->data.wheel.rotation, + event->data.wheel.direction); + break; + default: + fprintf(stderr,"\nError on file: %s, unusual event->type: %i\n",__FILE__,event->type); + return; + } + + // to-do remove this for + int i; + for (i = 0; i < 5; i++) { + switch (eb_chan_try_send(events, buffer)) { // never block the hook callback + case eb_chan_res_ok: + i=5; + break; + default: + if (i == 4) { // let's not leak memory + free(buffer); + } + continue; + } + } + + // fprintf(stdout, "----%s\n", buffer); +} + +void dispatch_proc_end(iohook_event * const event) { + char buffer[256] = { 0 }; + size_t length = snprintf(buffer, sizeof(buffer), + "id=%i,when=%" PRIu64 ",mask=0x%X", + event->type, event->time, event->mask); + + switch (event->type) { + case EVENT_KEY_PRESSED: + // If the escape key is pressed, naturally terminate the program. + if (event->data.keyboard.keycode == VC_ESCAPE) { + // int status = hook_stop(); + // switch (status) { + // // System level errors. + // case IOHOOK_ERROR_OUT_OF_MEMORY: + // loggerProc(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); + // break; + + // case IOHOOK_ERROR_X_RECORD_GET_CONTEXT: + // // NOTE This is the only platform specific error that occurs on hook_stop(). + // loggerProc(LOG_LEVEL_ERROR, "Failed to get XRecord context. (%#X)", status); + // break; + + // // Default error. + // case IOHOOK_FAILURE: + // default: + // loggerProc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); + // break; + // } + } + case EVENT_KEY_RELEASED: + snprintf(buffer + length, sizeof(buffer) - length, + ",keycode=%u,rawcode=0x%X", + event->data.keyboard.keycode, event->data.keyboard.rawcode); + int key_code = (uint16_t) event->data.keyboard.keycode; + + if (event->data.keyboard.keycode == VC_ESCAPE + && atoi(cevent) == 11) { + int stopEvent = stop_event(); + // printf("stop_event%d\n", stopEvent); + cstatus = 0; + } + + // printf("atoi(str)---%d\n", atoi(cevent)); + if (key_code == atoi(cevent)) { + int stopEvent = stop_event(); + // printf("%d\n", stopEvent); + cstatus = 0; + } + break; + + case EVENT_KEY_TYPED: + snprintf(buffer + length, sizeof(buffer) - length, + ",keychar=%lc,rawcode=%u", + (uint16_t) event->data.keyboard.keychar, + event->data.keyboard.rawcode); + + #ifdef WE_REALLY_WANT_A_POINTER + char *buf = malloc (6); + #else + char buf[6]; + #endif + + sprintf(buf, "%lc", (uint16_t) event->data.keyboard.keychar); + + #ifdef WE_REALLY_WANT_A_POINTER + free (buf); + #endif + + if (strcmp(buf, cevent) == 0) { + int stopEvent = stop_event(); + // printf("%d\n", stopEvent); + cstatus = 0; + } + // return (char*) event->data.keyboard.keychar; + break; + + case EVENT_MOUSE_PRESSED: + case EVENT_MOUSE_RELEASED: + case EVENT_MOUSE_CLICKED: + case EVENT_MOUSE_MOVED: + case EVENT_MOUSE_DRAGGED: + snprintf(buffer + length, sizeof(buffer) - length, + ",x=%i,y=%i,button=%i,clicks=%i", + event->data.mouse.x, event->data.mouse.y, + event->data.mouse.button, event->data.mouse.clicks); + + int abutton = event->data.mouse.button; + int aclicks = event->data.mouse.clicks; + int amouse = -1; + + if (strcmp(cevent, "mleft") == 0) { + amouse = 1; + } + if (strcmp(cevent, "mright") == 0) { + amouse = 2; + } + if (strcmp(cevent, "wheelDown") == 0) { + amouse = 4; + } + if (strcmp(cevent, "wheelUp") == 0) { + amouse = 5; + } + if (strcmp(cevent, "wheelLeft") == 0) { + amouse = 6; + } + if (strcmp(cevent, "wheelRight") == 0) { + amouse = 7; + } + if (abutton == amouse && aclicks == 1) { + int stopEvent = stop_event(); + cstatus = 0; + } + + break; + + case EVENT_MOUSE_WHEEL: + snprintf(buffer + length, sizeof(buffer) - length, + ",type=%i,amount=%i,rotation=%i", + event->data.wheel.type, event->data.wheel.amount, + event->data.wheel.rotation); + break; + + default: + break; + } + + // fprintf(stdout, "----%s\n", buffer); +} + +#endif \ No newline at end of file diff --git a/event/goEvent.h b/event/goEvent.h index 21ec2ae..0a1c431 100644 --- a/event/goEvent.h +++ b/event/goEvent.h @@ -7,6 +7,7 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. + #ifndef goevent_h #define goevent_h #ifdef HAVE_CONFIG_H @@ -15,23 +16,23 @@ #include #include "pub.h" -#include "../chan/eb_chan.h" -eb_chan events; +// #include "../chan/eb_chan.h" +#include "dispatch_proc.h" void go_send(char*); void go_sleep(void); -bool sending = false; - -void startev(){ +void start_ev(){ events = eb_chan_create(1024); eb_chan_retain(events); sending = true; - add_event("q"); + // add_event("q"); + add_event_async(); } void pollEv(){ - if (events == NULL) return; + if (events == NULL) { return; } + for (;eb_chan_buf_len(events)!=0;) { char* tmp; if (eb_chan_try_recv(events, (const void**) &tmp) @@ -51,82 +52,25 @@ void endPoll(){ eb_chan_release(events); } -void dispatch_proc(iohook_event * const event) { - if(!sending) return; - // leaking memory? hope not - char* buffer = calloc(200, sizeof(char)); +int add_event(char *key_event) { + // (uint16_t *) + cevent = key_event; + add(&dispatch_proc_end); - switch (event->type) { - case EVENT_HOOK_ENABLED: - case EVENT_HOOK_DISABLED: - sprintf(buffer,"{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu}", - event->type, event->time, event->mask,event->reserved); - break; // send it? - case EVENT_KEY_PRESSED: - case EVENT_KEY_RELEASED: - case EVENT_KEY_TYPED: - sprintf(buffer, - "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"keycode\":%hu,\"rawcode\":%hu,\"keychar\":%hu}", - event->type, event->time, event->mask,event->reserved, - event->data.keyboard.keycode, - event->data.keyboard.rawcode, - event->data.keyboard.keychar); - break; - case EVENT_MOUSE_PRESSED: - case EVENT_MOUSE_RELEASED: - case EVENT_MOUSE_CLICKED: - case EVENT_MOUSE_MOVED: - case EVENT_MOUSE_DRAGGED: - sprintf(buffer, - "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"x\":%hd,\"y\":%hd,\"button\":%u,\"clicks\":%u}", - event->type, event->time, event->mask,event->reserved, - event->data.mouse.x, - event->data.mouse.y, - event->data.mouse.button, - event->data.mouse.clicks); - break; - case EVENT_MOUSE_WHEEL: - sprintf(buffer, - "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"clicks\":%hu,\"x\":%hd,\"y\":%hd,\"type\":%hu,\"ammount\":%hu,\"rotation\":%hd,\"direction\":%hu}", - event->type, event->time, event->mask, event->reserved, - event->data.wheel.clicks, - event->data.wheel.x, - event->data.wheel.y, - event->data.wheel.type, - event->data.wheel.amount, - event->data.wheel.rotation, - event->data.wheel.direction); - break; - default: - fprintf(stderr,"\nError on file: %s, unusual event->type: %i\n",__FILE__,event->type); - return; - } - - // to-do remove this for - int i; - for (i = 0; i < 5; i++) { - switch (eb_chan_try_send(events,buffer)) { // never block the hook callback - case eb_chan_res_ok: - i=5; - break; - default: - if (i == 4) { // let's not leak memory - free(buffer); - } - continue; - } - } - - // fprintf(stdout, "----%s\n", buffer); + return cstatus; } -int add_event(char *key_event) { - cevent = key_event; +void add_event_async(){ + add(&dispatch_proc); +} + +int add(dispatcher_t dispatch) { // Set the logger callback for library output. hook_set_logger(&loggerProc); // Set the event callback for IOhook events. - hook_set_dispatch_proc(&dispatch_proc); + hook_set_dispatch_proc(dispatch); + // Start the hook and block. // NOTE If EVENT_HOOK_ENABLED was delivered, the status will always succeed. int status = hook_run(); @@ -198,9 +142,9 @@ int add_event(char *key_event) { break; } - // return status; + return status; // printf("%d\n", status); - return cstatus; + // return cstatus; } int stop_event(){ diff --git a/event/pub.h b/event/pub.h index 08d1df3..3aa7529 100644 --- a/event/pub.h +++ b/event/pub.h @@ -33,21 +33,28 @@ #include #include #include -#include "../hook/iohook.h" +#include "../hook/iohook.h" +#include "../chan/eb_chan.h" + +eb_chan events; +bool sending = false; int vccode[100]; int codesz; char *cevent; -int rrevent; // uint16_t *cevent; int cstatus = 1; int event_status; +int rrevent; - -int stop_event(); +int add(dispatcher_t dispatch); +void add_event_async(); int add_event(char *key_event); +int stop_event(); + +void dispatch_proc_end(iohook_event * const event); // int allEvent(char *key_event); int allEvent(char *key_event, int vcode[], int size); diff --git a/hook.go b/hook.go index 0a32ec8..65d59dd 100644 --- a/hook.go +++ b/hook.go @@ -131,7 +131,7 @@ func KeychartoRawcode(kc string) uint16 { // returns event channel func Start() chan Event { asyncon = true - go C.startev() + go C.start_ev() go func() { for { diff --git a/tables.go b/tables.go index c2e3829..9a4ec86 100644 --- a/tables.go +++ b/tables.go @@ -1,7 +1,7 @@ package hook var ( - raw2key = map[uint16]string{ //https://github.com/wesbos/keycodes + raw2key = map[uint16]string{ // https://github.com/wesbos/keycodes 0: "error", 3: "break", 8: "backspace", From 2a4d6bbf54c59ac3ab1733f9f893eb27189e358e Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 21 Feb 2019 08:29:22 -0400 Subject: [PATCH 028/120] rename add() func to add_hook() --- event/goEvent.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/event/goEvent.h b/event/goEvent.h index 0a1c431..e0db196 100644 --- a/event/goEvent.h +++ b/event/goEvent.h @@ -55,16 +55,16 @@ void endPoll(){ int add_event(char *key_event) { // (uint16_t *) cevent = key_event; - add(&dispatch_proc_end); + add_hook(&dispatch_proc_end); return cstatus; } void add_event_async(){ - add(&dispatch_proc); + add_hook(&dispatch_proc); } -int add(dispatcher_t dispatch) { +int add_hook(dispatcher_t dispatch) { // Set the logger callback for library output. hook_set_logger(&loggerProc); From a2f711c1441035f6b4b28c65c9f750ff057ddc42 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 21 Feb 2019 08:57:48 -0400 Subject: [PATCH 029/120] update code style --- hook.go | 27 ++++++++++++++++++--------- hook/iohook.h | 1 + 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/hook.go b/hook.go index 65d59dd..3cdb57f 100644 --- a/hook.go +++ b/hook.go @@ -93,23 +93,32 @@ func (e Event) String() string { case HookDisabled: return fmt.Sprintf("%v - Event: {Kind: HookDisabled}", e.When) case KeyUp: - return fmt.Sprintf("%v - Event: {Kind: KeyUp, Rawcode: %v, Keychar: %v}", e.When, e.Rawcode, e.Keychar) + return fmt.Sprintf("%v - Event: {Kind: KeyUp, Rawcode: %v, Keychar: %v}", + e.When, e.Rawcode, e.Keychar) case KeyHold: - return fmt.Sprintf("%v - Event: {Kind: KeyHold, Rawcode: %v, Keychar: %v}", e.When, e.Rawcode, e.Keychar) + return fmt.Sprintf("%v - Event: {Kind: KeyHold, Rawcode: %v, Keychar: %v}", + e.When, e.Rawcode, e.Keychar) case KeyDown: - return fmt.Sprintf("%v - Event: {Kind: KeyDown, Rawcode: %v, Keychar: %v}", e.When, e.Rawcode, e.Keychar) + return fmt.Sprintf("%v - Event: {Kind: KeyDown, Rawcode: %v, Keychar: %v}", + e.When, e.Rawcode, e.Keychar) case MouseUp: - return fmt.Sprintf("%v - Event: {Kind: MouseUp, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) + return fmt.Sprintf("%v - Event: {Kind: MouseUp, Button: %v, X: %v, Y: %v, Clicks: %v}", + e.When, e.Button, e.X, e.Y, e.Clicks) case MouseHold: - return fmt.Sprintf("%v - Event: {Kind: MouseHold, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) + return fmt.Sprintf("%v - Event: {Kind: MouseHold, Button: %v, X: %v, Y: %v, Clicks: %v}", + e.When, e.Button, e.X, e.Y, e.Clicks) case MouseDown: - return fmt.Sprintf("%v - Event: {Kind: MouseDown, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) + return fmt.Sprintf("%v - Event: {Kind: MouseDown, Button: %v, X: %v, Y: %v, Clicks: %v}", + e.When, e.Button, e.X, e.Y, e.Clicks) case MouseMove: - return fmt.Sprintf("%v - Event: {Kind: MouseMove, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) + return fmt.Sprintf("%v - Event: {Kind: MouseMove, Button: %v, X: %v, Y: %v, Clicks: %v}", + e.When, e.Button, e.X, e.Y, e.Clicks) case MouseDrag: - return fmt.Sprintf("%v - Event: {Kind: MouseDrag, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) + return fmt.Sprintf("%v - Event: {Kind: MouseDrag, Button: %v, X: %v, Y: %v, Clicks: %v}", + e.When, e.Button, e.X, e.Y, e.Clicks) case MouseWheel: - return fmt.Sprintf("%v - Event: {Kind: MouseWheel, Amount: %v, Rotation: %v, Direction: %v}", e.When, e.Amount, e.Rotation, e.Direction) + return fmt.Sprintf("%v - Event: {Kind: MouseWheel, Amount: %v, Rotation: %v, Direction: %v}", + e.When, e.Amount, e.Rotation, e.Direction) case FakeEvent: return fmt.Sprintf("%v - Event: {Kind: FakeEvent}", e.When) } diff --git a/hook/iohook.h b/hook/iohook.h index 34a1e94..e135ba6 100644 --- a/hook/iohook.h +++ b/hook/iohook.h @@ -79,6 +79,7 @@ typedef struct _screen_data { typedef struct _keyboard_event_data { uint16_t keycode; uint16_t rawcode; + // uint16_t keychar; uint32_t keychar; // char *keychar; } keyboard_event_data, From 8d5c93253274de95c0fa6d4921f4c1e87c0f19ae Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 21 Feb 2019 09:10:31 -0400 Subject: [PATCH 030/120] fixed c99 func and update code style --- event/pub.h | 2 +- hook.go | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/event/pub.h b/event/pub.h index 3aa7529..80a76d9 100644 --- a/event/pub.h +++ b/event/pub.h @@ -49,7 +49,7 @@ int cstatus = 1; int event_status; int rrevent; -int add(dispatcher_t dispatch); +int add_hook(dispatcher_t dispatch); void add_event_async(); int add_event(char *key_event); int stop_event(); diff --git a/hook.go b/hook.go index 3cdb57f..86316ba 100644 --- a/hook.go +++ b/hook.go @@ -96,28 +96,36 @@ func (e Event) String() string { return fmt.Sprintf("%v - Event: {Kind: KeyUp, Rawcode: %v, Keychar: %v}", e.When, e.Rawcode, e.Keychar) case KeyHold: - return fmt.Sprintf("%v - Event: {Kind: KeyHold, Rawcode: %v, Keychar: %v}", + return fmt.Sprintf( + "%v - Event: {Kind: KeyHold, Rawcode: %v, Keychar: %v}", e.When, e.Rawcode, e.Keychar) case KeyDown: - return fmt.Sprintf("%v - Event: {Kind: KeyDown, Rawcode: %v, Keychar: %v}", + return fmt.Sprintf( + "%v - Event: {Kind: KeyDown, Rawcode: %v, Keychar: %v}", e.When, e.Rawcode, e.Keychar) case MouseUp: - return fmt.Sprintf("%v - Event: {Kind: MouseUp, Button: %v, X: %v, Y: %v, Clicks: %v}", + return fmt.Sprintf( + "%v - Event: {Kind: MouseUp, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) case MouseHold: - return fmt.Sprintf("%v - Event: {Kind: MouseHold, Button: %v, X: %v, Y: %v, Clicks: %v}", + return fmt.Sprintf( + "%v - Event: {Kind: MouseHold, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) case MouseDown: - return fmt.Sprintf("%v - Event: {Kind: MouseDown, Button: %v, X: %v, Y: %v, Clicks: %v}", + return fmt.Sprintf( + "%v - Event: {Kind: MouseDown, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) case MouseMove: - return fmt.Sprintf("%v - Event: {Kind: MouseMove, Button: %v, X: %v, Y: %v, Clicks: %v}", + return fmt.Sprintf( + "%v - Event: {Kind: MouseMove, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) case MouseDrag: - return fmt.Sprintf("%v - Event: {Kind: MouseDrag, Button: %v, X: %v, Y: %v, Clicks: %v}", + return fmt.Sprintf( + "%v - Event: {Kind: MouseDrag, Button: %v, X: %v, Y: %v, Clicks: %v}", e.When, e.Button, e.X, e.Y, e.Clicks) case MouseWheel: - return fmt.Sprintf("%v - Event: {Kind: MouseWheel, Amount: %v, Rotation: %v, Direction: %v}", + return fmt.Sprintf( + "%v - Event: {Kind: MouseWheel, Amount: %v, Rotation: %v, Direction: %v}", e.When, e.Amount, e.Rotation, e.Direction) case FakeEvent: return fmt.Sprintf("%v - Event: {Kind: FakeEvent}", e.When) From ab3379003d5253997402703790c05191d3423c97 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 24 Feb 2019 09:19:44 -0400 Subject: [PATCH 031/120] Fixed run code in mac warning --- event/dispatch_proc.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/event/dispatch_proc.h b/event/dispatch_proc.h index fddf9c0..a598b21 100644 --- a/event/dispatch_proc.h +++ b/event/dispatch_proc.h @@ -31,7 +31,7 @@ void dispatch_proc(iohook_event * const event) { case EVENT_KEY_RELEASED: case EVENT_KEY_TYPED: sprintf(buffer, - "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"keycode\":%hu,\"rawcode\":%hu,\"keychar\":%hu}", + "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"keycode\":%hu,\"rawcode\":%hu,\"keychar\":%d}", event->type, event->time, event->mask,event->reserved, event->data.keyboard.keycode, event->data.keyboard.rawcode, @@ -52,7 +52,7 @@ void dispatch_proc(iohook_event * const event) { break; case EVENT_MOUSE_WHEEL: sprintf(buffer, - "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"clicks\":%hu,\"x\":%hd,\"y\":%hd,\"type\":%hu,\"ammount\":%hu,\"rotation\":%hd,\"direction\":%hu}", + "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"clicks\":%hu,\"x\":%hd,\"y\":%hd,\"type\":%d,\"ammount\":%hu,\"rotation\":%hu,\"direction\":%d}", event->type, event->time, event->mask, event->reserved, event->data.wheel.clicks, event->data.wheel.x, From 4e658a44067ca3562de0ed25c145d3b837ad2ae4 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Wed, 27 Feb 2019 09:59:41 -0400 Subject: [PATCH 032/120] update rotation to int32 Fixed sometimes json.Unmarshal break --- event/dispatch_proc.h | 2 +- hook.go | 2 +- hook/iohook.h | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/event/dispatch_proc.h b/event/dispatch_proc.h index a598b21..15ef19a 100644 --- a/event/dispatch_proc.h +++ b/event/dispatch_proc.h @@ -52,7 +52,7 @@ void dispatch_proc(iohook_event * const event) { break; case EVENT_MOUSE_WHEEL: sprintf(buffer, - "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"clicks\":%hu,\"x\":%hd,\"y\":%hd,\"type\":%d,\"ammount\":%hu,\"rotation\":%hu,\"direction\":%d}", + "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"clicks\":%hu,\"x\":%hd,\"y\":%hd,\"type\":%d,\"ammount\":%hu,\"rotation\":%d,\"direction\":%d}", event->type, event->time, event->mask, event->reserved, event->data.wheel.clicks, event->data.wheel.x, diff --git a/hook.go b/hook.go index 86316ba..00927b4 100644 --- a/hook.go +++ b/hook.go @@ -76,7 +76,7 @@ type Event struct { Y int16 `json:"y"` Amount uint16 `json:"amount"` - Rotation int16 `json:"rotation"` + Rotation int32 `json:"rotation"` Direction uint8 `json:"direction"` } diff --git a/hook/iohook.h b/hook/iohook.h index e135ba6..6fd3471 100644 --- a/hook/iohook.h +++ b/hook/iohook.h @@ -103,7 +103,8 @@ typedef struct _mouse_wheel_event_data { int16_t y; uint8_t type; uint16_t amount; - int16_t rotation; + int32_t rotation; + // int16_t rotation; uint8_t direction; } mouse_wheel_event_data; From 9bf349ed0b6df52faa076d60a71000404e86fb34 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Wed, 27 Feb 2019 10:34:24 -0400 Subject: [PATCH 033/120] update appveyor to go1.12 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 7d8bfb8..43c8bc8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.11.5 + GOVERSION: 1.12 # GOPATH: c:\gopath # scripts that run after cloning repository From bbbbefb5ff8541a05870596ea081ad8581b5879f Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 5 Mar 2019 15:29:54 -0400 Subject: [PATCH 034/120] add center mouse and update code style --- event/dispatch_proc.h | 3 +++ extern.go | 1 + 2 files changed, 4 insertions(+) diff --git a/event/dispatch_proc.h b/event/dispatch_proc.h index 15ef19a..be18d0d 100644 --- a/event/dispatch_proc.h +++ b/event/dispatch_proc.h @@ -181,6 +181,9 @@ void dispatch_proc_end(iohook_event * const event) { if (strcmp(cevent, "mright") == 0) { amouse = 2; } + if (strcmp(cevent, "center") == 0) { + amouse = 3; + } if (strcmp(cevent, "wheelDown") == 0) { amouse = 4; } diff --git a/extern.go b/extern.go index 6cacb4c..b1d0aff 100644 --- a/extern.go +++ b/extern.go @@ -5,6 +5,7 @@ package hook // #include "event/hook_async.h" */ import "C" + import ( "log" "time" From bc414970f3d65633e1aa51572a4f7e8c8f34e8d8 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 15 Mar 2019 13:45:47 -0400 Subject: [PATCH 035/120] Add lock, Fixed fatal error: concurrent map writes --- extern.go | 2 ++ hook.go | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/extern.go b/extern.go index b1d0aff..4d1df25 100644 --- a/extern.go +++ b/extern.go @@ -24,7 +24,9 @@ func go_send(s *C.char) { } if out.Keychar != CharUndefined { + lck.Lock() raw2key[out.Rawcode] = string([]rune{out.Keychar}) + lck.Unlock() } // todo bury this deep into the C lib so that the time is correct diff --git a/hook.go b/hook.go index 00927b4..ad29a47 100644 --- a/hook.go +++ b/hook.go @@ -26,6 +26,7 @@ import "C" import ( "fmt" + "sync" "time" "unsafe" ) @@ -83,6 +84,8 @@ type Event struct { var ( ev = make(chan Event, 1024) asyncon = false + + lck sync.RWMutex ) // String return hook kind string @@ -136,6 +139,9 @@ func (e Event) String() string { // RawcodetoKeychar rawcode to keychar func RawcodetoKeychar(r uint16) string { + lck.RLock() + defer lck.RUnlock() + return raw2key[r] } From a7a38946710e764b00839160e13a92b09e40e96a Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 18 Mar 2019 13:26:28 -0400 Subject: [PATCH 036/120] update code and CI --- appveyor.yml | 2 +- hook.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 43c8bc8..6f90e2c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.12 + GOVERSION: 1.12.1 # GOPATH: c:\gopath # scripts that run after cloning repository diff --git a/hook.go b/hook.go index ad29a47..6bd612f 100644 --- a/hook.go +++ b/hook.go @@ -20,7 +20,6 @@ package hook //#cgo windows LDFLAGS: -lgdi32 -luser32 #include "event/goEvent.h" - */ import "C" From 783846adc983d72a52c43e802f6491d7815a0347 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sat, 13 Apr 2019 13:25:26 -0400 Subject: [PATCH 037/120] Fixed #3 mouse is_drag error on x11 --- hook/x11/hook_c.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hook/x11/hook_c.h b/hook/x11/hook_c.h index 4733051..cc9c5e6 100644 --- a/hook/x11/hook_c.h +++ b/hook/x11/hook_c.h @@ -746,7 +746,7 @@ void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { // Check the upper half of virtual modifiers for non-zero // values and set the mouse dragged flag. - hook->input.mouse.is_dragged = (event.mask >> 8 > 0); + hook->input.mouse.is_dragged = ((event.mask & 0x1F00) > 0); if (hook->input.mouse.is_dragged) { // Create Mouse Dragged event. event.type = EVENT_MOUSE_DRAGGED; From 5b9ae1915c78b05a84b01dec19ad7a84daea8050 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sat, 13 Apr 2019 13:26:57 -0400 Subject: [PATCH 038/120] update .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index e22c771..6d4051e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +# Object files +.DS_Store +.vscode +.idea + # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a From 39135b21e438ce0dc31a1c9f36463e49c7ceab70 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 14 Apr 2019 13:10:59 -0400 Subject: [PATCH 039/120] update appveyor to go1.12.4 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 6f90e2c..71a7240 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.12.1 + GOVERSION: 1.12.4 # GOPATH: c:\gopath # scripts that run after cloning repository From c6f3965b549384a341a7c67956c715ce6218f176 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 21 May 2019 11:34:43 -0400 Subject: [PATCH 040/120] update appveyor to go1.12.5 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 71a7240..7803091 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.12.4 + GOVERSION: 1.12.5 # GOPATH: c:\gopath # scripts that run after cloning repository From ac371ff7cb4e7d6c72632f2d9d2bd79d19e29cf2 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Wed, 12 Jun 2019 12:50:48 -0400 Subject: [PATCH 041/120] update appveyor to go1.12.6 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 7803091..e3ed656 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.12.5 + GOVERSION: 1.12.6 # GOPATH: c:\gopath # scripts that run after cloning repository From 33863b34017c4b177ee3021393c3ec184ac314be Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sat, 22 Jun 2019 12:58:07 -0400 Subject: [PATCH 042/120] update go send json.Unmarshal error log --- extern.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extern.go b/extern.go index 4d1df25..0e12a83 100644 --- a/extern.go +++ b/extern.go @@ -20,7 +20,7 @@ func go_send(s *C.char) { err := json.Unmarshal(str, &out) if err != nil { - log.Fatal(err) + log.Fatal("json.Unmarshal error is: ", err) } if out.Keychar != CharUndefined { @@ -32,7 +32,7 @@ func go_send(s *C.char) { // todo bury this deep into the C lib so that the time is correct out.When = time.Now() // at least it's consistent if err != nil { - log.Fatal(err) + log.Fatal("json.Unmarshal error is: ", err) } // todo: maybe make non-bloking From 97c6ba28904a3b119a864420f015403c5f642151 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Wed, 10 Jul 2019 12:07:08 -0400 Subject: [PATCH 043/120] update appveyor to go1.12.7 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index e3ed656..5499aa2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.12.6 + GOVERSION: 1.12.7 # GOPATH: c:\gopath # scripts that run after cloning repository From ec680ecf20e895eb0e5935e87bafd85041d77ba5 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 28 Jul 2019 14:12:01 -0400 Subject: [PATCH 044/120] update example and readme code --- README.md | 2 +- example/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 251729c..3bc97a7 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ func main() { defer hook.End() for ev := range EvChan { - fmt.Println(ev) + fmt.Println("hook: ", ev) } } ``` diff --git a/example/main.go b/example/main.go index 9b1a7e8..4c1ac72 100644 --- a/example/main.go +++ b/example/main.go @@ -29,7 +29,7 @@ func base() { defer hook.End() for ev := range EvChan { - fmt.Println(ev) + fmt.Println("hook: ", ev) } } From 24fd05492e0379b3c60345b467be299db020c2a2 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 16 Aug 2019 10:13:36 -0400 Subject: [PATCH 045/120] update appveyor to go1.12.9 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 5499aa2..7865efd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.12.7 + GOVERSION: 1.12.9 # GOPATH: c:\gopath # scripts that run after cloning repository From d3470a865ab2b3a0bfde5c04f98e4c76eb69b25a Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 27 Aug 2019 12:51:46 -0400 Subject: [PATCH 046/120] add get gohook version code --- hook.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hook.go b/hook.go index 6bd612f..0e592bc 100644 --- a/hook.go +++ b/hook.go @@ -31,6 +31,9 @@ import ( ) const ( + // Version get the gohook version + Version = "v0.10.0.47, Sierra Nevada!" + // HookEnabled honk enable status HookEnabled = 1 // iota HookDisabled = 2 From 5bea7a10cc3a84a11fd1b4ba76d518b6fca7a537 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 5 Sep 2019 13:43:53 -0400 Subject: [PATCH 047/120] update appveyor to go1.13 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 7865efd..d2f2e40 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.12.9 + GOVERSION: 1.13 # GOPATH: c:\gopath # scripts that run after cloning repository From 221bcb399156a9445c5f6b32af872715c50fdc68 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 27 Sep 2019 13:09:41 -0400 Subject: [PATCH 048/120] update appveyor go version --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index d2f2e40..320dd35 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.13 + GOVERSION: 1.13.1 # GOPATH: c:\gopath # scripts that run after cloning repository From 4f5b3ead12b1752213323fd733b23821378f0c62 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 10 Oct 2019 13:11:32 -0400 Subject: [PATCH 049/120] update go hook example docs --- example/main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example/main.go b/example/main.go index 4c1ac72..c454a13 100644 --- a/example/main.go +++ b/example/main.go @@ -3,9 +3,10 @@ package main import ( "fmt" - "github.com/robotn/gohook" + hook "github.com/robotn/gohook" ) +// hook listen and return values using detailed examples func add() { s := hook.Start() defer hook.End() @@ -24,6 +25,7 @@ func add() { } } +// base hook example func base() { EvChan := hook.Start() defer hook.End() From a745895ba8c03c157b2539d7844b954eaf7ab4c8 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 18 Oct 2019 10:10:01 -0400 Subject: [PATCH 050/120] update appveyor to go1.13.3 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 320dd35..42cef4a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.13.1 + GOVERSION: 1.13.3 # GOPATH: c:\gopath # scripts that run after cloning repository From 9a38284fc4c98f661a919b26c9e9e2abcff1c944 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sat, 26 Oct 2019 13:05:46 -0400 Subject: [PATCH 051/120] update go mod file --- go.mod | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.mod b/go.mod index 214fb13..58688d3 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,3 @@ module github.com/robotn/gohook + +go 1.13 From 99f27f122aa13eca196e696b0ea353e7f7b04106 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 29 Oct 2019 12:45:28 -0400 Subject: [PATCH 052/120] update godoc --- hook.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hook.go b/hook.go index 0e592bc..e7b58a9 100644 --- a/hook.go +++ b/hook.go @@ -57,9 +57,11 @@ const ( ) // Event Holds a system event +// // If it's a Keyboard event the relevant fields are: // Mask, Keycode, Rawcode, and Keychar, // Keychar is probably what you want. +// // If it's a Mouse event the relevant fields are: // Button, Clicks, X, Y, Amount, Rotation and Direction type Event struct { From 5ca3c35c1cac943cdc82b02a0e7a6d7fffe89acc Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 31 Oct 2019 11:08:50 -0400 Subject: [PATCH 053/120] update hook example --- example/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/main.go b/example/main.go index c454a13..eddc31c 100644 --- a/example/main.go +++ b/example/main.go @@ -27,10 +27,10 @@ func add() { // base hook example func base() { - EvChan := hook.Start() + evChan := hook.Start() defer hook.End() - for ev := range EvChan { + for ev := range evChan { fmt.Println("hook: ", ev) } } From 6d41a8bfece3ab084032d891bf96d5ca3360ef80 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 1 Nov 2019 13:42:55 -0400 Subject: [PATCH 054/120] update appveyor to go1.13.4 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 42cef4a..0a76695 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.13.3 + GOVERSION: 1.13.4 # GOPATH: c:\gopath # scripts that run after cloning repository From aaa2000ed97a53d893898b1ea481cf09475298bb Mon Sep 17 00:00:00 2001 From: vcaesar Date: Wed, 13 Nov 2019 12:37:29 -0400 Subject: [PATCH 055/120] update cgo code and version --- hook.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hook.go b/hook.go index e7b58a9..9b0b7d8 100644 --- a/hook.go +++ b/hook.go @@ -11,7 +11,7 @@ package hook /* -#cgo darwin CFLAGS: -x objective-c -Wno-deprecated-declarations +#cgo darwin CFLAGS: -x objective-c -Wno-deprecated-declarations #cgo darwin LDFLAGS: -framework Cocoa #cgo linux CFLAGS:-I/usr/src @@ -32,7 +32,7 @@ import ( const ( // Version get the gohook version - Version = "v0.10.0.47, Sierra Nevada!" + Version = "v0.20.0.56, Sierra Nevada!" // HookEnabled honk enable status HookEnabled = 1 // iota From 59172123c9c1b30613340598fcca1271f53f1f91 Mon Sep 17 00:00:00 2001 From: vz Date: Thu, 28 Nov 2019 12:38:02 -0400 Subject: [PATCH 056/120] Create go.yml --- .github/workflows/go.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/go.yml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..279ccfe --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,28 @@ +name: Go +on: [push] +jobs: + + build: + name: Build + runs-on: macOS-10.14 + steps: + + - name: Set up Go 1.13 + uses: actions/setup-go@v1 + with: + go-version: 1.13 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v1 + + - name: Get dependencies + run: | + go get -v -t -d ./... + if [ -f Gopkg.toml ]; then + curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh + dep ensure + fi + + - name: Build + run: go build -v . From 123f5daf1c18fd80f893170e124ef1b35c04b8d5 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 6 Dec 2019 13:09:43 -0400 Subject: [PATCH 057/120] Update appveyor to go1.13.5 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 0a76695..a57ff2f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.13.4 + GOVERSION: 1.13.5 # GOPATH: c:\gopath # scripts that run after cloning repository From 2c5f9b09f6c96a2be2f4acefc52c5b0c39733beb Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 6 Dec 2019 13:10:05 -0400 Subject: [PATCH 058/120] Update go.yml --- .github/workflows/go.yml | 45 ++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 279ccfe..14e68b8 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,28 +1,33 @@ name: Go on: [push] jobs: + test: + # name: build + strategy: + matrix: + # go: [1.12.x, 1.13.x] + os: [macOS-latest, windows-latest] # ubuntu-latest + runs-on: ${{ matrix.os }} - build: - name: Build - runs-on: macOS-10.14 steps: + - name: Set up Go 1.13 + uses: actions/setup-go@v1 + with: + go-version: 1.13 + id: go - - name: Set up Go 1.13 - uses: actions/setup-go@v1 - with: - go-version: 1.13 - id: go + - name: Check out code into the Go module directory + uses: actions/checkout@v1 - - name: Check out code into the Go module directory - uses: actions/checkout@v1 + - name: Get dependencies + run: | + go get -v -t -d ./... + # if [ -f Gopkg.toml ]; then + # curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh + # dep ensure + # fi - - name: Get dependencies - run: | - go get -v -t -d ./... - if [ -f Gopkg.toml ]; then - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh - dep ensure - fi - - - name: Build - run: go build -v . + - name: Build + run: go build -v . + # - name: Test + # run: go test -v . From d7a0e66e8bf99b1c148ca8140cce64a8cda5eae0 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sat, 7 Dec 2019 14:27:27 -0400 Subject: [PATCH 059/120] Update README.md, Add more CI badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3bc97a7..fc70f92 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # gohook +[![Build Status](https://github.com/robotn/gohook/workflows/Go/badge.svg)](https://github.com/robotn/gohook/commits/master) [![CircleCI Status](https://circleci.com/gh/robotn/gohook.svg?style=shield)](https://circleci.com/gh/robotn/gohook) ![Appveyor](https://ci.appveyor.com/api/projects/status/github/robotn/gohook?branch=master&svg=true) [![Go Report Card](https://goreportcard.com/badge/github.com/robotn/gohook)](https://goreportcard.com/report/github.com/robotn/gohook) From 98eb507a75d9f7943fcdb9642c14b4ac5aa75479 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 8 Dec 2019 15:57:06 -0400 Subject: [PATCH 060/120] Update some logger code, use warn not error --- hook/x11/input_c.h | 5 +++-- hook/x11/properties_c.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/hook/x11/input_c.h b/hook/x11/input_c.h index e132d03..b734189 100644 --- a/hook/x11/input_c.h +++ b/hook/x11/input_c.h @@ -1880,8 +1880,9 @@ void load_input_helper(Display *disp) { is_evdev = true; } else #endif + if (strncmp(layout_name, prefix_xfree86, strlen(prefix_xfree86)) != 0) { - // logger(LOG_LEVEL_ERROR, + // logger(LOG_LEVEL_WARN, // "%s [%u]: Unknown keycode name '%s', please file a bug report!\n", // __FUNCTION__, __LINE__, layout_name); } @@ -1894,7 +1895,7 @@ void load_input_helper(Display *disp) { XkbFreeClientMap(desc, XkbGBN_AllComponentsMask, True); } else { - logger(LOG_LEVEL_ERROR, + logger(LOG_LEVEL_WARN, "%s [%u]: XkbGetKeyboard failed to locate a valid keyboard!\n", __FUNCTION__, __LINE__); } diff --git a/hook/x11/properties_c.h b/hook/x11/properties_c.h index 8a94b92..1124df0 100644 --- a/hook/x11/properties_c.h +++ b/hook/x11/properties_c.h @@ -444,7 +444,7 @@ void on_library_load() { // Open local display. properties_disp = XOpenDisplay(XDisplayName(NULL)); if (properties_disp == NULL) { - logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + logger(LOG_LEVEL_WARN, "%s [%u]: %s\n", __FUNCTION__, __LINE__, "XOpenDisplay failure!"); } else { From ad571b9a4f8df94da1aeab62227aeb040d9615e8 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 12 Dec 2019 13:22:24 -0400 Subject: [PATCH 061/120] update and fmt appveyor.yml --- appveyor.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a57ff2f..3f9dc22 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,7 +6,7 @@ version: "{build}" os: Visual Studio 2017 # Platform. -# platform: +# platform: # - x64 # - x86 @@ -26,7 +26,7 @@ environment: MSYSTEM: MINGW64 PATH: C:\msys64\mingw64\bin\;C:\Program Files (x86)\NSIS\;%PATH% - GOARCH: 386 - # GOVERSION: 1.9.3 + # GOVERSION: 1.9.3 GETH_ARCH: 386 MSYS2_ARCH: i686 MSYS2_BITS: 32 @@ -43,7 +43,7 @@ environment: # - go version # - go env # - gcc --version - # - python --version +# - python --version install: - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% @@ -53,7 +53,7 @@ install: - 7z x go%GOVERSION%.windows-%GETH_ARCH%.zip -y -oC:\ > NUL - go version - go env - - gcc --version + - gcc --version # To run your custom scripts instead of automatic MSBuild build_script: From 172f0885e321f94f2514a1b49e7d436f2a62504f Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 12 Dec 2019 13:23:25 -0400 Subject: [PATCH 062/120] update and fmt circle.yml --- circle.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/circle.yml b/circle.yml index 7570316..04110ab 100644 --- a/circle.yml +++ b/circle.yml @@ -10,10 +10,10 @@ jobs: - checkout # specify any bash command here prefixed with `run: ` # override: - # './...' is a relative pattern which means all subdirectories + # './...' is a relative pattern which means all subdirectories # - run: go get -u golang.org/x/sys/unix - run: go get -v -t -d ./... - - run: go test -v ./... + - run: go test -v ./... # codecov.io # - run: go test -v -covermode=count -coverprofile=coverage.out - # - run: bash <(curl -s https://codecov.io/bash) \ No newline at end of file + # - run: bash <(curl -s https://codecov.io/bash) From 5bfaedadd9ddcabaaa77d6486063cec76913c01b Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sat, 11 Jan 2020 11:26:47 -0400 Subject: [PATCH 063/120] update appveyor to go1.13.6 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 3f9dc22..bc2c136 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.13.5 + GOVERSION: 1.13.6 # GOPATH: c:\gopath # scripts that run after cloning repository From aea663e2b531e8b957c822aaec31d147748dd546 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Wed, 5 Feb 2020 10:39:57 -0400 Subject: [PATCH 064/120] update appveyor to go1.13.7 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index bc2c136..d561d25 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.13.6 + GOVERSION: 1.13.7 # GOPATH: c:\gopath # scripts that run after cloning repository From 6c89eeae6299d9286b29c0daf89b3c7b5daa1b67 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 21 Feb 2020 10:54:59 -0400 Subject: [PATCH 065/120] update appveyor to go1.13.8 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index d561d25..1b45c15 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.13.7 + GOVERSION: 1.13.8 # GOPATH: c:\gopath # scripts that run after cloning repository From b931a62015132488b92510efe67e8bd940750d91 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sat, 22 Feb 2020 12:09:45 -0400 Subject: [PATCH 066/120] add hook test code support --- go.mod | 2 ++ go.sum | 2 ++ hook_test.go | 15 +++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 go.sum create mode 100644 hook_test.go diff --git a/go.mod b/go.mod index 58688d3..f7ec693 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/robotn/gohook go 1.13 + +require github.com/vcaesar/tt v0.0.0-20200219144246-a190abe69d64 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..74d89ab --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/vcaesar/tt v0.0.0-20200219144246-a190abe69d64 h1:MwxT8hyvPcPafJ2LDmoFuH7Xpz5LZ6W3dKzwVQAHD/k= +github.com/vcaesar/tt v0.0.0-20200219144246-a190abe69d64/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg= diff --git a/hook_test.go b/hook_test.go new file mode 100644 index 0000000..30d87e7 --- /dev/null +++ b/hook_test.go @@ -0,0 +1,15 @@ +package hook + +import ( + "fmt" + "testing" + + "github.com/vcaesar/tt" +) + +func TestAdd(t *testing.T) { + fmt.Println("hook test...") + + e := Start() + tt.NotNil(t, e) +} From e15669d5302cc6c94e4d796dd84e40d272280428 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sat, 22 Feb 2020 12:11:56 -0400 Subject: [PATCH 067/120] update circle.yml --- circle.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 04110ab..2e965ff 100644 --- a/circle.yml +++ b/circle.yml @@ -9,11 +9,13 @@ jobs: steps: - checkout # specify any bash command here prefixed with `run: ` + - run: apt-get update + - run: apt-get -y install xvfb # override: # './...' is a relative pattern which means all subdirectories # - run: go get -u golang.org/x/sys/unix - run: go get -v -t -d ./... - - run: go test -v ./... + - run: xvfb-run go test -v ./... # codecov.io # - run: go test -v -covermode=count -coverprofile=coverage.out # - run: bash <(curl -s https://codecov.io/bash) From edd69ace647d19b1b143b073eefea8ed8fc04643 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sat, 22 Feb 2020 12:14:30 -0400 Subject: [PATCH 068/120] update appveyor.yml fixed test --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 1b45c15..3263006 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -67,7 +67,7 @@ build_script: test_script: # Unit tests - ps: Add-AppveyorTest "Unit Tests" -Outcome Running - - go test -u github.com/robotn/gohook... + - go test -v github.com/robotn/gohook... - ps: Update-AppveyorTest "Unit Tests" -Outcome Passed # notifications: From abe328aaa1c798974534b78d84b2539553dcd215 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 23 Feb 2020 11:02:21 -0400 Subject: [PATCH 069/120] update go.yml --- .github/workflows/go.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 14e68b8..8b0aa55 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -29,5 +29,5 @@ jobs: - name: Build run: go build -v . - # - name: Test - # run: go test -v . + - name: Test + run: go test -v . From 1a18374b244df383fd44f30e70f86ac96f9f1609 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 23 Feb 2020 11:02:58 -0400 Subject: [PATCH 070/120] add keycode test support --- hook_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hook_test.go b/hook_test.go index 30d87e7..d9540d5 100644 --- a/hook_test.go +++ b/hook_test.go @@ -13,3 +13,11 @@ func TestAdd(t *testing.T) { e := Start() tt.NotNil(t, e) } + +func TestKey(t *testing.T) { + k := RawcodetoKeychar(0) + tt.Equal(t, "error", k) + + r := KeychartoRawcode("error") + tt.Equal(t, 0, r) +} From 031ea6b81173c7c726c647c3d15a42cd49932244 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 23 Feb 2020 11:10:55 -0400 Subject: [PATCH 071/120] fixed event windows timestamp error --- hook/windows/hook_c.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/hook/windows/hook_c.h b/hook/windows/hook_c.h index ec36bc8..7a10218 100644 --- a/hook/windows/hook_c.h +++ b/hook/windows/hook_c.h @@ -314,7 +314,8 @@ LRESULT CALLBACK keyboard_hook_event_proc(int nCode, WPARAM wParam, LPARAM lPara static void process_button_pressed(MSLLHOOKSTRUCT *mshook, uint16_t button) { - uint64_t timestamp = GetMessageTime(); + // uint64_t timestamp = GetMessageTime(); + uint64_t timestamp = mshook->time; // Track the number of clicks, the button must match the previous button. if (button == click_button && (long int) (timestamp - click_time) <= hook_get_multi_click_time()) { @@ -364,7 +365,7 @@ static void process_button_pressed(MSLLHOOKSTRUCT *mshook, uint16_t button) { static void process_button_released(MSLLHOOKSTRUCT *mshook, uint16_t button) { // Populate mouse released event. - event.time = GetMessageTime(); + event.time = mshook->time; event.reserved = 0x00; event.type = EVENT_MOUSE_RELEASED; @@ -387,7 +388,7 @@ static void process_button_released(MSLLHOOKSTRUCT *mshook, uint16_t button) { // If the pressed event was not consumed... if (event.reserved ^ 0x01 && last_click.x == mshook->pt.x && last_click.y == mshook->pt.y) { // Populate mouse clicked event. - event.time = GetMessageTime(); + event.time = mshook->time; event.reserved = 0x00; event.type = EVENT_MOUSE_CLICKED; @@ -414,7 +415,7 @@ static void process_button_released(MSLLHOOKSTRUCT *mshook, uint16_t button) { } static void process_mouse_moved(MSLLHOOKSTRUCT *mshook) { - uint64_t timestamp = GetMessageTime(); + uint64_t timestamp = mshook->time; // We received a mouse move event with the mouse actually moving. // This verifies that the mouse was moved after being depressed. @@ -462,7 +463,7 @@ static void process_mouse_wheel(MSLLHOOKSTRUCT *mshook, uint8_t direction) { click_button = MOUSE_NOBUTTON; // Populate mouse wheel event. - event.time = GetMessageTime(); + event.time = mshook->time; event.reserved = 0x00; event.type = EVENT_MOUSE_WHEEL; From caaa106d8037ea8052111e7e96f38cbb20d47163 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 24 Feb 2020 11:49:40 -0400 Subject: [PATCH 072/120] add capslock key support when darwin --- hook/darwin/hook_c.h | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/hook/darwin/hook_c.h b/hook/darwin/hook_c.h index b717bcf..646cf1c 100644 --- a/hook/darwin/hook_c.h +++ b/hook/darwin/hook_c.h @@ -582,15 +582,17 @@ static inline void process_modifier_changed(uint64_t timestamp, CGEventRef event process_key_released(timestamp, event_ref); } } - /* FIXME This should produce a modifier mask for the caps lock key! else if (keycode == kVK_CapsLock) { - // Process as a key pressed event. - process_key_pressed(timestamp, event_ref); - - // Set the caps-lock flag for release. - caps_down = true; + if (current_modifiers & MASK_CAPS_LOCK) { + // Process as a key pressed event. + unset_modifier_mask(MASK_CAPS_LOCK); + process_key_released(timestamp, event_ref); + } else { + // Process as a key released event. + set_modifier_mask(MASK_CAPS_LOCK); + process_key_pressed(timestamp, event_ref); + } } - */ } /* These events are totally undocumented for the CGEvent type, but are required to grab media and caps-lock keys. From 858d3206aeb6e924fc3f963f0ed126a73a280583 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 27 Feb 2020 11:04:21 -0400 Subject: [PATCH 073/120] update appveyo.yml and go.yml to go1.14 --- .github/workflows/go.yml | 4 ++-- appveyor.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 8b0aa55..e253a0a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,10 +10,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Set up Go 1.13 + - name: Set up Go 1.14 uses: actions/setup-go@v1 with: - go-version: 1.13 + go-version: 1.14 id: go - name: Check out code into the Go module directory diff --git a/appveyor.yml b/appveyor.yml index 3263006..f001c53 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.13.8 + GOVERSION: 1.14 # GOPATH: c:\gopath # scripts that run after cloning repository From fceda58dd0dc1532fec44ab0e1a66997b77b5064 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 20 Mar 2020 12:42:59 -0400 Subject: [PATCH 074/120] update appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index f001c53..fc6c280 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.14 + GOVERSION: 1.14.1 # GOPATH: c:\gopath # scripts that run after cloning repository From 146f7aeeb5e8f30994a8f1f3434ebc815e790e5a Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sat, 11 Apr 2020 13:21:14 -0400 Subject: [PATCH 075/120] update appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index fc6c280..0de62a5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.14.1 + GOVERSION: 1.14.2 # GOPATH: c:\gopath # scripts that run after cloning repository From 47d3b68877e0351505c70626109890fa19debb5c Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 19 May 2020 11:01:25 -0400 Subject: [PATCH 076/120] add robotgo keycode to gohook --- keycode.go | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 keycode.go diff --git a/keycode.go b/keycode.go new file mode 100644 index 0000000..1db0925 --- /dev/null +++ b/keycode.go @@ -0,0 +1,112 @@ +// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://github.com/go-vgo/robotgo/blob/master/LICENSE +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +package hook + +type uMap map[string]uint16 + +// MouseMap robotgo hook mouse's code map +var MouseMap = uMap{ + "left": 1, + "right": 2, + "center": 3, + "wheelDown": 4, + "wheelUp": 5, + "wheelLeft": 6, + "wheelRight": 7, +} + +// Keycode robotgo hook key's code map +var Keycode = uMap{ + "`": 41, + "1": 2, + "2": 3, + "3": 4, + "4": 5, + "5": 6, + "6": 7, + "7": 8, + "8": 9, + "9": 10, + "0": 11, + "-": 12, + "+": 13, + // + "q": 16, + "w": 17, + "e": 18, + "r": 19, + "t": 20, + "y": 21, + "u": 22, + "i": 23, + "o": 24, + "p": 25, + "[": 26, + "]": 27, + "\\": 43, + // + "a": 30, + "s": 31, + "d": 32, + "f": 33, + "g": 34, + "h": 35, + "j": 36, + "k": 37, + "l": 38, + ";": 39, + "'": 40, + // + "z": 44, + "x": 45, + "c": 46, + "v": 47, + "b": 48, + "n": 49, + "m": 50, + ",": 51, + ".": 52, + "/": 53, + // + "f1": 59, + "f2": 60, + "f3": 61, + "f4": 62, + "f5": 63, + "f6": 64, + "f7": 65, + "f8": 66, + "f9": 67, + "f10": 68, + "f11": 69, + "f12": 70, + // more + "esc": 1, + "delete": 14, + "tab": 15, + "ctrl": 29, + "control": 29, + "alt": 56, + "space": 57, + "shift": 42, + "rshift": 54, + "enter": 28, + // + "cmd": 3675, + "command": 3675, + "rcmd": 3676, + "ralt": 3640, + // + "up": 57416, + "down": 57424, + "left": 57419, + "right": 57421, +} From d6bffe47f1d6ee594af6fbf9458ca7a78500be93 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 19 May 2020 11:11:40 -0400 Subject: [PATCH 077/120] add modern and concurrent API, merged #10 --- hook.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/hook.go b/hook.go index 9b0b7d8..5b0fc5c 100644 --- a/hook.go +++ b/hook.go @@ -90,8 +90,71 @@ var ( asyncon = false lck sync.RWMutex + + pressed = make(map[uint16]bool, 256) + used = []int{} + + keys = map[int][]uint16{} + cbs = map[int]func(Event){} + events = map[uint8][]int{} ) +func allPressed(pressed map[uint16]bool, keys ...uint16) bool { + for _, i := range keys { + // fmt.Println(i) + if pressed[i] == false { + return false + } + } + + return true +} + +// Register register gohook event +func Register(when uint8, cmds []string, cb func(Event)) { + key := len(used) + used = append(used, key) + tmp := []uint16{} + + for _, v := range cmds { + tmp = append(tmp, Keycode[v]) + } + + keys[key] = tmp + cbs[key] = cb + events[when] = append(events[when], key) + return +} + +// Process return go hook process +func Process(EvChan <-chan Event) (out chan bool) { + out = make(chan bool) + go func() { + for ev := range EvChan { + if ev.Kind == KeyDown || ev.Kind == KeyHold { + pressed[ev.Keycode] = true + } else if ev.Kind == KeyUp { + pressed[ev.Keycode] = false + } + + for _, v := range events[ev.Kind] { + if !asyncon { + break + } + + if allPressed(pressed, keys[v]...) { + cbs[v](ev) + } + } + } + + // fmt.Println("exiting after end (process)") + out <- true + }() + + return out +} + // String return hook kind string func (e Event) String() string { switch e.Kind { @@ -157,18 +220,19 @@ func KeychartoRawcode(kc string) uint16 { // Start Adds global event hook to OS // returns event channel func Start() chan Event { - asyncon = true + ev = make(chan Event, 1024) go C.start_ev() + asyncon = true go func() { for { - C.pollEv() - time.Sleep(time.Millisecond * 50) - - // todo: find smallest time that does not destroy the cpu utilization if !asyncon { return } + + C.pollEv() + time.Sleep(time.Millisecond * 50) + //todo: find smallest time that does not destroy the cpu utilization } }() @@ -177,14 +241,21 @@ func Start() chan Event { // End removes global event hook func End() { + asyncon = false C.endPoll() C.stop_event() for len(ev) != 0 { <-ev } + close(ev) - asyncon = false + pressed = make(map[uint16]bool, 256) + used = []int{} + + keys = map[int][]uint16{} + cbs = map[int]func(Event){} + events = map[uint8][]int{} } // AddEvent add event listener From 185bc1badc96f4e2125fdc77b07b68d0275dfcf3 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 19 May 2020 11:28:03 -0400 Subject: [PATCH 078/120] update version and godoc --- hook.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hook.go b/hook.go index 5b0fc5c..1cbb2ae 100644 --- a/hook.go +++ b/hook.go @@ -32,7 +32,7 @@ import ( const ( // Version get the gohook version - Version = "v0.20.0.56, Sierra Nevada!" + Version = "v0.30.0.80, Sierra Nevada!" // HookEnabled honk enable status HookEnabled = 1 // iota @@ -217,7 +217,7 @@ func KeychartoRawcode(kc string) uint16 { return keytoraw[kc] } -// Start Adds global event hook to OS +// Start adds global event hook to OS // returns event channel func Start() chan Event { ev = make(chan Event, 1024) From 8c52dfa0096ece368eb04435641dfac895ddcaa7 Mon Sep 17 00:00:00 2001 From: zeropool <1285055670@qq.com> Date: Wed, 20 May 2020 10:37:43 +0800 Subject: [PATCH 079/120] sched_yield support for non-POSIX windows gcc --- chan/eb_chan.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/chan/eb_chan.h b/chan/eb_chan.h index e4855b4..72189e1 100644 --- a/chan/eb_chan.h +++ b/chan/eb_chan.h @@ -30,6 +30,13 @@ #include +#if (defined(__MINGW32__) || defined(__CYGWIN__)) && !defined(_POSIX_PRIORITY_SCHEDULING) +#include +int sched_yield() { + return SwitchToThread(); +} +#endif + typedef uint64_t eb_nsec; /* Units of nanoseconds */ #define eb_nsec_zero UINT64_C(0) #define eb_nsec_forever UINT64_MAX @@ -1436,4 +1443,4 @@ eb_chan_op *eb_chan_select_list(eb_nsec timeout, eb_chan_op *const ops[], size_t return result; } -#endif /* EB_CHAN_H */ \ No newline at end of file +#endif /* EB_CHAN_H */ From 419a0340b3a1c9d70d9ad12782add09f6e10e6fa Mon Sep 17 00:00:00 2001 From: vcaesar Date: Wed, 20 May 2020 10:55:32 -0400 Subject: [PATCH 080/120] update version and gomod pkg --- chan/eb_chan.h | 1 + go.mod | 2 +- go.sum | 4 ++-- hook.go | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/chan/eb_chan.h b/chan/eb_chan.h index 72189e1..8ac4fc9 100644 --- a/chan/eb_chan.h +++ b/chan/eb_chan.h @@ -32,6 +32,7 @@ #if (defined(__MINGW32__) || defined(__CYGWIN__)) && !defined(_POSIX_PRIORITY_SCHEDULING) #include + int sched_yield() { return SwitchToThread(); } diff --git a/go.mod b/go.mod index f7ec693..f3163d8 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/robotn/gohook go 1.13 -require github.com/vcaesar/tt v0.0.0-20200219144246-a190abe69d64 +require github.com/vcaesar/tt v0.10.0 diff --git a/go.sum b/go.sum index 74d89ab..91c4e9b 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -github.com/vcaesar/tt v0.0.0-20200219144246-a190abe69d64 h1:MwxT8hyvPcPafJ2LDmoFuH7Xpz5LZ6W3dKzwVQAHD/k= -github.com/vcaesar/tt v0.0.0-20200219144246-a190abe69d64/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg= +github.com/vcaesar/tt v0.10.0 h1:jPtFiRRnCOXVakd5ujCed2dwWwTs91wh8W1iHeUIfc0= +github.com/vcaesar/tt v0.10.0/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg= diff --git a/hook.go b/hook.go index 1cbb2ae..174ce5d 100644 --- a/hook.go +++ b/hook.go @@ -32,7 +32,7 @@ import ( const ( // Version get the gohook version - Version = "v0.30.0.80, Sierra Nevada!" + Version = "v0.30.1.83, Sierra Nevada!" // HookEnabled honk enable status HookEnabled = 1 // iota From 6ca4fbe91adb942ac3ccfabcd7309ea0e6ca8855 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 21 May 2020 12:18:10 -0400 Subject: [PATCH 081/120] add more examples code and add log print --- example/main.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/example/main.go b/example/main.go index eddc31c..89ff892 100644 --- a/example/main.go +++ b/example/main.go @@ -6,8 +6,25 @@ import ( hook "github.com/robotn/gohook" ) +func addEvent() { + fmt.Println("--- Please press ctrl + shift + q to stop hook ---") + hook.Register(hook.KeyDown, []string{"q", "ctrl", "shift"}, func(e hook.Event) { + fmt.Println("ctrl-shift-q") + hook.End() + }) + + fmt.Println("--- Please press w---") + hook.Register(hook.KeyDown, []string{"w"}, func(e hook.Event) { + fmt.Println("w") + }) + + s := hook.Start() + <-hook.Process(s) +} + // hook listen and return values using detailed examples func add() { + fmt.Println("hook add...") s := hook.Start() defer hook.End() @@ -27,6 +44,7 @@ func add() { // base hook example func base() { + fmt.Println("hook start...") evChan := hook.Start() defer hook.End() @@ -36,6 +54,8 @@ func base() { } func main() { + addEvent() + base() add() From 0fa5a96dfe00a4c4288be9524a1ef07fbcfbc603 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 28 May 2020 10:31:52 -0400 Subject: [PATCH 082/120] Update README.md --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index fc70f92..42fc821 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,20 @@ import ( ) func main() { + fmt.Println("--- Please press ctrl + shift + q to stop hook ---") + hook.Register(hook.KeyDown, []string{"q", "ctrl", "shift"}, func(e hook.Event) { + fmt.Println("ctrl-shift-q") + hook.End() + }) + + fmt.Println("--- Please press w---") + hook.Register(hook.KeyDown, []string{"w"}, func(e hook.Event) { + fmt.Println("w") + }) + + s := hook.Start() + <-hook.Process(s) + EvChan := hook.Start() defer hook.End() From a945300efab8f2063b2dd1d745f8dd9808805e4e Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 29 May 2020 10:24:17 -0400 Subject: [PATCH 083/120] Update README.md --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 42fc821..d6c37ad 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,12 @@ import ( ) func main() { + add() + + low() +} + +func add() { fmt.Println("--- Please press ctrl + shift + q to stop hook ---") hook.Register(hook.KeyDown, []string{"q", "ctrl", "shift"}, func(e hook.Event) { fmt.Println("ctrl-shift-q") @@ -30,14 +36,17 @@ func main() { s := hook.Start() <-hook.Process(s) +} +func low() { EvChan := hook.Start() defer hook.End() - + for ev := range EvChan { fmt.Println("hook: ", ev) } } + ``` Based on [libuiohook](https://github.com/kwhat/libuiohook). \ No newline at end of file From e29fdb45c2d3d10035cc3c8e11046b7beedb44b6 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 31 May 2020 10:11:43 -0400 Subject: [PATCH 084/120] Update End() function, Fixed close chan too fast --- hook.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hook.go b/hook.go index 174ce5d..9a8d107 100644 --- a/hook.go +++ b/hook.go @@ -32,7 +32,7 @@ import ( const ( // Version get the gohook version - Version = "v0.30.1.83, Sierra Nevada!" + Version = "v0.30.2.88, Sierra Nevada!" // HookEnabled honk enable status HookEnabled = 1 // iota @@ -244,6 +244,7 @@ func End() { asyncon = false C.endPoll() C.stop_event() + time.Sleep(time.Millisecond * 10) for len(ev) != 0 { <-ev From d17136bdf9d01983466e0e2b7b01d8fbc4b6e666 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Wed, 3 Jun 2020 11:14:56 -0400 Subject: [PATCH 085/120] update appveyor.yml to go1.14.4 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 0de62a5..0515a15 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.14.2 + GOVERSION: 1.14.4 # GOPATH: c:\gopath # scripts that run after cloning repository From 80d080b1409bf6e805fc206a2b50a84fb519269b Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 28 Jun 2020 23:53:06 -0400 Subject: [PATCH 086/120] update code style --- hook/darwin/event_c.h | 27 +++------ hook/darwin/hook_c.h | 131 ++++++++++++++---------------------------- 2 files changed, 53 insertions(+), 105 deletions(-) diff --git a/hook/darwin/event_c.h b/hook/darwin/event_c.h index 0497aec..e6ab219 100644 --- a/hook/darwin/event_c.h +++ b/hook/darwin/event_c.h @@ -77,26 +77,21 @@ static inline void post_mouse_button_event(iohook_event * const event, bool is_p if (event->data.mouse.button == MOUSE_BUTTON1) { if (is_pressed) { mouse_type = kCGEventLeftMouseDown; - } - else { + } else { mouse_type = kCGEventLeftMouseUp; } mouse_button = kCGMouseButtonLeft; - } - else if (event->data.mouse.button == MOUSE_BUTTON2) { + } else if (event->data.mouse.button == MOUSE_BUTTON2) { if (is_pressed) { mouse_type = kCGEventRightMouseDown; - } - else { + } else { mouse_type = kCGEventRightMouseUp; } mouse_button = kCGMouseButtonRight; - } - else { + } else { if (is_pressed) { mouse_type = kCGEventOtherMouseDown; - } - else { + } else { mouse_type = kCGEventOtherMouseUp; } mouse_button = event->data.mouse.button - 1; @@ -125,8 +120,7 @@ static inline void post_mouse_wheel_event(iohook_event * const event) { if (event->data.wheel.type == WHEEL_BLOCK_SCROLL) { // Scrolling data is line-based. scroll_unit = kCGScrollEventUnitLine; - } - else { + } else { // Scrolling data is pixel-based. scroll_unit = kCGScrollEventUnitPixel; } @@ -156,8 +150,7 @@ static inline void post_mouse_motion_event(iohook_event * const event) { ), 0 ); - } - else if (event->mask & MASK_BUTTON1) { + } else if (event->mask & MASK_BUTTON1) { cg_event = CGEventCreateMouseEvent(src, kCGEventLeftMouseDragged, CGPointMake( @@ -166,8 +159,7 @@ static inline void post_mouse_motion_event(iohook_event * const event) { ), kCGMouseButtonLeft ); - } - else if (event->mask & MASK_BUTTON2) { + } else if (event->mask & MASK_BUTTON2) { cg_event = CGEventCreateMouseEvent(src, kCGEventRightMouseDragged, CGPointMake( @@ -176,8 +168,7 @@ static inline void post_mouse_motion_event(iohook_event * const event) { ), kCGMouseButtonRight ); - } - else { + } else { cg_event = CGEventCreateMouseEvent(src, kCGEventOtherMouseDragged, CGPointMake( diff --git a/hook/darwin/hook_c.h b/hook/darwin/hook_c.h index 646cf1c..3723b58 100644 --- a/hook/darwin/hook_c.h +++ b/hook/darwin/hook_c.h @@ -491,98 +491,82 @@ static inline void process_modifier_changed(uint64_t timestamp, CGEventRef event // Process as a key pressed event. set_modifier_mask(MASK_SHIFT_L); process_key_pressed(timestamp, event_ref); - } - else { + } else { // Process as a key released event. unset_modifier_mask(MASK_SHIFT_L); process_key_released(timestamp, event_ref); } - } - else if (keycode == kVK_Control) { + } else if (keycode == kVK_Control) { if (event_mask & kCGEventFlagMaskControl) { // Process as a key pressed event. set_modifier_mask(MASK_CTRL_L); process_key_pressed(timestamp, event_ref); - } - else { + } else { // Process as a key released event. unset_modifier_mask(MASK_CTRL_L); process_key_released(timestamp, event_ref); } - } - else if (keycode == kVK_Command) { + } else if (keycode == kVK_Command) { if (event_mask & kCGEventFlagMaskCommand) { // Process as a key pressed event. set_modifier_mask(MASK_META_L); process_key_pressed(timestamp, event_ref); - } - else { + } else { // Process as a key released event. unset_modifier_mask(MASK_META_L); process_key_released(timestamp, event_ref); } - } - else if (keycode == kVK_Option) { + } else if (keycode == kVK_Option) { if (event_mask & kCGEventFlagMaskAlternate) { // Process as a key pressed event. set_modifier_mask(MASK_ALT_L); process_key_pressed(timestamp, event_ref); - } - else { + } else { // Process as a key released event. unset_modifier_mask(MASK_ALT_L); process_key_released(timestamp, event_ref); } - } - else if (keycode == kVK_RightShift) { + } else if (keycode == kVK_RightShift) { if (event_mask & kCGEventFlagMaskShift) { // Process as a key pressed event. set_modifier_mask(MASK_SHIFT_R); process_key_pressed(timestamp, event_ref); - } - else { + } else { // Process as a key released event. unset_modifier_mask(MASK_SHIFT_R); process_key_released(timestamp, event_ref); } - } - else if (keycode == kVK_RightControl) { + } else if (keycode == kVK_RightControl) { if (event_mask & kCGEventFlagMaskControl) { // Process as a key pressed event. set_modifier_mask(MASK_CTRL_R); process_key_pressed(timestamp, event_ref); - } - else { + } else { // Process as a key released event. unset_modifier_mask(MASK_CTRL_R); process_key_released(timestamp, event_ref); } - } - else if (keycode == kVK_RightCommand) { + } else if (keycode == kVK_RightCommand) { if (event_mask & kCGEventFlagMaskCommand) { // Process as a key pressed event. set_modifier_mask(MASK_META_R); process_key_pressed(timestamp, event_ref); - } - else { + } else { // Process as a key released event. unset_modifier_mask(MASK_META_R); process_key_released(timestamp, event_ref); } - } - else if (keycode == kVK_RightOption) { + } else if (keycode == kVK_RightOption) { if (event_mask & kCGEventFlagMaskAlternate) { // Process as a key pressed event. set_modifier_mask(MASK_ALT_R); process_key_pressed(timestamp, event_ref); - } - else { + } else { // Process as a key released event. unset_modifier_mask(MASK_ALT_R); process_key_released(timestamp, event_ref); } - } - else if (keycode == kVK_CapsLock) { + } else if (keycode == kVK_CapsLock) { if (current_modifiers & MASK_CAPS_LOCK) { // Process as a key pressed event. unset_modifier_mask(MASK_CAPS_LOCK); @@ -631,15 +615,13 @@ static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) if (key_down) { process_key_pressed(timestamp, ns_event); - } - else { + } else { process_key_released(timestamp, ns_event); } CFRelease(ns_event); CFRelease(src); - } - else if (key_code == NX_KEYTYPE_SOUND_UP) { + } else if (key_code == NX_KEYTYPE_SOUND_UP) { // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_VolumeUp, key_down); @@ -647,15 +629,13 @@ static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) if (key_down) { process_key_pressed(timestamp, ns_event); - } - else { + } else { process_key_released(timestamp, ns_event); } CFRelease(ns_event); CFRelease(src); - } - else if (key_code == NX_KEYTYPE_SOUND_DOWN) { + } else if (key_code == NX_KEYTYPE_SOUND_DOWN) { // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_VolumeDown, key_down); @@ -663,15 +643,13 @@ static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) if (key_down) { process_key_pressed(timestamp, ns_event); - } - else { + } else { process_key_released(timestamp, ns_event); } CFRelease(ns_event); CFRelease(src); - } - else if (key_code == NX_KEYTYPE_MUTE) { + } else if (key_code == NX_KEYTYPE_MUTE) { // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_Mute, key_down); @@ -679,16 +657,13 @@ static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) if (key_down) { process_key_pressed(timestamp, ns_event); - } - else { + } else { process_key_released(timestamp, ns_event); } CFRelease(ns_event); CFRelease(src); - } - - else if (key_code == NX_KEYTYPE_EJECT) { + } else if (key_code == NX_KEYTYPE_EJECT) { // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_NX_Eject, key_down); @@ -696,15 +671,13 @@ static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) if (key_down) { process_key_pressed(timestamp, ns_event); - } - else { + } else { process_key_released(timestamp, ns_event); } CFRelease(ns_event); CFRelease(src); - } - else if (key_code == NX_KEYTYPE_PLAY) { + } else if (key_code == NX_KEYTYPE_PLAY) { // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_MEDIA_Play, key_down); @@ -712,15 +685,13 @@ static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) if (key_down) { process_key_pressed(timestamp, ns_event); - } - else { + } else { process_key_released(timestamp, ns_event); } CFRelease(ns_event); CFRelease(src); - } - else if (key_code == NX_KEYTYPE_FAST) { + } else if (key_code == NX_KEYTYPE_FAST) { // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_MEDIA_Next, key_down); @@ -728,15 +699,13 @@ static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) if (key_down) { process_key_pressed(timestamp, ns_event); - } - else { + } else { process_key_released(timestamp, ns_event); } CFRelease(ns_event); CFRelease(src); - } - else if (key_code == NX_KEYTYPE_REWIND) { + } else if (key_code == NX_KEYTYPE_REWIND) { // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_MEDIA_Previous, key_down); @@ -744,8 +713,7 @@ static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) if (key_down) { process_key_pressed(timestamp, ns_event); - } - else { + } else { process_key_released(timestamp, ns_event); } @@ -767,13 +735,11 @@ static inline void process_button_pressed(uint64_t timestamp, CGEventRef event_r if (button == click_button && (long int) (timestamp - click_time) <= hook_get_multi_click_time()) { if (click_count < USHRT_MAX) { click_count++; - } - else { + } else { logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n", __FUNCTION__, __LINE__); } - } - else { + } else { // Reset the click count. click_count = 1; @@ -871,8 +837,7 @@ static inline void process_mouse_moved(uint64_t timestamp, CGEventRef event_ref) if (mouse_dragged) { event.type = EVENT_MOUSE_DRAGGED; - } - else { + } else { event.type = EVENT_MOUSE_MOVED; } event.mask = get_modifiers(); @@ -917,8 +882,7 @@ static inline void process_mouse_wheel(uint64_t timestamp, CGEventRef event_ref) if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventIsContinuous) == 0) { // Scrolling data is line-based. event.data.wheel.type = WHEEL_BLOCK_SCROLL; - } - else { + } else { // Scrolling data is pixel-based. event.data.wheel.type = WHEEL_UNIT_SCROLL; } @@ -934,14 +898,12 @@ static inline void process_mouse_wheel(uint64_t timestamp, CGEventRef event_ref) // Scrolling data uses a fixed-point 16.16 signed integer format (Ex: 1.0 = 0x00010000). event.data.wheel.rotation = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) * -1; - } - else if(CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) != 0) { + } else if(CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) != 0) { event.data.wheel.amount = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventPointDeltaAxis2) / CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2); // Scrolling data uses a fixed-point 16.16 signed integer format (Ex: 1.0 = 0x00010000). event.data.wheel.rotation = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) * -1; - } - else { + } else { //Fail Silently if a 3rd axis gets added without changing this section of code. event.data.wheel.amount = 0; event.data.wheel.rotation = 0; @@ -952,8 +914,8 @@ static inline void process_mouse_wheel(uint64_t timestamp, CGEventRef event_ref) if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0) { // Wheel Rotated Up or Down. event.data.wheel.direction = WHEEL_VERTICAL_DIRECTION; - } - else { // data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight + } else { + // data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight // Wheel Rotated Left or Right. event.data.wheel.direction = WHEEL_HORIZONTAL_DIRECTION; } @@ -1013,8 +975,7 @@ CGEventRef hook_event_proc(CGEventTapProxy tap_proxy, CGEventType type, CGEventR // Add support for mouse 4 & 5. if (button == 4) { set_modifier_mask(MOUSE_BUTTON4); - } - else if (button == 5) { + } else if (button == 5) { set_modifier_mask(MOUSE_BUTTON5); } @@ -1040,8 +1001,7 @@ CGEventRef hook_event_proc(CGEventTapProxy tap_proxy, CGEventType type, CGEventR // Add support for mouse 4 & 5. if (button == 4) { unset_modifier_mask(MOUSE_BUTTON4); - } - else if (button == 5) { + } else if (button == 5) { unset_modifier_mask(MOUSE_BUTTON5); } @@ -1088,8 +1048,7 @@ CGEventRef hook_event_proc(CGEventTapProxy tap_proxy, CGEventType type, CGEventR // We need to restart the tap! restart_tap = true; CFRunLoopStop(CFRunLoopGetCurrent()); - } - else { + } else { // In theory this *should* never execute. logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled Darwin event: %#X.\n", __FUNCTION__, __LINE__, (unsigned int) type); @@ -1100,8 +1059,7 @@ CGEventRef hook_event_proc(CGEventTapProxy tap_proxy, CGEventType type, CGEventR CGEventRef result_ref = NULL; if (event.reserved ^ 0x01) { result_ref = event_ref; - } - else { + } else { logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%#X) (%#p)\n", __FUNCTION__, __LINE__, type, event_ref); } @@ -1328,8 +1286,7 @@ IOHOOK_API int hook_run() { // Free the hook structure. free(hook); - } - else { + } else { status = IOHOOK_ERROR_OUT_OF_MEMORY; } } while (restart_tap); From ef93345f665c10eaec148a4ac0a9e8b959e14bda Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 29 Jun 2020 00:09:10 -0400 Subject: [PATCH 087/120] update code style --- hook/darwin/input_c.h | 9 +++---- hook/darwin/properties_c.h | 3 +-- hook/windows/event_c.h | 6 ++--- hook/windows/hook_c.h | 48 +++++++++++++------------------------ hook/windows/input_c.h | 48 +++++++++++++------------------------ hook/windows/properties_c.h | 3 +-- 6 files changed, 39 insertions(+), 78 deletions(-) diff --git a/hook/darwin/input_c.h b/hook/darwin/input_c.h index d884381..f045fe2 100644 --- a/hook/darwin/input_c.h +++ b/hook/darwin/input_c.h @@ -203,14 +203,12 @@ UniCharCount keycode_to_unicode(CGEventRef event_ref, UniChar *buffer, UniCharCo CFStringUppercase(keytxt, locale); CFRelease(locale); CFRelease(keytxt); - } - else { + } else { // There was an problem creating the CFMutableStringRef. count = 0; } } - } - else { + } else { // Make sure the buffer count is zero if an error occurred. count = 0; } @@ -518,8 +516,7 @@ UInt64 scancode_to_keycode(uint16_t scancode) { // Bound check 0 <= keycode < 128 if (scancode < 128) { keycode = keycode_scancode_table[scancode][1]; - } - else { + } else { // Calculate the upper offset. unsigned short i = (scancode & 0x007F) | 0x80; diff --git a/hook/darwin/properties_c.h b/hook/darwin/properties_c.h index 89559e6..9dc8ebb 100644 --- a/hook/darwin/properties_c.h +++ b/hook/darwin/properties_c.h @@ -72,8 +72,7 @@ IOHOOK_API screen_data* hook_create_screen_info(unsigned char *count) { } } } - } - else { + } else { logger(LOG_LEVEL_INFO, "%s [%u]: multiple_get_screen_info failed: %ld. Fallback.\n", __FUNCTION__, __LINE__, status); diff --git a/hook/windows/event_c.h b/hook/windows/event_c.h index c1128fc..622749b 100644 --- a/hook/windows/event_c.h +++ b/hook/windows/event_c.h @@ -118,8 +118,7 @@ IOHOOK_API void hook_post_event(iohook_event * const event) { events[events_size].ki.wScan = 0; // event->data.keyboard.keycode; events[events_size].ki.time = 0; // GetSystemTime() events_size++; - } - else { + } else { logger(LOG_LEVEL_INFO, "%s [%u]: Unable to lookup scancode: %li\n", __FUNCTION__, __LINE__, event->data.keyboard.keycode); @@ -135,8 +134,7 @@ IOHOOK_API void hook_post_event(iohook_event * const event) { events[events_size].ki.wScan = 0; // event->data.keyboard.keycode; events[events_size].ki.time = 0; // GetSystemTime() events_size++; - } - else { + } else { logger(LOG_LEVEL_INFO, "%s [%u]: Unable to lookup scancode: %li\n", __FUNCTION__, __LINE__, event->data.keyboard.keycode); diff --git a/hook/windows/hook_c.h b/hook/windows/hook_c.h index 7a10218..26cd0b2 100644 --- a/hook/windows/hook_c.h +++ b/hook/windows/hook_c.h @@ -54,8 +54,7 @@ static inline void dispatch_event(iohook_event *const event) { __FUNCTION__, __LINE__, event->type); dispatcher(event); - } - else { + } else { logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n", __FUNCTION__, __LINE__); } @@ -113,8 +112,7 @@ static unsigned short int get_scroll_wheel_type() { SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheel_type, 0); if (wheel_type == WHEEL_PAGESCROLL) { value = WHEEL_BLOCK_SCROLL; - } - else { + } else { value = WHEEL_UNIT_SCROLL; } @@ -131,8 +129,7 @@ static unsigned short int get_scroll_wheel_amount() { SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheel_amount, 0); if (wheel_amount == WHEEL_PAGESCROLL) { value = 1; - } - else { + } else { value = (unsigned short int) wheel_amount; } @@ -303,8 +300,7 @@ LRESULT CALLBACK keyboard_hook_event_proc(int nCode, WPARAM wParam, LPARAM lPara LRESULT hook_result = -1; if (nCode < 0 || event.reserved ^ 0x01) { hook_result = CallNextHookEx(keyboard_event_hhook, nCode, wParam, lParam); - } - else { + } else { logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%li)\n", __FUNCTION__, __LINE__, (long) hook_result); } @@ -321,13 +317,11 @@ static void process_button_pressed(MSLLHOOKSTRUCT *mshook, uint16_t button) { if (button == click_button && (long int) (timestamp - click_time) <= hook_get_multi_click_time()) { if (click_count < USHRT_MAX) { click_count++; - } - else { + } else { logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n", __FUNCTION__, __LINE__); } - } - else { + } else { // Reset the click count. click_count = 1; @@ -436,8 +430,7 @@ static void process_mouse_moved(MSLLHOOKSTRUCT *mshook) { if (mouse_dragged) { // Create Mouse Dragged event. event.type = EVENT_MOUSE_DRAGGED; - } - else { + } else { // Create a Mouse Moved event. event.type = EVENT_MOUSE_MOVED; } @@ -519,20 +512,17 @@ LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) if (HIWORD(mshook->mouseData) == XBUTTON1) { set_modifier_mask(MASK_BUTTON4); process_button_pressed(mshook, MOUSE_BUTTON4); - } - else if (HIWORD(mshook->mouseData) == XBUTTON2) { + } else if (HIWORD(mshook->mouseData) == XBUTTON2) { set_modifier_mask(MASK_BUTTON5); process_button_pressed(mshook, MOUSE_BUTTON5); - } - else { + } else { // Extra mouse buttons. uint16_t button = HIWORD(mshook->mouseData); // Add support for mouse 4 & 5. if (button == 4) { set_modifier_mask(MOUSE_BUTTON4); - } - else if (button == 5) { + } else if (button == 5) { set_modifier_mask(MOUSE_BUTTON5); } @@ -561,20 +551,17 @@ LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) if (HIWORD(mshook->mouseData) == XBUTTON1) { unset_modifier_mask(MASK_BUTTON4); process_button_released(mshook, MOUSE_BUTTON4); - } - else if (HIWORD(mshook->mouseData) == XBUTTON2) { + } else if (HIWORD(mshook->mouseData) == XBUTTON2) { unset_modifier_mask(MASK_BUTTON5); process_button_released(mshook, MOUSE_BUTTON5); - } - else { + } else { // Extra mouse buttons. uint16_t button = HIWORD(mshook->mouseData); // Add support for mouse 4 & 5. if (button == 4) { unset_modifier_mask(MOUSE_BUTTON4); - } - else if (button == 5) { + } else if (button == 5) { unset_modifier_mask(MOUSE_BUTTON5); } @@ -608,8 +595,7 @@ LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) LRESULT hook_result = -1; if (nCode < 0 || event.reserved ^ 0x01) { hook_result = CallNextHookEx(mouse_event_hhook, nCode, wParam, lParam); - } - else { + } else { logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%li)\n", __FUNCTION__, __LINE__, (long) hook_result); } @@ -674,8 +660,7 @@ IOHOOK_API int hook_run() { if (hInst != NULL) { // Initialize native input helper functions. load_input_helper(); - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: Could not determine hInst for SetWindowsHookEx()! (%#lX)\n", __FUNCTION__, __LINE__, (unsigned long) GetLastError()); @@ -721,8 +706,7 @@ IOHOOK_API int hook_run() { TranslateMessage(&message); DispatchMessage(&message); } - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: SetWindowsHookEx() failed! (%#lX)\n", __FUNCTION__, __LINE__, (unsigned long) GetLastError()); diff --git a/hook/windows/input_c.h b/hook/windows/input_c.h index 995101a..561708a 100644 --- a/hook/windows/input_c.h +++ b/hook/windows/input_c.h @@ -315,8 +315,7 @@ unsigned short keycode_to_scancode(DWORD vk_code, DWORD flags) { scancode |= 0x0E00; break; } - } - else { + } else { // logger(LOG_LEVEL_WARN, "%s [%u]: Test2, vk_code %li\n", // __FUNCTION__, __LINE__, vk_code); } @@ -332,8 +331,7 @@ DWORD scancode_to_keycode(unsigned short scancode) { // NOTE vk_code >= 0 is assumed because the scancode is unsigned. if (scancode < 128) { keycode = keycode_scancode_table[scancode][1]; - } - else { + } else { // Calculate the upper offset based on the lower half of the scancode + 128. unsigned short int i = (scancode & 0x007F) | 0x80; @@ -431,8 +429,7 @@ static int refresh_locale_list() { logger(LOG_LEVEL_WARN, "%s [%u]: Locale size mismatch! " "Expected %i, received %i!\n", __FUNCTION__, __LINE__, hkl_size, new_size); - } - else { + } else { logger(LOG_LEVEL_INFO, "%s [%u]: Received %i locales.\n", __FUNCTION__, __LINE__, new_size); } @@ -468,8 +465,7 @@ static int refresh_locale_list() { } count++; - } - else { + } else { logger(LOG_LEVEL_DEBUG, "%s [%u]: Removing locale ID %#p from the cache.\n", __FUNCTION__, __LINE__, locale_item->id); @@ -542,8 +538,7 @@ static int refresh_locale_list() { if (locale_previous == NULL) { // If nothing came before, the list is empty. locale_first = locale_item; - } - else { + } else { // Append the new locale to the end of the list. locale_previous->next = locale_item; } @@ -557,8 +552,7 @@ static int refresh_locale_list() { locale_previous = locale_item; count++; - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: GetProcAddress() failed for KbdLayerDescriptor!\n", __FUNCTION__, __LINE__); @@ -567,22 +561,19 @@ static int refresh_locale_list() { free(locale_item); locale_item = NULL; } - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: GetSystemDirectory() failed!\n", __FUNCTION__, __LINE__); } - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: Could not find keyboard map for locale %#p!\n", __FUNCTION__, __LINE__, hkl_list[i]); } } } - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: GetKeyboardLayoutList() failed!\n", __FUNCTION__, __LINE__); @@ -627,8 +618,7 @@ SIZE_T keycode_to_unicode(DWORD keycode, PWCHAR buffer, SIZE_T size) { // If they layout changes the dead key state needs to be reset. // This is consistent with the way Windows handles locale changes. deadChar = WCH_NONE; - } - else { + } else { logger(LOG_LEVEL_DEBUG, "%s [%u]: Refreshing locale cache.\n", __FUNCTION__, __LINE__); @@ -669,11 +659,9 @@ SIZE_T keycode_to_unicode(DWORD keycode, PWCHAR buffer, SIZE_T size) { if (state & ~SHRT_MAX) { if (pVkToBit[i].Vk == VK_SHIFT) { is_shift = true; - } - else if (pVkToBit[i].Vk == VK_CONTROL) { + } else if (pVkToBit[i].Vk == VK_CONTROL) { is_ctrl = true; - } - else if (pVkToBit[i].Vk == VK_MENU) { + } else if (pVkToBit[i].Vk == VK_MENU) { is_alt = true; } } @@ -720,8 +708,7 @@ SIZE_T keycode_to_unicode(DWORD keycode, PWCHAR buffer, SIZE_T size) { if ((((PVK_TO_WCHARS) pCurrentVkToWchars)->Attributes == CAPLOK) && capsLock) { if (is_shift && mod > 0) { mod -= 1; - } - else { + } else { mod += 1; } } @@ -739,8 +726,7 @@ SIZE_T keycode_to_unicode(DWORD keycode, PWCHAR buffer, SIZE_T size) { // No previous dead key was set so cache the next // wchar so we know what to do next time its pressed. deadChar = ((PVK_TO_WCHARS) pCurrentVkToWchars)->wch[mod]; - } - else { + } else { if (size >= 2) { // Received a second dead key. memset(buffer, deadChar, 2); @@ -751,8 +737,7 @@ SIZE_T keycode_to_unicode(DWORD keycode, PWCHAR buffer, SIZE_T size) { charCount = 2; } } - } - else if (unicode != WCH_NONE) { + } else if (unicode != WCH_NONE) { // We are not WCH_NONE or WCH_DEAD if (size >= 1) { buffer[0] = unicode; @@ -761,8 +746,7 @@ SIZE_T keycode_to_unicode(DWORD keycode, PWCHAR buffer, SIZE_T size) { } break; - } - else { + } else { // Add sizeof WCHAR because we are really an array of WCHAR[n] not WCHAR[] pCurrentVkToWchars += sizeof(VK_TO_WCHARS) + (sizeof(WCHAR) * n); } diff --git a/hook/windows/properties_c.h b/hook/windows/properties_c.h index c9f2c74..c66930c 100644 --- a/hook/windows/properties_c.h +++ b/hook/windows/properties_c.h @@ -40,8 +40,7 @@ static BOOL CALLBACK monitor_enum_proc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT if (screens->data == NULL) { screens->data = (screen_data *) malloc(sizeof(screen_data)); - } - else { + } else { screens->data = (screen_data *) realloc(screens, sizeof(screen_data) * screens->count); } From ac6a9e969d51ebc6ee8f20c592f6763f0d3cefb4 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 29 Jun 2020 11:37:12 -0400 Subject: [PATCH 088/120] update code style --- hook/x11/event_c.h | 24 ++++------ hook/x11/hook_c.h | 99 ++++++++++++++--------------------------- hook/x11/input_c.h | 60 +++++++++---------------- hook/x11/properties_c.h | 42 ++++++----------- 4 files changed, 75 insertions(+), 150 deletions(-) diff --git a/hook/x11/event_c.h b/hook/x11/event_c.h index 85d5593..9d628c9 100644 --- a/hook/x11/event_c.h +++ b/hook/x11/event_c.h @@ -75,8 +75,7 @@ static inline void post_key_event(iohook_event * const event) { scancode_to_keycode(event->data.keyboard.keycode), True, 0); - } - else if (event->type == EVENT_KEY_RELEASED) { + } else if (event->type == EVENT_KEY_RELEASED) { XTestFakeKeyEvent( properties_disp, scancode_to_keycode(event->data.keyboard.keycode), @@ -111,8 +110,7 @@ static inline void post_key_event(iohook_event * const event) { if (event->type == EVENT_KEY_PRESSED) { key_event.type = KeyPress; XSendEvent(properties_disp, InputFocus, False, KeyPressMask, (XEvent *) &key_event); - } - else if (event->type == EVENT_KEY_RELEASED) { + } else if (event->type == EVENT_KEY_RELEASED) { key_event.type = KeyRelease; XSendEvent(properties_disp, InputFocus, False, KeyReleaseMask, (XEvent *) &key_event); } @@ -135,8 +133,7 @@ static inline void post_mouse_button_event(iohook_event * const event) { if (event->data.mouse.x != root_x || event->data.mouse.y != root_y) { // Move the pointer to the specified position. XTestFakeMotionEvent(properties_disp, -1, event->data.mouse.x, event->data.mouse.y, 0); - } - else { + } else { query_status = False; } } @@ -147,19 +144,15 @@ static inline void post_mouse_button_event(iohook_event * const event) { if (event->data.wheel.rotation < 0) { XTestFakeButtonEvent(properties_disp, WheelUp, True, 0); XTestFakeButtonEvent(properties_disp, WheelUp, False, 0); - } - else { + } else { XTestFakeButtonEvent(properties_disp, WheelDown, True, 0); XTestFakeButtonEvent(properties_disp, WheelDown, False, 0); } - } - else if (event->type == EVENT_MOUSE_PRESSED) { + } else if (event->type == EVENT_MOUSE_PRESSED) { XTestFakeButtonEvent(properties_disp, event->data.mouse.button, True, 0); - } - else if (event->type == EVENT_MOUSE_RELEASED) { + } else if (event->type == EVENT_MOUSE_RELEASED) { XTestFakeButtonEvent(properties_disp, event->data.mouse.button, False, 0); - } - else if (event->type == EVENT_MOUSE_CLICKED) { + } else if (event->type == EVENT_MOUSE_CLICKED) { XTestFakeButtonEvent(properties_disp, event->data.mouse.button, True, 0); XTestFakeButtonEvent(properties_disp, event->data.mouse.button, False, 0); } @@ -215,8 +208,7 @@ static inline void post_mouse_button_event(iohook_event * const event) { // type, amount and rotation if (event->data.wheel.rotation < 0) { btn_event.button = WheelUp; - } - else { + } else { btn_event.button = WheelDown; } } diff --git a/hook/x11/hook_c.h b/hook/x11/hook_c.h index cc9c5e6..11d1451 100644 --- a/hook/x11/hook_c.h +++ b/hook/x11/hook_c.h @@ -112,8 +112,7 @@ static inline void dispatch_event(iohook_event *const event) { __FUNCTION__, __LINE__, event->type); dispatcher(event); - } - else { + } else { logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n", __FUNCTION__, __LINE__); } @@ -140,22 +139,19 @@ static void initialize_locks() { if (xkb_state_led_name_is_active(state, XKB_LED_NAME_CAPS)) { set_modifier_mask(MASK_CAPS_LOCK); - } - else { + } else { unset_modifier_mask(MASK_CAPS_LOCK); } if (xkb_state_led_name_is_active(state, XKB_LED_NAME_NUM)) { set_modifier_mask(MASK_NUM_LOCK); - } - else { + } else { unset_modifier_mask(MASK_NUM_LOCK); } if (xkb_state_led_name_is_active(state, XKB_LED_NAME_SCROLL)) { set_modifier_mask(MASK_SCROLL_LOCK); - } - else { + } else { unset_modifier_mask(MASK_SCROLL_LOCK); } #else @@ -163,26 +159,22 @@ static void initialize_locks() { if (XkbGetIndicatorState(hook->ctrl.display, XkbUseCoreKbd, &led_mask) == Success) { if (led_mask & 0x01) { set_modifier_mask(MASK_CAPS_LOCK); - } - else { + } else { unset_modifier_mask(MASK_CAPS_LOCK); } if (led_mask & 0x02) { set_modifier_mask(MASK_NUM_LOCK); - } - else { + } else { unset_modifier_mask(MASK_NUM_LOCK); } if (led_mask & 0x04) { set_modifier_mask(MASK_SCROLL_LOCK); - } - else { + } else { unset_modifier_mask(MASK_SCROLL_LOCK); } - } - else { + } else { logger(LOG_LEVEL_WARN, "%s [%u]: XkbGetIndicatorState failed to get current led mask!\n", __FUNCTION__, __LINE__); } @@ -231,8 +223,7 @@ static void initialize_modifiers() { if (mask & Button3Mask) { set_modifier_mask(MASK_BUTTON3); } if (mask & Button4Mask) { set_modifier_mask(MASK_BUTTON4); } if (mask & Button5Mask) { set_modifier_mask(MASK_BUTTON5); } - } - else { + } else { logger(LOG_LEVEL_WARN, "%s [%u]: XQueryPointer failed to get current modifiers!\n", __FUNCTION__, __LINE__); @@ -270,8 +261,7 @@ void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { // Fire the hook start event. dispatch_event(&event); - } - else if (recorded_data->category == XRecordEndOfData) { + } else if (recorded_data->category == XRecordEndOfData) { // Populate the hook stop event. event.time = timestamp; event.reserved = 0x00; @@ -281,8 +271,7 @@ void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { // Fire the hook stop event. dispatch_event(&event); - } - else if (recorded_data->category == XRecordFromServer || recorded_data->category == XRecordFromClient) { + } else if (recorded_data->category == XRecordFromServer || recorded_data->category == XRecordFromClient) { // Get XRecord data. XRecordDatum *data = (XRecordDatum *) recorded_data->data; @@ -381,8 +370,7 @@ void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { dispatch_event(&event); } } - } - else if (data->type == KeyRelease) { + } else if (data->type == KeyRelease) { // The X11 KeyCode associated with this event. KeyCode keycode = (KeyCode) data->event.u.u.detail; KeySym keysym = 0x00; @@ -442,8 +430,7 @@ void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { // Fire key released event. dispatch_event(&event); - } - else if (data->type == ButtonPress) { + } else if (data->type == ButtonPress) { // X11 handles wheel events as button events. if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelDown || data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight) { @@ -500,8 +487,7 @@ void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelLeft) { // Wheel Rotated Up and Away. event.data.wheel.rotation = -1; - } - else { // data->event.u.u.detail == WheelDown + } else { // data->event.u.u.detail == WheelDown // Wheel Rotated Down and Towards. event.data.wheel.rotation = 1; } @@ -509,8 +495,7 @@ void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelDown) { // Wheel Rotated Up or Down. event.data.wheel.direction = WHEEL_VERTICAL_DIRECTION; - } - else { // data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight + } else { // data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight // Wheel Rotated Left or Right. event.data.wheel.direction = WHEEL_HORIZONTAL_DIRECTION; } @@ -523,8 +508,7 @@ void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { // Fire mouse wheel event. dispatch_event(&event); - } - else { + } else { /* This information is all static for X11, its up to the WM to * decide how to interpret the wheel events. */ @@ -566,13 +550,11 @@ void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { if (button == hook->input.mouse.click.button && (long int) (timestamp - hook->input.mouse.click.time) <= hook_get_multi_click_time()) { if (hook->input.mouse.click.count < USHRT_MAX) { hook->input.mouse.click.count++; - } - else { + } else { logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n", __FUNCTION__, __LINE__); } - } - else { + } else { // Reset the click count. hook->input.mouse.click.count = 1; @@ -616,8 +598,7 @@ void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { // Fire mouse pressed event. dispatch_event(&event); } - } - else if (data->type == ButtonRelease) { + } else if (data->type == ButtonRelease) { // X11 handles wheel events as button events. if (data->event.u.u.detail != WheelUp && data->event.u.u.detail != WheelDown) { /* This information is all static for X11, its up to the WM to @@ -731,8 +712,7 @@ void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { hook->input.mouse.click.count = 0; } } - } - else if (data->type == MotionNotify) { + } else if (data->type == MotionNotify) { // Reset the click count. if (hook->input.mouse.click.count != 0 && (long int) (timestamp - hook->input.mouse.click.time) > hook_get_multi_click_time()) { hook->input.mouse.click.count = 0; @@ -750,8 +730,7 @@ void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { if (hook->input.mouse.is_dragged) { // Create Mouse Dragged event. event.type = EVENT_MOUSE_DRAGGED; - } - else { + } else { // Create a Mouse Moved event. event.type = EVENT_MOUSE_MOVED; } @@ -780,14 +759,12 @@ void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { // Fire mouse move event. dispatch_event(&event); - } - else { + } else { // In theory this *should* never execute. logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled X11 event: %#X.\n", __FUNCTION__, __LINE__, (unsigned int) data->type); } - } - else { + } else { logger(LOG_LEVEL_WARN, "%s [%u]: Unhandled X11 hook category! (%#X)\n", __FUNCTION__, __LINE__, recorded_data->category); } @@ -917,8 +894,7 @@ static int xrecord_alloc() { // Free up the context if it was set. XRecordFreeContext(hook->data.display, hook->ctrl.context); - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordCreateContext failure!\n", __FUNCTION__, __LINE__); @@ -928,8 +904,7 @@ static int xrecord_alloc() { // Free the XRecord range. XFree(hook->data.range); - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordAllocRange failure!\n", __FUNCTION__, __LINE__); @@ -950,8 +925,7 @@ static int xrecord_query() { __FUNCTION__, __LINE__, major, minor); status = xrecord_alloc(); - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: XRecord is not currently available!\n", __FUNCTION__, __LINE__); @@ -978,8 +952,7 @@ static int xrecord_start() { if (is_auto_repeat) { logger(LOG_LEVEL_DEBUG, "%s [%u]: Successfully enabled detectable autorepeat.\n", __FUNCTION__, __LINE__); - } - else { + } else { logger(LOG_LEVEL_WARN, "%s [%u]: Could not enable detectable auto-repeat!\n", __FUNCTION__, __LINE__); } @@ -994,13 +967,11 @@ static int xrecord_start() { if (context != NULL) { hook->input.context = xkb_context_ref(context); - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: xkb_context_new failure!\n", __FUNCTION__, __LINE__); } - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: xcb_connect failure! (%d)\n", __FUNCTION__, __LINE__, xcb_status); } @@ -1030,8 +1001,7 @@ static int xrecord_start() { hook->input.connection = NULL; } #endif - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: XOpenDisplay failure!\n", __FUNCTION__, __LINE__); @@ -1070,8 +1040,7 @@ IOHOOK_API int hook_run() { // Free data associated with this hook. free(hook); hook = NULL; - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for hook structure!\n", __FUNCTION__, __LINE__); @@ -1112,8 +1081,7 @@ IOHOOK_API int hook_stop() { status = IOHOOK_SUCCESS; } - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordGetContext failure!\n", __FUNCTION__, __LINE__); @@ -1121,8 +1089,7 @@ IOHOOK_API int hook_stop() { } free(state); - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for XRecordState!\n", __FUNCTION__, __LINE__); diff --git a/hook/x11/input_c.h b/hook/x11/input_c.h index b734189..84e08ee 100644 --- a/hook/x11/input_c.h +++ b/hook/x11/input_c.h @@ -1465,11 +1465,9 @@ KeySym unicode_to_keysym(uint16_t unicode) { mid = (min + max) / 2; if (keysym_unicode_table[mid].unicode < unicode) { min = mid + 1; - } - else if (keysym_unicode_table[mid].unicode > unicode) { + } else if (keysym_unicode_table[mid].unicode > unicode) { max = mid - 1; - } - else { + } else { // Found it. return keysym_unicode_table[mid].keysym; } @@ -1535,11 +1533,9 @@ size_t keysym_to_unicode(KeySym keysym, uint16_t *buffer, size_t size) { mid = (min + max) / 2; if (keysym_unicode_table[mid].keysym < keysym) { min = mid + 1; - } - else if (keysym_unicode_table[mid].keysym > keysym) { + } else if (keysym_unicode_table[mid].keysym > keysym) { max = mid - 1; - } - else { + } else { // Found it. if (count < size) { buffer[count++] = keysym_unicode_table[mid].unicode; @@ -1577,8 +1573,7 @@ uint16_t keycode_to_scancode(KeyCode keycode) { // extra space in the lookup table due to binary padding. scancode = evdev_scancode_table[keycode][0]; } - } - else { + } else { // Evdev was disabled, fallback to XFree86. #endif unsigned short xfree86_size = sizeof(xfree86_scancode_table) / sizeof(xfree86_scancode_table[0]); @@ -1611,8 +1606,7 @@ KeyCode scancode_to_keycode(uint16_t scancode) { // but math is generally slower than memory and we cannot save any // extra space in the lookup table due to binary padding. keycode = evdev_scancode_table[scancode][1]; - } - else { + } else { // Offset is the lower order bits + 128 scancode = (scancode & 0x007F) | 0x80; @@ -1620,8 +1614,7 @@ KeyCode scancode_to_keycode(uint16_t scancode) { keycode = evdev_scancode_table[scancode][1]; } } - } - else { + } else { // Evdev was disabled, fallback to XFree86. #endif unsigned short xfree86_size = sizeof(xfree86_scancode_table) / sizeof(xfree86_scancode_table[0]); @@ -1632,8 +1625,7 @@ KeyCode scancode_to_keycode(uint16_t scancode) { // but math is generally slower than memory and we cannot save any // extra space in the lookup table due to binary padding. keycode = xfree86_scancode_table[scancode][1]; - } - else { + } else { // Offset: lower order bits + 128 (If no size optimization!) scancode = (scancode & 0x007F) | 0x80; @@ -1687,8 +1679,7 @@ size_t keycode_to_unicode(struct xkb_state* state, KeyCode keycode, uint16_t *bu if ((unicode <= 0xD7FF || (unicode >= 0xE000 && unicode <= 0xFFFF)) && length >= 1) { buffer[0] = unicode; count = 1; - } - else if (unicode >= 0x10000) { + } else if (unicode >= 0x10000) { unsigned int code = (unicode - 0x10000); buffer[0] = 0xD800 | (code >> 10); buffer[1] = 0xDC00 | (code & 0x3FF); @@ -1789,20 +1780,17 @@ KeySym keycode_to_keysym(KeyCode keycode, unsigned int modifier_mask) { if (modifier_mask & ShiftMask || (modifier_mask & LockMask && is_shift_lock)) { // i = 0 keysym = keyboard_map[keycode *keysym_per_keycode]; - } - else { + } else { // i = 1 keysym = keyboard_map[keycode *keysym_per_keycode + 1]; } - } - else if (modifier_mask ^ ShiftMask && modifier_mask ^ LockMask) { + } else if (modifier_mask ^ ShiftMask && modifier_mask ^ LockMask) { /* The Shift and Lock modifiers are both off. In this case, * the first KeySym is used. */ // index = 0 keysym = keyboard_map[keycode *keysym_per_keycode]; - } - else if (modifier_mask ^ ShiftMask && modifier_mask & LockMask && is_caps_lock) { + } else if (modifier_mask ^ ShiftMask && modifier_mask & LockMask && is_caps_lock) { /* The Shift modifier is off, and the Lock modifier is on * and is interpreted as CapsLock. In this case, the first * KeySym is used, but if that KeySym is lowercase @@ -1818,8 +1806,7 @@ KeySym keycode_to_keysym(KeyCode keycode, unsigned int modifier_mask) { XConvertCase(keysym, &lower_keysym, &upper_keysym); keysym = upper_keysym; } - } - else if (modifier_mask & ShiftMask && modifier_mask & LockMask && is_caps_lock) { + } else if (modifier_mask & ShiftMask && modifier_mask & LockMask && is_caps_lock) { /* The Shift modifier is on, and the Lock modifier is on and * is interpreted as CapsLock. In this case, the second * KeySym is used, but if that KeySym is lowercase @@ -1835,16 +1822,14 @@ KeySym keycode_to_keysym(KeyCode keycode, unsigned int modifier_mask) { XConvertCase(keysym, &lower_keysym, &upper_keysym); keysym = lower_keysym; } - } - else if (modifier_mask & ShiftMask || (modifier_mask & LockMask && is_shift_lock) || modifier_mask & (ShiftMask + LockMask)) { + } else if (modifier_mask & ShiftMask || (modifier_mask & LockMask && is_shift_lock) || modifier_mask & (ShiftMask + LockMask)) { /* The Shift modifier is on, or the Lock modifier is on and * is interpreted as ShiftLock, or both. In this case, the * second KeySym is used. */ // index = 1 keysym = keyboard_map[keycode *keysym_per_keycode + 1]; - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: Unable to determine the KeySym index!\n", __FUNCTION__, __LINE__); } @@ -1885,16 +1870,14 @@ void load_input_helper(Display *disp) { // logger(LOG_LEVEL_WARN, // "%s [%u]: Unknown keycode name '%s', please file a bug report!\n", // __FUNCTION__, __LINE__, layout_name); - } - else if (layout_name == NULL) { + } else if (layout_name == NULL) { logger(LOG_LEVEL_ERROR, "%s [%u]: X atom name failure for desc->names->keycodes!\n", __FUNCTION__, __LINE__); } XkbFreeClientMap(desc, XkbGBN_AllComponentsMask, True); - } - else { + } else { logger(LOG_LEVEL_WARN, "%s [%u]: XkbGetKeyboard failed to locate a valid keyboard!\n", __FUNCTION__, __LINE__); @@ -1940,23 +1923,20 @@ void load_input_helper(Display *disp) { if (capsLock != 0 && modifierMap->modifiermap[i] == capsLock) { is_caps_lock = true; is_shift_lock = false; - } - else if (shiftLock != 0 && modifierMap->modifiermap[i] == shiftLock) { + } else if (shiftLock != 0 && modifierMap->modifiermap[i] == shiftLock) { is_shift_lock = true; } } XFree(modifierMap); - } - else { + } else { XFree(keyboard_map); logger(LOG_LEVEL_ERROR, "%s [%u]: Unable to get modifier mapping table!\n", __FUNCTION__, __LINE__); } - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: Unable to get keyboard mapping table!\n", __FUNCTION__, __LINE__); diff --git a/hook/x11/properties_c.h b/hook/x11/properties_c.h index 1124df0..2c19b52 100644 --- a/hook/x11/properties_c.h +++ b/hook/x11/properties_c.h @@ -95,8 +95,7 @@ static void *settings_thread_proc(void *arg) { __FUNCTION__, __LINE__); } pthread_mutex_unlock(&xrandr_mutex); - } - else { + } else { logger(LOG_LEVEL_WARN, "%s [%u]: XRandR is not currently available!\n", __FUNCTION__, __LINE__); } @@ -106,8 +105,7 @@ static void *settings_thread_proc(void *arg) { // Execute the thread cleanup handler. pthread_cleanup_pop(1); - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: XOpenDisplay failure!\n", __FUNCTION__, __LINE__); } @@ -131,8 +129,7 @@ IOHOOK_API screen_data* hook_create_screen_info(unsigned char *count) { logger(LOG_LEVEL_WARN, "%s [%u]: Screen count overflow detected!\n", __FUNCTION__, __LINE__); - } - else { + } else { *count = (uint8_t) xine_count; } @@ -163,8 +160,7 @@ IOHOOK_API screen_data* hook_create_screen_info(unsigned char *count) { logger(LOG_LEVEL_WARN, "%s [%u]: Screen count overflow detected!\n", __FUNCTION__, __LINE__); - } - else { + } else { *count = (uint8_t) xrandr_count; } @@ -185,8 +181,7 @@ IOHOOK_API screen_data* hook_create_screen_info(unsigned char *count) { }; XRRFreeCrtcInfo(crtc_info); - } - else { + } else { logger(LOG_LEVEL_WARN, "%s [%u]: XRandr failed to return crtc information! (%#X)\n", __FUNCTION__, __LINE__, xrandr_resources->crtcs[i]); } @@ -249,8 +244,7 @@ IOHOOK_API long int hook_get_auto_repeat_rate() { } } #endif - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", __FUNCTION__, __LINE__, "XOpenDisplay failure!"); } @@ -295,8 +289,7 @@ IOHOOK_API long int hook_get_auto_repeat_delay() { } } #endif - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", __FUNCTION__, __LINE__, "XOpenDisplay failure!"); } @@ -321,8 +314,7 @@ IOHOOK_API long int hook_get_pointer_acceleration_multiplier() { value = (long int) accel_denominator; } - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", __FUNCTION__, __LINE__, "XOpenDisplay failure!"); } @@ -343,8 +335,7 @@ IOHOOK_API long int hook_get_pointer_acceleration_threshold() { value = (long int) threshold; } - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", __FUNCTION__, __LINE__, "XOpenDisplay failure!"); } @@ -365,8 +356,7 @@ IOHOOK_API long int hook_get_pointer_sensitivity() { value = (long int) accel_numerator; } - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", __FUNCTION__, __LINE__, "XOpenDisplay failure!"); } @@ -393,8 +383,7 @@ IOHOOK_API long int hook_get_multi_click_time() { successful = true; } } - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", __FUNCTION__, __LINE__, "XOpenDisplay failure!"); } @@ -422,8 +411,7 @@ IOHOOK_API long int hook_get_multi_click_time() { successful = true; } } - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", __FUNCTION__, __LINE__, "XOpenDisplay failure!"); } @@ -446,8 +434,7 @@ void on_library_load() { if (properties_disp == NULL) { logger(LOG_LEVEL_WARN, "%s [%u]: %s\n", __FUNCTION__, __LINE__, "XOpenDisplay failure!"); - } - else { + } else { logger(LOG_LEVEL_DEBUG, "%s [%u]: %s\n", __FUNCTION__, __LINE__, "XOpenDisplay success."); } @@ -461,8 +448,7 @@ void on_library_load() { if (pthread_create(&settings_thread_id, &settings_thread_attr, settings_thread_proc, NULL) == 0) { logger(LOG_LEVEL_DEBUG, "%s [%u]: Successfully created settings thread.\n", __FUNCTION__, __LINE__); - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to create settings thread!\n", __FUNCTION__, __LINE__); } From e6086cbf68b6286280f51afe19ac0066b223de26 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 29 Jun 2020 11:50:14 -0400 Subject: [PATCH 089/120] update code style --- hook/darwin/hook_c.h | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/hook/darwin/hook_c.h b/hook/darwin/hook_c.h index 3723b58..382c0d3 100644 --- a/hook/darwin/hook_c.h +++ b/hook/darwin/hook_c.h @@ -104,8 +104,7 @@ static inline void dispatch_event(iohook_event *const event) { __FUNCTION__, __LINE__, event->type); dispatcher(event); - } - else { + } else { logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n", __FUNCTION__, __LINE__); } @@ -264,8 +263,7 @@ static int start_message_port_runloop() { __FUNCTION__, __LINE__); status = IOHOOK_SUCCESS; - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopSourceCreate failure!\n", __FUNCTION__, __LINE__); @@ -273,15 +271,13 @@ static int start_message_port_runloop() { } pthread_mutex_unlock(&msg_port_mutex); - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopObserverCreate failure!\n", __FUNCTION__, __LINE__); status = IOHOOK_ERROR_CREATE_OBSERVER; } - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: No available TIS Message pointer.\n", __FUNCTION__, __LINE__); } @@ -411,8 +407,7 @@ static inline void process_key_pressed(uint64_t timestamp, CGEventRef event_ref) // Wait for a lock while the main runloop processes they key typed event. pthread_cond_wait(&msg_port_cond, &msg_port_mutex); - } - else { + } else { logger(LOG_LEVEL_WARN, "%s [%u]: Failed to signal RunLoop main!\n", __FUNCTION__, __LINE__); } @@ -952,7 +947,6 @@ CGEventRef hook_event_proc(CGEventTapProxy tap_proxy, CGEventType type, CGEventR process_modifier_changed(timestamp, event_ref); break; - //b // case NX_SYSDEFINED: // process_system_key(timestamp, event_ref); // break; @@ -1230,8 +1224,7 @@ IOHOOK_API int hook_run() { // Free the TIS Message. free(tis_message); - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for TIS message structure!\n", __FUNCTION__, __LINE__); @@ -1242,8 +1235,7 @@ IOHOOK_API int hook_run() { // Invalidate and free hook observer. CFRunLoopObserverInvalidate(hook->observer); CFRelease(hook->observer); - } - else { + } else { // We cant do a whole lot of anything if we cant // create run loop observer. logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopObserverCreate failure!\n", @@ -1252,8 +1244,7 @@ IOHOOK_API int hook_run() { // Set the exit status. status = IOHOOK_ERROR_CREATE_OBSERVER; } - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopGetCurrent failure!\n", __FUNCTION__, __LINE__); @@ -1263,8 +1254,7 @@ IOHOOK_API int hook_run() { // Clean up the event source. CFRelease(hook->source); - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: CFMachPortCreateRunLoopSource failure!\n", __FUNCTION__, __LINE__); @@ -1275,8 +1265,7 @@ IOHOOK_API int hook_run() { // Stop the CFMachPort from receiving any more messages. CFMachPortInvalidate(hook->port); CFRelease(hook->port); - } - else { + } else { logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to create event port!\n", __FUNCTION__, __LINE__); From a1db03bfdbdf29c38f35d5ad99b5dc851786c0f8 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sat, 25 Jul 2020 11:41:27 -0400 Subject: [PATCH 090/120] update appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 0515a15..09ec168 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.14.4 + GOVERSION: 1.14.6 # GOPATH: c:\gopath # scripts that run after cloning repository From 80a5bf3c7e38bbbcdfe44a6082451daf970daa0a Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 9 Aug 2020 08:38:35 -0400 Subject: [PATCH 091/120] update and optimize hook code --- hook.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/hook.go b/hook.go index 9a8d107..15b4563 100644 --- a/hook.go +++ b/hook.go @@ -102,9 +102,7 @@ var ( func allPressed(pressed map[uint16]bool, keys ...uint16) bool { for _, i := range keys { // fmt.Println(i) - if pressed[i] == false { - return false - } + return !pressed[i] } return true @@ -123,7 +121,7 @@ func Register(when uint8, cmds []string, cb func(Event)) { keys[key] = tmp cbs[key] = cb events[when] = append(events[when], key) - return + // return } // Process return go hook process @@ -152,7 +150,7 @@ func Process(EvChan <-chan Event) (out chan bool) { out <- true }() - return out + return } // String return hook kind string From 2a5dc199e5c1abeeac06e47572cbe6ef90494f39 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 7 Sep 2020 10:27:44 -0400 Subject: [PATCH 092/120] Update README.md --- README.md | 2 +- {example => examples}/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename {example => examples}/main.go (97%) diff --git a/README.md b/README.md index d6c37ad..52d91c4 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ package main import ( "fmt" - "github.com/robotn/gohook" + hook "github.com/robotn/gohook" ) func main() { diff --git a/example/main.go b/examples/main.go similarity index 97% rename from example/main.go rename to examples/main.go index 89ff892..367cf0a 100644 --- a/example/main.go +++ b/examples/main.go @@ -15,7 +15,7 @@ func addEvent() { fmt.Println("--- Please press w---") hook.Register(hook.KeyDown, []string{"w"}, func(e hook.Event) { - fmt.Println("w") + fmt.Println("w-") }) s := hook.Start() From f76284ab753c511b3a9631cc5682c19488bb0672 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 6 Oct 2020 11:23:16 -0400 Subject: [PATCH 093/120] update go mod and CI --- .github/workflows/go.yml | 4 ++-- appveyor.yml | 2 +- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index e253a0a..9401d06 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,10 +10,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Set up Go 1.14 + - name: Set up Go 1.15 uses: actions/setup-go@v1 with: - go-version: 1.14 + go-version: 1.15 id: go - name: Check out code into the Go module directory diff --git a/appveyor.yml b/appveyor.yml index 09ec168..7f46dbe 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.14.6 + GOVERSION: 1.15.2 # GOPATH: c:\gopath # scripts that run after cloning repository diff --git a/go.mod b/go.mod index f3163d8..4c06c0e 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/robotn/gohook go 1.13 -require github.com/vcaesar/tt v0.10.0 +require github.com/vcaesar/tt v0.10.1 diff --git a/go.sum b/go.sum index 91c4e9b..37a9ae7 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -github.com/vcaesar/tt v0.10.0 h1:jPtFiRRnCOXVakd5ujCed2dwWwTs91wh8W1iHeUIfc0= -github.com/vcaesar/tt v0.10.0/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg= +github.com/vcaesar/tt v0.10.1 h1:0DEl+XkyXivEwb/7jIYhDQ1g7KtMYqD/ZqABcMwC3wo= +github.com/vcaesar/tt v0.10.1/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg= From d6d3b4663e019912ba61de017db77eecd43313af Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 6 Oct 2020 11:24:07 -0400 Subject: [PATCH 094/120] Fixed all pressed return bug --- hook.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hook.go b/hook.go index 15b4563..189f41c 100644 --- a/hook.go +++ b/hook.go @@ -102,7 +102,9 @@ var ( func allPressed(pressed map[uint16]bool, keys ...uint16) bool { for _, i := range keys { // fmt.Println(i) - return !pressed[i] + if !pressed[i] { + return false + } } return true From b3a746d8c4e98f3b233b7a08c726cae3062c6808 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 8 Oct 2020 11:09:37 -0400 Subject: [PATCH 095/120] update parameter name and version --- hook.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hook.go b/hook.go index 189f41c..420afa1 100644 --- a/hook.go +++ b/hook.go @@ -32,7 +32,7 @@ import ( const ( // Version get the gohook version - Version = "v0.30.2.88, Sierra Nevada!" + Version = "v0.30.4.100, Sierra Nevada!" // HookEnabled honk enable status HookEnabled = 1 // iota @@ -50,6 +50,7 @@ const ( MouseWheel = 11 FakeEvent = 12 + // Keychar could be v CharUndefined = 0xFFFF WheelUp = -1 @@ -127,10 +128,10 @@ func Register(when uint8, cmds []string, cb func(Event)) { } // Process return go hook process -func Process(EvChan <-chan Event) (out chan bool) { +func Process(evChan <-chan Event) (out chan bool) { out = make(chan bool) go func() { - for ev := range EvChan { + for ev := range evChan { if ev.Kind == KeyDown || ev.Kind == KeyHold { pressed[ev.Keycode] = true } else if ev.Kind == KeyUp { From 05ca764c4246b64edd4c3d5f42c7eeaf88b47d8a Mon Sep 17 00:00:00 2001 From: vcaesar Date: Mon, 16 Nov 2020 11:27:15 -0400 Subject: [PATCH 096/120] update go mod and appveyor.yml --- appveyor.yml | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 7f46dbe..ffd5df8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.15.2 + GOVERSION: 1.15.5 # GOPATH: c:\gopath # scripts that run after cloning repository diff --git a/go.mod b/go.mod index 4c06c0e..d90a411 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/robotn/gohook go 1.13 -require github.com/vcaesar/tt v0.10.1 +require github.com/vcaesar/tt v0.11.0 diff --git a/go.sum b/go.sum index 37a9ae7..51a16a0 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -github.com/vcaesar/tt v0.10.1 h1:0DEl+XkyXivEwb/7jIYhDQ1g7KtMYqD/ZqABcMwC3wo= -github.com/vcaesar/tt v0.10.1/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg= +github.com/vcaesar/tt v0.11.0 h1:obQecjgbnAxxC6OYGY6yDvhGRW2PR5wD8Ma2uJH3WGA= +github.com/vcaesar/tt v0.11.0/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg= From 4f8581a884f793695a66a034d880b285138acc16 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Wed, 2 Dec 2020 13:01:20 -0400 Subject: [PATCH 097/120] update godoc and example --- examples/main.go | 6 +++--- hook.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/main.go b/examples/main.go index 367cf0a..bbe68f7 100644 --- a/examples/main.go +++ b/examples/main.go @@ -6,14 +6,14 @@ import ( hook "github.com/robotn/gohook" ) -func addEvent() { +func registerEvent() { fmt.Println("--- Please press ctrl + shift + q to stop hook ---") hook.Register(hook.KeyDown, []string{"q", "ctrl", "shift"}, func(e hook.Event) { fmt.Println("ctrl-shift-q") hook.End() }) - fmt.Println("--- Please press w---") + fmt.Println("--- Please press w ---") hook.Register(hook.KeyDown, []string{"w"}, func(e hook.Event) { fmt.Println("w-") }) @@ -54,7 +54,7 @@ func base() { } func main() { - addEvent() + registerEvent() base() diff --git a/hook.go b/hook.go index 420afa1..7fa2134 100644 --- a/hook.go +++ b/hook.go @@ -156,7 +156,7 @@ func Process(evChan <-chan Event) (out chan bool) { return } -// String return hook kind string +// String return formatted hook kind string func (e Event) String() string { switch e.Kind { case HookEnabled: @@ -202,7 +202,7 @@ func (e Event) String() string { return fmt.Sprintf("%v - Event: {Kind: FakeEvent}", e.When) } - return "Unknown event, contact the mantainers" + return "Unknown event, contact the mantainers." } // RawcodetoKeychar rawcode to keychar From f75bf036f9cfce99484000ffb50c5e54a6a723a8 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 22 Dec 2020 11:45:11 -0400 Subject: [PATCH 098/120] update appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index ffd5df8..c43ada5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.15.5 + GOVERSION: 1.15.6 # GOPATH: c:\gopath # scripts that run after cloning repository From 725a6e76521907b55e68041d2c1980c9d34482f0 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 22 Jan 2021 12:40:51 -0400 Subject: [PATCH 099/120] update appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index c43ada5..7bfebfe 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.15.6 + GOVERSION: 1.15.7 # GOPATH: c:\gopath # scripts that run after cloning repository From ff5e0f3e18dfd7b13453162be30b722a04ebc547 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 25 Feb 2021 11:36:14 -0400 Subject: [PATCH 100/120] update CI yml --- .github/workflows/go.yml | 4 ++-- appveyor.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 9401d06..f38d6ae 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,10 +10,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Set up Go 1.15 + - name: Set up Go 1.16 uses: actions/setup-go@v1 with: - go-version: 1.15 + go-version: 1.16 id: go - name: Check out code into the Go module directory diff --git a/appveyor.yml b/appveyor.yml index 7bfebfe..b8bb583 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.15.7 + GOVERSION: 1.16 # GOPATH: c:\gopath # scripts that run after cloning repository From d8ecd92ccfc45e7418f71220d1536d47ccdde972 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 8 Apr 2021 12:23:23 -0400 Subject: [PATCH 101/120] update appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index b8bb583..148c091 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.16 + GOVERSION: 1.16.3 # GOPATH: c:\gopath # scripts that run after cloning repository From 16f2ab895f148c02afd7db689ef148853f6f0899 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 10 Jun 2021 12:26:50 -0400 Subject: [PATCH 102/120] fixed darwin type warning --- hook/darwin/hook_c.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hook/darwin/hook_c.h b/hook/darwin/hook_c.h index 382c0d3..f4da3c1 100644 --- a/hook/darwin/hook_c.h +++ b/hook/darwin/hook_c.h @@ -597,8 +597,8 @@ static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) #endif // int - uint16_t key_code = ((uint16_t)data & 0xFFFF0000) >> 16; - uint16_t key_flags = ((uint16_t)data & 0xFFFF); + uint64_t key_code = ((uint64_t)data & 0xFFFF0000) >> 16; + uint64_t key_flags = ((uint64_t)data & 0xFFFF); //int key_state = (key_flags & 0xFF00) >> 8; bool key_down = (key_flags & 0x1) > 0; From 6c7707ceebfe6bea1305b2be05d95c5eeb005509 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 11 Jun 2021 10:27:08 -0400 Subject: [PATCH 103/120] update appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 148c091..9b32f01 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.16.3 + GOVERSION: 1.16.5 # GOPATH: c:\gopath # scripts that run after cloning repository From 927fa72aa5f3b650861bb2c84a42dc19e2af6e48 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 8 Aug 2021 14:42:54 -0400 Subject: [PATCH 104/120] add more keycodes and update appveyor.yml --- appveyor.yml | 2 +- tables.go | 346 ++++++++++++++++++++++++++------------------------- 2 files changed, 175 insertions(+), 173 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 9b32f01..60c63c1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.16.5 + GOVERSION: 1.16.7 # GOPATH: c:\gopath # scripts that run after cloning repository diff --git a/tables.go b/tables.go index 9a4ec86..0778dcd 100644 --- a/tables.go +++ b/tables.go @@ -2,178 +2,179 @@ package hook var ( raw2key = map[uint16]string{ // https://github.com/wesbos/keycodes - 0: "error", - 3: "break", - 8: "backspace", - 9: "tab", - 12: "clear", - 13: "enter", - 16: "shift", - 17: "ctrl", - 18: "alt", - 19: "pause/break", - 20: "caps lock", - 21: "hangul", - 25: "hanja", - 27: "escape", - 28: "conversion", - 29: "non-conversion", - 32: "spacebar", - 33: "page up", - 34: "page down", - 35: "end", - 36: "home", - 37: "left arrow", - 38: "up arrow", - 39: "right arrow", - 40: "down arrow", - 41: "select", - 42: "print", - 43: "execute", - 44: "Print Screen", - 45: "insert", - 46: "delete", - 47: "help", - 48: "0", - 49: "1", - 50: "2", - 51: "3", - 52: "4", - 53: "5", - 54: "6", - 55: "7", - 56: "8", - 57: "9", - 58: ":", - 59: ";", - 60: "<", - 61: "=", - 63: "ß", - 64: "@", - 65: "a", - 66: "b", - 67: "c", - 68: "d", - 69: "e", - 70: "f", - 71: "g", - 72: "h", - 73: "i", - 74: "j", - 75: "k", - 76: "l", - 77: "m", - 78: "n", - 79: "o", - 80: "p", - 81: "q", - 82: "r", - 83: "s", - 84: "t", - 85: "u", - 86: "v", - 87: "w", - 88: "x", - 89: "y", - 90: "z", - 91: "l-super", - 92: "r-super", - 93: "apps", - 95: "sleep", - 96: "numpad 0", - 97: "numpad 1", - 98: "numpad 2", - 99: "numpad 3", - 100: "numpad 4", - 101: "numpad 5", - 102: "numpad 6", - 103: "numpad 7", - 104: "numpad 8", - 105: "numpad 9", - 106: "multiply", - 107: "add", - 108: "numpad period", - 109: "subtract", - 110: "decimal point", - 111: "divide", - 112: "f1", - 113: "f2", - 114: "f3", - 115: "f4", - 116: "f5", - 117: "f6", - 118: "f7", - 119: "f8", - 120: "f9", - 121: "f10", - 122: "f11", - 123: "f12", - 124: "f13", - 125: "f14", - 126: "f15", - 127: "f16", - 128: "f17", - 129: "f18", - 130: "f19", - 131: "f20", - 132: "f21", - 133: "f22", - 134: "f23", - 135: "f24", - 144: "num lock", - 145: "scroll lock", - 160: "^", - 161: "!", - 162: "؛", - 163: "#", - 164: "$", - 165: "ù", - 166: "page backward", - 167: "page forward", - 168: "refresh", - 169: "closing paren (AZERTY)", - 170: "*", - 171: "~ + * key", - 172: "home key", - 173: "minus (firefox), mute/unmute", - 174: "decrease volume level", - 175: "increase volume level", - 176: "next", - 177: "previous", - 178: "stop", - 179: "play/pause", - 180: "e-mail", - 181: "mute/unmute (firefox)", - 182: "decrease volume level (firefox)", - 183: "increase volume level (firefox)", - 186: "semi-colon / ñ", - 187: "equal sign", - 188: "comma", - 189: "dash", - 190: "period", - 191: "forward slash / ç", - 192: "grave accent / ñ / æ / ö", - 193: "?, / or °", - 194: "numpad period (chrome)", - 219: "open bracket", - 220: "back slash", - 221: "close bracket / å", - 222: "single quote / ø / ä", - 223: "`", - 224: "left or right ⌘ key (firefox)", - 225: "altgr", - 226: "< /git >, left back slash", - 230: "GNOME Compose Key", - 231: "ç", - 233: "XF86Forward", - 234: "XF86Back", - 235: "non-conversion", - 240: "alphanumeric", - 242: "hiragana/katakana", - 243: "half-width/full-width", - 244: "kanji", - 251: "unlock trackpad (Chrome/Edge)", - 255: "toggle touchpad", + 0: "error", + 3: "break", + 8: "backspace", + 9: "tab", + 12: "clear", + 13: "enter", + 16: "shift", + 17: "ctrl", + 18: "alt", + 19: "pause/break", + 20: "caps lock", + 21: "hangul", + 25: "hanja", + 27: "escape", + 28: "conversion", + 29: "non-conversion", + 32: "spacebar", + 33: "page up", + 34: "page down", + 35: "end", + 36: "home", + 37: "left arrow", + 38: "up arrow", + 39: "right arrow", + 40: "down arrow", + 41: "select", + 42: "print", + 43: "execute", + 44: "Print Screen", + 45: "insert", + 46: "delete", + 47: "help", + 48: "0", + 49: "1", + 50: "2", + 51: "3", + 52: "4", + 53: "5", + 54: "6", + 55: "7", + 56: "8", + 57: "9", + 58: ":", + 59: ";", + 60: "<", + 61: "=", + 63: "ß", + 64: "@", + 65: "a", + 66: "b", + 67: "c", + 68: "d", + 69: "e", + 70: "f", + 71: "g", + 72: "h", + 73: "i", + 74: "j", + 75: "k", + 76: "l", + 77: "m", + 78: "n", + 79: "o", + 80: "p", + 81: "q", + 82: "r", + 83: "s", + 84: "t", + 85: "u", + 86: "v", + 87: "w", + 88: "x", + 89: "y", + 90: "z", + 91: "l-super", + 92: "r-super", + 93: "apps", + 95: "sleep", + 96: "numpad 0", + 97: "numpad 1", + 98: "numpad 2", + 99: "numpad 3", + 100: "numpad 4", + 101: "numpad 5", + 102: "numpad 6", + 103: "numpad 7", + 104: "numpad 8", + 105: "numpad 9", + 106: "multiply", + 107: "add", + 108: "numpad period", + 109: "subtract", + 110: "decimal point", + 111: "divide", + 112: "f1", + 113: "f2", + 114: "f3", + 115: "f4", + 116: "f5", + 117: "f6", + 118: "f7", + 119: "f8", + 120: "f9", + 121: "f10", + 122: "f11", + 123: "f12", + 124: "f13", + 125: "f14", + 126: "f15", + 127: "f16", + 128: "f17", + 129: "f18", + 130: "f19", + 131: "f20", + 132: "f21", + 133: "f22", + 134: "f23", + 135: "f24", + 144: "num lock", + 145: "scroll lock", + 160: "^", + 161: "!", + 162: "؛", + 163: "#", + 164: "$", + 165: "ù", + 166: "page backward", + 167: "page forward", + 168: "refresh", + 169: "closing paren (AZERTY)", + 170: "*", + 171: "~ + * key", + 172: "home key", + 173: "minus (firefox), mute/unmute", + 174: "decrease volume level", + 175: "increase volume level", + 176: "next", + 177: "previous", + 178: "stop", + 179: "play/pause", + 180: "e-mail", + 181: "mute/unmute (firefox)", + 182: "decrease volume level (firefox)", + 183: "increase volume level (firefox)", + 186: "semi-colon / ñ", + 187: "equal sign", + 188: "comma", + 189: "dash", + 190: "period", + 191: "forward slash / ç", + 192: "grave accent / ñ / æ / ö", + 193: "?, / or °", + 194: "numpad period (chrome)", + 219: "open bracket", + 220: "back slash", + 221: "close bracket / å", + 222: "single quote / ø / ä", + 223: "`", + 224: "left or right ⌘ key (firefox)", + 225: "altgr", + 226: "< /git >, left back slash", + 230: "GNOME Compose Key", + 231: "ç", + 233: "XF86Forward", + 234: "XF86Back", + 235: "non-conversion", + 240: "alphanumeric", + 242: "hiragana/katakana", + 243: "half-width/full-width", + 244: "kanji", + 251: "unlock trackpad (Chrome/Edge)", + 255: "toggle touchpad", + 65517: "hyper", } keytoraw = map[string]uint16{ @@ -348,5 +349,6 @@ var ( "kanji": 244, "unlock trackpad (Chrome/Edge)": 251, "toggle touchpad": 255, + "hyper": 65517, } ) From 35f39384135b9971bba9f2f1a42755498eb22b0f Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 22 Aug 2021 10:13:04 -0400 Subject: [PATCH 105/120] Update go.yml --- .github/workflows/go.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index f38d6ae..670d0d1 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,10 +10,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Set up Go 1.16 + - name: Set up Go 1.17 uses: actions/setup-go@v1 with: - go-version: 1.16 + go-version: 1.17 id: go - name: Check out code into the Go module directory From 095f4f31967bed8dbade6b2e39c60efaa5668fee Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 9 Sep 2021 10:34:48 -0400 Subject: [PATCH 106/120] Changed to both two license --- LICENSE | 695 ++------------------------------------------------- hook/LICENSE | 674 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 695 insertions(+), 674 deletions(-) create mode 100644 hook/LICENSE diff --git a/LICENSE b/LICENSE index 9cecc1d..cc87081 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,21 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - {project} Copyright (C) {year} {fullname} - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +MIT License + +Copyright (c) 2016 go-ego Project Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/hook/LICENSE b/hook/LICENSE new file mode 100644 index 0000000..9cecc1d --- /dev/null +++ b/hook/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From b9033f7e4cb8ab7a321e6ed4cf6ffc66f07fc112 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 10 Sep 2021 14:12:04 -0400 Subject: [PATCH 107/120] Update appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 60c63c1..02fe53f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.16.7 + GOVERSION: 1.17.1 # GOPATH: c:\gopath # scripts that run after cloning repository From 384f5adf977510c1084d92bddc9907bc2fa3f3bb Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 26 Sep 2021 15:09:05 -0400 Subject: [PATCH 108/120] Update go mod and version --- go.mod | 4 ++-- go.sum | 4 ++-- hook.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index d90a411..d55c838 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/robotn/gohook -go 1.13 +go 1.17 -require github.com/vcaesar/tt v0.11.0 +require github.com/vcaesar/tt v0.20.0 diff --git a/go.sum b/go.sum index 51a16a0..b3cb17f 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -github.com/vcaesar/tt v0.11.0 h1:obQecjgbnAxxC6OYGY6yDvhGRW2PR5wD8Ma2uJH3WGA= -github.com/vcaesar/tt v0.11.0/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg= +github.com/vcaesar/tt v0.20.0 h1:9t2Ycb9RNHcP0WgQgIaRKJBB+FrRdejuaL6uWIHuoBA= +github.com/vcaesar/tt v0.20.0/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg= diff --git a/hook.go b/hook.go index 7fa2134..a88a992 100644 --- a/hook.go +++ b/hook.go @@ -32,7 +32,7 @@ import ( const ( // Version get the gohook version - Version = "v0.30.4.100, Sierra Nevada!" + Version = "v0.31.2.113, Sierra Nevada!" // HookEnabled honk enable status HookEnabled = 1 // iota From 4c47f7354b7d160bed3fb1655b001f9ce481b91c Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sat, 16 Oct 2021 12:01:40 -0400 Subject: [PATCH 109/120] Update README.md --- README.md | 4 ++-- appveyor.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 52d91c4..f64a133 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,10 @@ func add() { } func low() { - EvChan := hook.Start() + evChan := hook.Start() defer hook.End() - for ev := range EvChan { + for ev := range evChan { fmt.Println("hook: ", ev) } } diff --git a/appveyor.yml b/appveyor.yml index 02fe53f..34b711f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.17.1 + GOVERSION: 1.17.2 # GOPATH: c:\gopath # scripts that run after cloning repository From 3e0ac28943c163d06c9c5345e419640ee141a8fc Mon Sep 17 00:00:00 2001 From: vcaesar Date: Tue, 23 Nov 2021 16:19:46 -0400 Subject: [PATCH 110/120] Update keycode.go use the newest code --- appveyor.yml | 2 +- go.mod | 5 ++- go.sum | 2 + keycode.go | 102 +++------------------------------------------------ 4 files changed, 13 insertions(+), 98 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 34b711f..70c124c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.17.2 + GOVERSION: 1.17.3 # GOPATH: c:\gopath # scripts that run after cloning repository diff --git a/go.mod b/go.mod index d55c838..c688876 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/robotn/gohook go 1.17 -require github.com/vcaesar/tt v0.20.0 +require ( + github.com/vcaesar/keycode v0.10.0 + github.com/vcaesar/tt v0.20.0 +) diff --git a/go.sum b/go.sum index b3cb17f..fa34337 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ +github.com/vcaesar/keycode v0.10.0 h1:Qx5QE8ZXHyRyjoA2QOxBp25OKMKB+zxMVqm0FWGV0d4= +github.com/vcaesar/keycode v0.10.0/go.mod h1:JNlY7xbKsh+LAGfY2j4M3znVrGEm5W1R8s/Uv6BJcfQ= github.com/vcaesar/tt v0.20.0 h1:9t2Ycb9RNHcP0WgQgIaRKJBB+FrRdejuaL6uWIHuoBA= github.com/vcaesar/tt v0.20.0/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg= diff --git a/keycode.go b/keycode.go index 1db0925..d1ccd2e 100644 --- a/keycode.go +++ b/keycode.go @@ -10,103 +10,13 @@ package hook -type uMap map[string]uint16 +import "github.com/vcaesar/keycode" // MouseMap robotgo hook mouse's code map -var MouseMap = uMap{ - "left": 1, - "right": 2, - "center": 3, - "wheelDown": 4, - "wheelUp": 5, - "wheelLeft": 6, - "wheelRight": 7, -} +var MouseMap = keycode.MouseMap // Keycode robotgo hook key's code map -var Keycode = uMap{ - "`": 41, - "1": 2, - "2": 3, - "3": 4, - "4": 5, - "5": 6, - "6": 7, - "7": 8, - "8": 9, - "9": 10, - "0": 11, - "-": 12, - "+": 13, - // - "q": 16, - "w": 17, - "e": 18, - "r": 19, - "t": 20, - "y": 21, - "u": 22, - "i": 23, - "o": 24, - "p": 25, - "[": 26, - "]": 27, - "\\": 43, - // - "a": 30, - "s": 31, - "d": 32, - "f": 33, - "g": 34, - "h": 35, - "j": 36, - "k": 37, - "l": 38, - ";": 39, - "'": 40, - // - "z": 44, - "x": 45, - "c": 46, - "v": 47, - "b": 48, - "n": 49, - "m": 50, - ",": 51, - ".": 52, - "/": 53, - // - "f1": 59, - "f2": 60, - "f3": 61, - "f4": 62, - "f5": 63, - "f6": 64, - "f7": 65, - "f8": 66, - "f9": 67, - "f10": 68, - "f11": 69, - "f12": 70, - // more - "esc": 1, - "delete": 14, - "tab": 15, - "ctrl": 29, - "control": 29, - "alt": 56, - "space": 57, - "shift": 42, - "rshift": 54, - "enter": 28, - // - "cmd": 3675, - "command": 3675, - "rcmd": 3676, - "ralt": 3640, - // - "up": 57416, - "down": 57424, - "left": 57419, - "right": 57421, -} +var Keycode = keycode.Keycode + +// Special is the special key map +var Special = keycode.Special From 1dd095bd1f92d45dea08d66851fd7a58db1ab7dc Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 24 Dec 2021 14:05:52 -0400 Subject: [PATCH 111/120] Update README.md --- README.md | 14 ++++++++++++++ appveyor.yml | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f64a133..350b281 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,20 @@ [![GoDoc](https://godoc.org/github.com/robotn/gohook?status.svg)](https://godoc.org/github.com/robotn/gohook) +## Requirements (Linux): + +[Robotgo-requirements-event](https://github.com/go-vgo/robotgo#requirements) + +## Install: + +With Go module support (Go 1.11+), just import: + +```go +import "github.com/robotn/gohook" +``` + +## Examples: + ```Go package main diff --git a/appveyor.yml b/appveyor.yml index 70c124c..4f9c648 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.17.3 + GOVERSION: 1.17.5 # GOPATH: c:\gopath # scripts that run after cloning repository From 833856fd33f1c6d7f364696b41fc6eddfc9074a3 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 2 Jan 2022 16:24:25 -0400 Subject: [PATCH 112/120] Move robotgo event code to event.go --- event.go | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hook.go | 2 +- 2 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 event.go diff --git a/event.go b/event.go new file mode 100644 index 0000000..3c8f01c --- /dev/null +++ b/event.go @@ -0,0 +1,150 @@ +// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://github.com/go-vgo/robotgo/blob/master/LICENSE +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +package hook + +import "strconv" + +/* + ___________ ____ _______ .__ __. .___________. +| ____\ \ / / | ____|| \ | | | | +| |__ \ \/ / | |__ | \| | `---| |----` +| __| \ / | __| | . ` | | | +| |____ \ / | |____ | |\ | | | +|_______| \__/ |_______||__| \__| |__| +*/ + +// AddEvent add event listener, +// +// parameters for the string type, +// the keyboard corresponding key parameters, +// +// mouse arguments: mleft, center, mright, wheelDown, wheelUp, +// wheelLeft, wheelRight. +// +// Use "hook.AddEvents()" or "gohook" add asynchronous event listener +func AddEvent(key string) bool { + var ( + // cs *C.char + mArr = []string{"mleft", "center", "mright", "wheelDown", + "wheelUp", "wheelLeft", "wheelRight"} + mouseBool bool + ) + + for i := 0; i < len(mArr); i++ { + if key == mArr[i] { + mouseBool = true + } + } + + if len(key) > 1 && !mouseBool { + key = strconv.Itoa(int(Keycode[key])) + } + + geve := addEvent(key) + // defer C.free(unsafe.Pointer(cs)) + return geve == 0 +} + +// AddEvents add global event hook +// +// hook.AddEvents("q") +// hook.AddEvents("q", "ctrl") +// hook.AddEvents("q", "ctrl", "shift") +func AddEvents(key string, arr ...string) bool { + s := Start() + // defer End() + + ct := false + k := 0 + for { + e := <-s + + l := len(arr) + if l > 0 { + for i := 0; i < l; i++ { + ukey := Keycode[arr[i]] + + if e.Kind == KeyHold && e.Keycode == ukey { + k++ + } + + if k == l { + ct = true + } + + if e.Kind == KeyUp && e.Keycode == ukey { + if k > 0 { + k-- + } + // time.Sleep(10 * time.Microsecond) + ct = false + } + } + } else { + ct = true + } + + if ct && e.Kind == KeyUp && e.Keycode == Keycode[key] { + End() + // k = 0 + break + } + } + + return true +} + +// AddMouse add mouse event hook +// +// mouse arguments: left, center, right, wheelDown, wheelUp, +// wheelLeft, wheelRight. +// +// hook.AddMouse("left") +// hook.AddMouse("left", 100, 100) +func AddMouse(btn string, x ...int16) bool { + s := Start() + ukey := MouseMap[btn] + + ct := false + for { + e := <-s + + if len(x) > 1 { + if e.Kind == MouseMove && e.X == x[0] && e.Y == x[1] { + ct = true + } + } else { + ct = true + } + + if ct && e.Kind == MouseDown && e.Button == ukey { + End() + break + } + } + + return true +} + +// AddMousePos add listen mouse event pos hook +func AddMousePos(x, y int16) bool { + s := Start() + + for { + e := <-s + if e.Kind == MouseMove && e.X == x && e.Y == y { + End() + break + } + } + + return true +} diff --git a/hook.go b/hook.go index a88a992..4f14c85 100644 --- a/hook.go +++ b/hook.go @@ -261,7 +261,7 @@ func End() { } // AddEvent add event listener -func AddEvent(key string) int { +func addEvent(key string) int { cs := C.CString(key) defer C.free(unsafe.Pointer(cs)) From c8fc1e36f45a6a35961e219d66033d442b1cb8a5 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Wed, 9 Feb 2022 09:26:46 -0800 Subject: [PATCH 113/120] Add more examples code --- appveyor.yml | 2 +- event/os.h | 3 +- event/pub.h | 2 - examples/event/main.go | 113 +++++++++++++++++++++++++++++++++++++++++ examples/main.go | 18 +++++++ 5 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 examples/event/main.go diff --git a/appveyor.yml b/appveyor.yml index 4f9c648..e107716 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.17.5 + GOVERSION: 1.17.6 # GOPATH: c:\gopath # scripts that run after cloning repository diff --git a/event/os.h b/event/os.h index 60a4d86..25bd8d7 100644 --- a/event/os.h +++ b/event/os.h @@ -5,8 +5,7 @@ /* Python versions under 2.5 don't support this macro, but it's not * terribly difficult to replicate: */ #ifndef PyModule_AddIntMacro - #define PyModule_AddIntMacro(module, macro) \ - PyModule_AddIntConstant(module, #macro, macro) + #define PyModule_AddIntMacro(module, macro) PyModule_AddIntConstant(module, #macro, macro) #endif /* PyModule_AddIntMacro */ #if !defined(IS_MACOSX) && defined(__APPLE__) && defined(__MACH__) diff --git a/event/pub.h b/event/pub.h index 80a76d9..fc87386 100644 --- a/event/pub.h +++ b/event/pub.h @@ -73,10 +73,8 @@ struct _MEvent { typedef struct _MEvent MEvent; // typedef MMBitmap *MMBitmapRef; - MEvent mEvent; - bool loggerProc(unsigned int level, const char *format, ...) { bool status = false; diff --git a/examples/event/main.go b/examples/event/main.go new file mode 100644 index 0000000..404aec0 --- /dev/null +++ b/examples/event/main.go @@ -0,0 +1,113 @@ +// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://github.com/go-vgo/robotgo/blob/master/LICENSE +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +package main + +import ( + "fmt" + + hook "github.com/robotn/gohook" +) + +func addEvent() { + fmt.Println("--- Please press ctrl + shift + q ---") + ok := hook.AddEvents("q", "ctrl", "shift") + if ok { + fmt.Println("add events...") + } + + fmt.Println("--- Please press w---") + ok = hook.AddEvents("w") + if ok { + fmt.Println("add events") + } + + // start hook + s := hook.Start() + // end hook + defer hook.End() + + for ev := range s { + fmt.Println("hook: ", ev) + } +} + +func addMouse() { + fmt.Println("--- Please press left mouse button ---") + ok := hook.AddMouse("left") + if ok { + fmt.Println("add mouse...") + } + + fmt.Println("--- Please press left mouse button and move mosue to 100,100 ---") + ok = hook.AddMouse("left", 100, 100) + if ok { + fmt.Println("add mouse and move to 100,100 ...") + } + + fmt.Println("--- Please move mosue to 100,100 ---") + ok = hook.AddMousePos(100, 100) + if ok { + fmt.Println(" move mouse to 100,100 ...") + } +} + +func add() { + fmt.Println("--- Please press v---") + eve := hook.AddEvent("v") + + if eve { + fmt.Println("--- You press v---", "v") + } + + fmt.Println("--- Please press k---") + keve := hook.AddEvent("k") + if keve { + fmt.Println("--- You press k---", "k") + } + + fmt.Println("--- Please press f1---") + feve := hook.AddEvent("f1") + if feve { + fmt.Println("You press...", "f1") + } +} + +func event() { + //////////////////////////////////////////////////////////////////////////////// + // Global event listener + //////////////////////////////////////////////////////////////////////////////// + + add() + + fmt.Println("--- Please press left mouse button---") + mleft := hook.AddEvent("mleft") + if mleft { + fmt.Println("--- You press left mouse button---", "mleft") + } + + mright := hook.AddEvent("mright") + if mright { + fmt.Println("--- You press right mouse button---", "mright") + } + + // stop AddEvent + // hook.StopEvent() +} + +func main() { + fmt.Println("test begin...") + + addEvent() + + addMouse() + + event() +} diff --git a/examples/main.go b/examples/main.go index bbe68f7..2a1ab8b 100644 --- a/examples/main.go +++ b/examples/main.go @@ -22,6 +22,20 @@ func registerEvent() { <-hook.Process(s) } +func addMouse() { + fmt.Println("--- Please press left mouse button to see it's position and the right mouse button to exit ---") + hook.Register(hook.MouseDown, []string{}, func(e hook.Event) { + if e.Button == hook.MouseMap["left"] { + fmt.Printf("mouse left @ %v - %v\n", e.X, e.Y) + } else if e.Button == hook.MouseMap["right"] { + hook.End() + } + }) + + s := hook.Start() + <-hook.Process(s) +} + // hook listen and return values using detailed examples func add() { fmt.Println("hook add...") @@ -50,6 +64,9 @@ func base() { for ev := range evChan { fmt.Println("hook: ", ev) + if ev.Keychar == 'q' { + break + } } } @@ -59,4 +76,5 @@ func main() { base() add() + addMouse() } From 03b71aacdc22a266854f27944ccc30fa5b81bcac Mon Sep 17 00:00:00 2001 From: vcaesar Date: Sun, 10 Apr 2022 13:24:46 -0700 Subject: [PATCH 114/120] Update CI to go1.18.x --- .github/workflows/go.yml | 8 ++------ appveyor.yml | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 670d0d1..00bb0a7 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,10 +10,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Set up Go 1.17 + - name: Set up Go 1.18 uses: actions/setup-go@v1 with: - go-version: 1.17 + go-version: 1.18 id: go - name: Check out code into the Go module directory @@ -22,10 +22,6 @@ jobs: - name: Get dependencies run: | go get -v -t -d ./... - # if [ -f Gopkg.toml ]; then - # curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh - # dep ensure - # fi - name: Build run: go build -v . diff --git a/appveyor.yml b/appveyor.yml index e107716..f9e4008 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.17.6 + GOVERSION: 1.17.8 # GOPATH: c:\gopath # scripts that run after cloning repository From 762bfbdd9a10ad5c0d85b53d98a9cc8b75f89c32 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 8 Sep 2022 12:50:44 -0700 Subject: [PATCH 115/120] Update: bump CI to 1.19 --- .github/workflows/go.yml | 4 ++-- appveyor.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 00bb0a7..6ebbaf8 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,10 +10,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Set up Go 1.18 + - name: Set up Go 1.19 uses: actions/setup-go@v1 with: - go-version: 1.18 + go-version: 1.19 id: go - name: Check out code into the Go module directory diff --git a/appveyor.yml b/appveyor.yml index f9e4008..8f2fce7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.17.8 + GOVERSION: 1.19.1 # GOPATH: c:\gopath # scripts that run after cloning repository From 78031794caf988d9946ee7c747100f7fc42b0b52 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 22 Sep 2022 11:54:08 -0700 Subject: [PATCH 116/120] Add: add .github issue and PR template --- .github/issue_template.md | 28 ++++++++++++++++++++++++++++ .github/pull_request_template.md | 25 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 .github/issue_template.md create mode 100644 .github/pull_request_template.md diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..72812fe --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,28 @@ +1. Please **speak English (English only)**, this is the language everybody of us can speak and write. +2. Please take a moment to **search** that an issue **doesn't already exist**. +3. Please make sure `Golang, GCC` is installed correctly before installing Gohook. + +5. Please give all relevant information below for bug reports, incomplete details will be handled as an invalid report. + +**You MUST delete the content above including this line before posting, otherwise your issue will be invalid.** + + +- Gohook version (or commit ref): +- Go version: +- Gcc version: +- Operating system and bit: +- Resolution: +- Can you reproduce the bug at [Examples](https://github.com/robotn/gohook/blob/master/examples/main.go): + - [ ] Yes (provide example code) + - [ ] No + - [ ] Not relevant +- Provide example code: + +```Go + +``` +- Log gist: + +## Description + +... diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..264f0aa --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,25 @@ +The pull request will be closed without any reasons if it does not satisfy any of following requirements: + +1. Make sure you are targeting the `master` branch, pull requests on release branches are only allowed for bug fixes. +2. Add new features, please provide the reasons and test code. +3. Please read contributing guidelines: [CONTRIBUTING](https://github.com/robotn/gohook/blob/master/CONTRIBUTING.md) +4. Describe what your pull request does and which issue you're targeting (if any and **Please use English**) +5. ... if it is not related to any particular issues, explain why we should not reject your pull request. +6. The Commits must **use English**, must be test and No useless submissions. + +**You MUST delete the content above including this line before posting, otherwise your pull request will be invalid.** + + +**Please provide Issues links to:** + +- Issues: #1 + +**Provide test code:** + +```Go + +``` + +## Description + +... From 64c6b8bf809aeb3ad4c19655b827ddd0a0e408e2 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 24 Feb 2023 10:41:31 -0800 Subject: [PATCH 117/120] Update: update CI to go1.20.x --- .github/workflows/go.yml | 7 +++++-- appveyor.yml | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 6ebbaf8..aab9878 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,6 +1,9 @@ name: Go on: [push] jobs: + # build: + # name: Build + # runs-on: ubuntu-latest test: # name: build strategy: @@ -10,10 +13,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Set up Go 1.19 + - name: Set up Go 1.20 uses: actions/setup-go@v1 with: - go-version: 1.19 + go-version: 1.20 id: go - name: Check out code into the Go module directory diff --git a/appveyor.yml b/appveyor.yml index 8f2fce7..d5515e5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.19.1 + GOVERSION: 1.20.1 # GOPATH: c:\gopath # scripts that run after cloning repository From 885d45d4bd6a36d1c53d3621c03520b60f191e90 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Thu, 2 Mar 2023 17:37:06 -0800 Subject: [PATCH 118/120] Update: update godoc and clean go.yml --- .github/workflows/go.yml | 3 --- hook.go | 6 +++--- keycode.go | 6 +++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index aab9878..6224046 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,9 +1,6 @@ name: Go on: [push] jobs: - # build: - # name: Build - # runs-on: ubuntu-latest test: # name: build strategy: diff --git a/hook.go b/hook.go index 4f14c85..123c208 100644 --- a/hook.go +++ b/hook.go @@ -32,7 +32,7 @@ import ( const ( // Version get the gohook version - Version = "v0.31.2.113, Sierra Nevada!" + Version = "v0.40.0.123, Sierra Nevada!" // HookEnabled honk enable status HookEnabled = 1 // iota @@ -260,7 +260,7 @@ func End() { events = map[uint8][]int{} } -// AddEvent add event listener +// AddEvent add the block event listener func addEvent(key string) int { cs := C.CString(key) defer C.free(unsafe.Pointer(cs)) @@ -271,7 +271,7 @@ func addEvent(key string) int { return geve } -// StopEvent stop event listener +// StopEvent stop the block event listener func StopEvent() { C.stop_event() } diff --git a/keycode.go b/keycode.go index d1ccd2e..4224ad0 100644 --- a/keycode.go +++ b/keycode.go @@ -12,11 +12,11 @@ package hook import "github.com/vcaesar/keycode" -// MouseMap robotgo hook mouse's code map +// MouseMap defines the robotgo hook mouse's code map var MouseMap = keycode.MouseMap -// Keycode robotgo hook key's code map +// Keycode defines the robotgo hook key's code map var Keycode = keycode.Keycode -// Special is the special key map +// Special defines the special key map var Special = keycode.Special From 36f129375f4780d419811ba04fcc50381e839cdc Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 18 Aug 2023 10:31:18 -0700 Subject: [PATCH 119/120] Update: update gomod and bump CI --- .github/workflows/go.yml | 4 ++-- appveyor.yml | 2 +- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 6224046..91edc52 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,10 +10,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Set up Go 1.20 + - name: Set up Go 1.21.0 uses: actions/setup-go@v1 with: - go-version: 1.20 + go-version: 1.21.0 id: go - name: Check out code into the Go module directory diff --git a/appveyor.yml b/appveyor.yml index d5515e5..78b79f6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.20.1 + GOVERSION: 1.21.0 # GOPATH: c:\gopath # scripts that run after cloning repository diff --git a/go.mod b/go.mod index c688876..ff7abfe 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,6 @@ module github.com/robotn/gohook go 1.17 require ( - github.com/vcaesar/keycode v0.10.0 + github.com/vcaesar/keycode v0.10.1 github.com/vcaesar/tt v0.20.0 ) diff --git a/go.sum b/go.sum index fa34337..eb3eede 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,4 @@ -github.com/vcaesar/keycode v0.10.0 h1:Qx5QE8ZXHyRyjoA2QOxBp25OKMKB+zxMVqm0FWGV0d4= -github.com/vcaesar/keycode v0.10.0/go.mod h1:JNlY7xbKsh+LAGfY2j4M3znVrGEm5W1R8s/Uv6BJcfQ= +github.com/vcaesar/keycode v0.10.1 h1:0DesGmMAPWpYTCYddOFiCMKCDKgNnwiQa2QXindVUHw= +github.com/vcaesar/keycode v0.10.1/go.mod h1:JNlY7xbKsh+LAGfY2j4M3znVrGEm5W1R8s/Uv6BJcfQ= github.com/vcaesar/tt v0.20.0 h1:9t2Ycb9RNHcP0WgQgIaRKJBB+FrRdejuaL6uWIHuoBA= github.com/vcaesar/tt v0.20.0/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg= From c94ab299da47174a07a1d2d28d42750183062a51 Mon Sep 17 00:00:00 2001 From: vcaesar Date: Fri, 23 Feb 2024 08:57:29 -0800 Subject: [PATCH 120/120] Update: bump CI to the newest --- .github/workflows/go.yml | 4 ++-- appveyor.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 91edc52..4a83c51 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,10 +10,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Set up Go 1.21.0 + - name: Set up Go 1.22.0 uses: actions/setup-go@v1 with: - go-version: 1.21.0 + go-version: 1.22.0 id: go - name: Check out code into the Go module directory diff --git a/appveyor.yml b/appveyor.yml index 78b79f6..0e05864 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% # - COMPILER: MINGW_W64 # ARCHITECTURE: x64 - GOVERSION: 1.21.0 + GOVERSION: 1.22.0 # GOPATH: c:\gopath # scripts that run after cloning repository