diff options
author | Sergey Udaltsov <svu@gnome.org> | 2003-09-19 23:36:42 +0000 |
---|---|---|
committer | Sergey Udaltsov <svu@gnome.org> | 2003-09-19 23:36:42 +0000 |
commit | 25fffbb68d26671e5e7dbd9d7f4d57f4a4ca3999 (patch) | |
tree | f8b52883028626658a13809c249c307f12c48fe1 | |
download | libxklavier-25fffbb68d26671e5e7dbd9d7f4d57f4a4ca3999.tar.gz |
Initial revision
35 files changed, 6138 insertions, 0 deletions
diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..ad62422 --- /dev/null +++ b/.cvsignore @@ -0,0 +1,27 @@ +ChangeLog +Doxyfile +Makefile +Makefile.in +aclocal.m4 +config.cache +config.guess +config.h +config.h.in +config.log +config.status +config.sub +configure +libtool +libxklavier.prj +libxklavier.pws +libxklavier.spec +ltmain.sh +stamp-h +stamp-h.in +autom4te-2.53.cache +stamp-h1 +libxklavier.pc +autom4te.cache +.tm_project.cache +stamp-h2 + @@ -0,0 +1 @@ +Sergey V. Udaltsov (svu@users.sourceforge.net) diff --git a/COPYING.LIB b/COPYING.LIB new file mode 100644 index 0000000..92b8903 --- /dev/null +++ b/COPYING.LIB @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library 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 Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey 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 library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! @@ -0,0 +1,2 @@ +Ivan Pascal <pascal@info.tsu.ru> +Andriy Rysin <arysin@myrealbox.com> diff --git a/Doxyfile.in b/Doxyfile.in new file mode 100644 index 0000000..ad44261 --- /dev/null +++ b/Doxyfile.in @@ -0,0 +1,808 @@ +# Doxyfile 1.2.8.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = @PACKAGE@ + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @VERSION@ + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Chinese, Croatian, Czech, Danish, Dutch, Finnish, French, +# German, Hungarian, Italian, Japanese, Korean, Norwegian, Polish, +# Portuguese, Romanian, Russian, Slovak, Slovene, Spanish and Swedish. + +OUTPUT_LANGUAGE = English + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these class will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. It is allowed to use relative paths in the argument list. + +STRIP_FROM_PATH = + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a class diagram (in Html and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. + +CLASS_DIAGRAMS = YES + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower case letters. If set to YES upper case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# users are adviced to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explict @brief command for a brief description. + +JAVADOC_AUTOBRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# reimplements. + +INHERIT_DOCS = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consist of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. +# For instance some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = libxklavier + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +FILE_PATTERNS = xklavier.h xklavier_config.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command <filter> <input-file>, where <filter> +# is the value of the INPUT_FILTER tag, and <input-file> is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. + +INPUT_FILTER = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse. + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the Html help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript and frames is required (for instance Netscape 4.0+ +# or Internet explorer 4.0+). + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimised for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assigments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_PREDEF_ONLY tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tagfiles. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermedate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO + +# The CGI_NAME tag should be the name of the CGI script that +# starts the search engine (doxysearch) with the correct parameters. +# A script with this name will be generated by doxygen. + +CGI_NAME = + +# The CGI_URL tag should be the absolute URL to the directory where the +# cgi binaries are located. See the documentation of your http daemon for +# details. + +CGI_URL = + +# The DOC_URL tag should be the absolute URL to the directory where the +# documentation is located. If left blank the absolute path to the +# documentation, with file:// prepended to it, will be used. + +DOC_URL = + +# The DOC_ABSPATH tag should be the absolute path to the directory where the +# documentation is located. If left blank the directory on the local machine +# will be used. + +DOC_ABSPATH = + +# The BIN_ABSPATH tag must point to the directory where the doxysearch binary +# is installed. + +BIN_ABSPATH = + +# The EXT_DOC_PATHS tag can be used to specify one or more paths to +# documentation generated for other projects. This allows doxysearch to search +# the documentation for these projects as well. + +EXT_DOC_PATHS = diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..2408119 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,17 @@ +SUBDIRS = libxklavier doc + +EXTRA_DIST = libxklavier.spec libxklavier.spec.in \ + Doxyfile Doxyfile.in autogen.sh \ + CREDITS libxklavier.pc.in + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libxklavier.pc + +doxydocs: libxklavier/xklavier.h libxklavier/xklavier_config.h + if test "x$(DO_DOXYGEN)" = "xyes" ; then \ + doxygen Doxyfile; \ + fi; + +all: doxydocs + +.PHONY: doxydocs @@ -0,0 +1 @@ +Fixes, code reogranization @@ -0,0 +1,3 @@ +libxklavier - utility library to make XKB stuff easier + +Sergey V. Udaltsov diff --git a/RELEASE_NOTES_0_3 b/RELEASE_NOTES_0_3 new file mode 100644 index 0000000..046dd61 --- /dev/null +++ b/RELEASE_NOTES_0_3 @@ -0,0 +1,25 @@ +The third development release of libxklavier XKB utility library. + +Now libxklavier is in a nearly "feature freeze"/"API freeze" state till 1.0 +release. + +Since the only using it application (gswitchit toolkit) is going to have +massive architectural changes associated with porting to GNOME 2 platform, no +major changes are planned for the nearest future. Sure, if anyone would be +interested in adding something useful... + +This version adds some extra configuration handling sugar. Now an application +can backup and restore XKB configuration using X root window's properties in a +generic way. The standard way X Window is doing this (in "_XKB_RULES_NAMES" +property only) is not generic enough. Now an application can be sure that the +property "_XKB_RULES_NAMES_BACKUP" contains the original XKB configuration, +straight from XF86Config (taking that XklBackupNamesProp is called properly). +Also, any arbitrary property can be used to keep XKB configuration - if +necessary. + +About the deadlines. I think 1.0 is not going to be released before the +XFree86 version which would incorporate multiple layouts, XML-based +configuration registry (now in xfree86_xkb_xml). Only after that great event, +some testing and user feedback, the world will hopefully see libxklavier 1.0. + +Some fixes for the latest gcc/autoconf/automake were done too. diff --git a/RELEASE_NOTES_0_4 b/RELEASE_NOTES_0_4 new file mode 100644 index 0000000..4f370f1 --- /dev/null +++ b/RELEASE_NOTES_0_4 @@ -0,0 +1,11 @@ +The forth development release of libxklavier XKB utility library. + +Libxklavier is in a nearly "feature freeze"/"API freeze" state till 1.0 +release (not earlier than XFree86 4.3.0 release). + +This maintenance release fixes one major bag (which could cause some crashes) +and one possible memory leak. Also, some code/headers reorganization was +done - all the header files include all the necessary system headers +(X11/X???.h) themselves. + +Users of libxklavier 0.3 are strongly advised to upgrade. diff --git a/RELEASE_NOTES_0_5 b/RELEASE_NOTES_0_5 new file mode 100644 index 0000000..f9dbea0 --- /dev/null +++ b/RELEASE_NOTES_0_5 @@ -0,0 +1,7 @@ +The maintenance/bugfix release of libxklavier XKB utility library. + +- Some minor API/ABI changes (sorry for new .so version). In particular, ability to use custom logging callback. + +- Usage of pkgconfig (libxklavier.pc created) + +- Little fixes and polishing (related to GSwitchIt applet reincarnation as a shared library) diff --git a/RELEASE_NOTES_0_6 b/RELEASE_NOTES_0_6 new file mode 100644 index 0000000..1caa0ba --- /dev/null +++ b/RELEASE_NOTES_0_6 @@ -0,0 +1,7 @@ +Some big big changes in API: + +- Handling of secondary groups + +- Support for XKB keyboard description patching + +- Fixes in root window's _XKB_RF_NAMES_PROP_ATOM property handling diff --git a/RELEASE_NOTES_0_7 b/RELEASE_NOTES_0_7 new file mode 100644 index 0000000..95f7f45 --- /dev/null +++ b/RELEASE_NOTES_0_7 @@ -0,0 +1,3 @@ +Major change: support for XFree 4.3.0 and multiple layouts + +Minor changes: API improvements, bug fixes diff --git a/RELEASE_NOTES_0_8 b/RELEASE_NOTES_0_8 new file mode 100644 index 0000000..6c9b0c8 --- /dev/null +++ b/RELEASE_NOTES_0_8 @@ -0,0 +1 @@ +Minor changes: secondary layout bugfixes, more versatile error processing in configuration activation diff --git a/RELEASE_NOTES_0_90 b/RELEASE_NOTES_0_90 new file mode 100644 index 0000000..0ff523e --- /dev/null +++ b/RELEASE_NOTES_0_90 @@ -0,0 +1 @@ +This release is devoted to bugfixing. Some memory problems, fixed compatibility with setxkmap, a bit of optimization (in atom handling) diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..8ab9db1 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,148 @@ +#!/bin/sh +# Run this to generate all the initial makefiles, etc. + +srcdir=`dirname $0` +PKG_NAME="libxklavier" + +DIE=0 + +(autoconf --version) < /dev/null > /dev/null 2>&1 || { + echo + echo "**Error**: You must have \`autoconf' installed to." + echo "Download the appropriate package for your distribution," + echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/" + DIE=1 +} + +(grep "^AM_PROG_LIBTOOL" $srcdir/configure.in >/dev/null) && { + (libtool --version) < /dev/null > /dev/null 2>&1 || { + echo + echo "**Error**: You must have \`libtool' installed." + echo "Get ftp://ftp.gnu.org/pub/gnu/libtool-1.2d.tar.gz" + echo "(or a newer version if it is available)" + DIE=1 + } +} + +grep "^AM_GNU_GETTEXT" $srcdir/configure.in >/dev/null && { + grep "sed.*POTFILES" $srcdir/configure.in >/dev/null || \ + (gettext --version) < /dev/null > /dev/null 2>&1 || { + echo + echo "**Error**: You must have \`gettext' installed." + echo "Get ftp://alpha.gnu.org/gnu/gettext-0.10.35.tar.gz" + echo "(or a newer version if it is available)" + DIE=1 + } +} + +grep "^AM_GNOME_GETTEXT" $srcdir/configure.in >/dev/null && { + grep "sed.*POTFILES" $srcdir/configure.in >/dev/null || \ + (gettext --version) < /dev/null > /dev/null 2>&1 || { + echo + echo "**Error**: You must have \`gettext' installed." + echo "Get ftp://alpha.gnu.org/gnu/gettext-0.10.35.tar.gz" + echo "(or a newer version if it is available)" + DIE=1 + } +} + +(automake --version) < /dev/null > /dev/null 2>&1 || { + echo + echo "**Error**: You must have \`automake' installed." + echo "Get ftp://ftp.gnu.org/pub/gnu/automake-1.3.tar.gz" + echo "(or a newer version if it is available)" + DIE=1 + NO_AUTOMAKE=yes +} + + +# if no automake, don't bother testing for aclocal +test -n "$NO_AUTOMAKE" || (aclocal --version) < /dev/null > /dev/null 2>&1 || { + echo + echo "**Error**: Missing \`aclocal'. The version of \`automake'" + echo "installed doesn't appear recent enough." + echo "Get ftp://ftp.gnu.org/pub/gnu/automake-1.3.tar.gz" + echo "(or a newer version if it is available)" + DIE=1 +} + +if test "$DIE" -eq 1; then + exit 1 +fi + +if test -z "$*"; then + echo "**Warning**: I am going to run \`configure' with no arguments." + echo "If you wish to pass any to it, please specify them on the" + echo \`$0\'" command line." + echo +fi + +case $CC in +xlc ) + am_opt=--include-deps;; +esac + +for coin in `find $srcdir -name configure.in -print` +do + dr=`dirname $coin` + if test -f $dr/NO-AUTO-GEN; then + echo skipping $dr -- flagged as no auto-gen + else + echo processing $dr + macrodirs=`sed -n -e 's,AM_ACLOCAL_INCLUDE(\(.*\)),\1,gp' < $coin` + ( cd $dr + aclocalinclude="$ACLOCAL_FLAGS" + for k in $macrodirs; do + if test -d $k; then + aclocalinclude="$aclocalinclude -I $k" + ##else + ## echo "**Warning**: No such directory \`$k'. Ignored." + fi + done + if grep "^AM_GNU_GETTEXT" configure.in >/dev/null; then + if grep "sed.*POTFILES" configure.in >/dev/null; then + : do nothing -- we still have an old unmodified configure.in + else + echo "Creating $dr/aclocal.m4 ..." + test -r $dr/aclocal.m4 || touch $dr/aclocal.m4 + echo "Running gettextize... Ignore non-fatal messages." + echo "no" | gettextize --force --copy + echo "Making $dr/aclocal.m4 writable ..." + test -r $dr/aclocal.m4 && chmod u+w $dr/aclocal.m4 + fi + fi + if grep "^AM_GNOME_GETTEXT" configure.in >/dev/null; then + echo "Creating $dr/aclocal.m4 ..." + test -r $dr/aclocal.m4 || touch $dr/aclocal.m4 + echo "Running gettextize... Ignore non-fatal messages." + echo "no" | gettextize --force --copy + echo "Making $dr/aclocal.m4 writable ..." + test -r $dr/aclocal.m4 && chmod u+w $dr/aclocal.m4 + fi + if grep "^AM_PROG_LIBTOOL" configure.in >/dev/null; then + echo "Running libtoolize..." + libtoolize --force --copy + fi + echo "Running aclocal $aclocalinclude ..." + aclocal $aclocalinclude + if grep "^AM_CONFIG_HEADER" configure.in >/dev/null; then + echo "Running autoheader..." + autoheader + fi + echo "Running automake --gnu $am_opt ..." + automake --add-missing --gnu $am_opt + echo "Running autoconf ..." + autoconf + ) + fi +done + +#conf_flags="--enable-maintainer-mode --enable-compile-warnings" #--enable-iso-c + +if test x$NOCONFIGURE = x; then + echo Running $srcdir/configure $conf_flags "$@" ... + $srcdir/configure $conf_flags "$@" \ + && echo Now type \`make\' to compile $PKG_NAME +else + echo Skipping configure process. +fi diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..ba63a0c --- /dev/null +++ b/configure.in @@ -0,0 +1,89 @@ +AC_INIT(libxklavier/xklavier.c) + +PACKAGE=libxklavier +MAJOR_VERSION=0 +MINOR_VERSION=95 +VERSION=$MAJOR_VERSION.$MINOR_VERSION +VERSION_INFO=7:0:0 + +AC_SUBST(MAJOR_VERSION) +AC_SUBST(MINOR_VERSION) +AC_SUBST(PACKAGE) +AC_SUBST(VERSION) +AC_SUBST(VERSION_INFO) + +AM_CONFIG_HEADER(config.h) + +AM_INIT_AUTOMAKE($PACKAGE, $VERSION) + +AM_MAINTAINER_MODE + +AC_PROG_CC +AC_ISC_POSIX +AC_HEADER_STDC +dnl AC_ARG_PROGRAM +AM_PROG_LIBTOOL + + +dnl From Bruno Haible. +dnl From gnoome-vfs + +AC_DEFUN([jm_LANGINFO_CODESET], +[ + AC_CHECK_HEADERS(langinfo.h) + AC_CHECK_FUNCS(nl_langinfo) + + AC_CACHE_CHECK([for nl_langinfo and CODESET], jm_cv_langinfo_codeset, + [AC_TRY_LINK([#include <langinfo.h>], + [char* cs = nl_langinfo(CODESET);], + jm_cv_langinfo_codeset=yes, + jm_cv_langinfo_codeset=no) + ]) + if test $jm_cv_langinfo_codeset = yes; then + AC_DEFINE(HAVE_LANGINFO_CODESET, 1, + [Define if you have <langinfo.h> and nl_langinfo(CODESET).]) + fi +]) + +dnl + +jm_LANGINFO_CODESET +AC_CHECK_FUNCS(setlocale) +AC_PATH_X +AC_SUBST(x_libraries) +AC_ARG_WITH( xkb_base, + [ --with-xkb-base=DIR XKB base path (by default it is /usr/X11R6/lib/X11/xkb)], + xkb_base="$withval", + xkb_base="/usr/X11R6/lib/X11/xkb" ) + +if ! test -d $xkb_base; then + AC_MSG_ERROR([The path $xkb_base does not denote the directory]) +fi + +AC_DEFINE_UNQUOTED(XKB_BASE,"${xkb_base}",Base for XKB configuration) + +AC_ARG_ENABLE(doxygen, +[ --disable-doxygen Do not build doxygen documentation], +, enable_doxygen=yes) + +AC_SUBST(DO_DOXYGEN,"${enable_doxygen}") + +dnl Checks for libraries. +PKG_CHECK_MODULES(XML, \ + libxml-2.0 >= 2.0.0) +AC_SUBST(XML_LIBS) +AC_SUBST(XML_CFLAGS) + +AC_SUBST(CFLAGS) +AC_SUBST(LDFLAGS) + +AC_OUTPUT([ +Makefile +libxklavier/Makefile +libxklavier.spec +Doxyfile +doc/Makefile +doc/html/Makefile +libxklavier.pc +]) + diff --git a/doc/.cvsignore b/doc/.cvsignore new file mode 100644 index 0000000..6179e0d --- /dev/null +++ b/doc/.cvsignore @@ -0,0 +1 @@ +Makefile Makefile.in diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..38d9a77 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=html diff --git a/doc/html/.cvsignore b/doc/html/.cvsignore new file mode 100644 index 0000000..4414e3f --- /dev/null +++ b/doc/html/.cvsignore @@ -0,0 +1 @@ +Makefile Makefile.in *.html *.css *.gif *.png diff --git a/doc/html/Makefile.am b/doc/html/Makefile.am new file mode 100644 index 0000000..dd145d2 --- /dev/null +++ b/doc/html/Makefile.am @@ -0,0 +1,58 @@ +EXTRA_WEB_FILES = doxygen.css\ + doxygen.png + +HTML_FILES = annotated.html\ + files.html\ + functions.html\ + globals.html\ + group__activation.html\ + group__callbacks.html\ + group__currents.html\ + group__debugerr.html\ + group__enum.html\ + group__lookup.html\ + group__props.html\ + group__settings.html\ + group__wininfo.html\ + group__xkbevents.html\ + group__xkbgroup.html\ + group__xkbinfo.html\ + group__xklconfig.html\ + group__xklconfiginitterm.html\ + group__xklinitterm.html\ + index.html\ + modules.html\ + struct__XklConfigItem.html\ + struct__XklConfigItem-members.html\ + struct__XklConfigRec.html\ + struct__XklConfigRec-members.html\ + structXklState.html\ + structXklState-members.html\ + xklavier_8h.html\ + xklavier_8h-source.html\ + xklavier__config_8h.html\ + xklavier__config_8h-source.html + +DOXYGEN_OUT_FILES = $(EXTRA_WEB_FILES) $(HTML_FILES) + +html_to = $(datadir)/doc/$(PACKAGE)-$(VERSION)/html + +html_files = $(DOXYGEN_OUT_FILES) + +install: + @$(NORMAL_INSTALL) + if test "x$(DO_DOXYGEN)" = "xyes"; then \ + $(mkinstalldirs) $(DESTDIR)$(html_to); \ + for p in $(html_files); do \ + $(INSTALL_DATA) $$p $(DESTDIR)$(html_to)/$$p; \ + done; \ + fi + +uninstall: + @$(NORMAL_UNINSTALL) + for p in $(html_files); do \ + rm -f $(DESTDIR)$(html_to)/$$p; \ + done + +MAINTAINERCLEANFILES = $(DOXYGEN_OUT_FILES) + diff --git a/libxklavier.pc.in b/libxklavier.pc.in new file mode 100644 index 0000000..1f9badb --- /dev/null +++ b/libxklavier.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libxklavier +Description: libxklavier library +Requires: libxml-2.0 +Version: @VERSION@ +Libs: -L${libdir} -lxklavier +Cflags: -I${includedir}/libxklavier diff --git a/libxklavier.spec.in b/libxklavier.spec.in new file mode 100644 index 0000000..4825858 --- /dev/null +++ b/libxklavier.spec.in @@ -0,0 +1,70 @@ +Name: libxklavier +Summary: libXklavier library +Version: @VERSION@ +Release: 1 +License: LGPL +Group: Development/Libraries +Url: http://gswitchit.sourceforge.net/ +BuildRequires: doxygen +Source: http://gswitchit.sourceforge.net/%{name}-%{version}.tar.gz +Buildroot: %{_tmppath}/%{name}-%{version}-%{release}-root + + +%description +This library allows you simplify XKB-related development. + +%package devel +Summary: Libraries, includes, etc to develop libxklavier applications +Group: Development/Libraries +Requires: %{name} = %{version} + +%description devel +Libraries, include files, etc you can use to develop libxklavier applications. + +%prep +%setup -q + +%build + +CONFIG_FLAGS="--prefix=%{_prefix} --libdir=%{_libdir} \ + --includedir=%{_includedir} --bindir=%{_bindir} \ + --enable-doxygen" + +if [ ! -f configure ]; then + CFLAGS="$RPM_OPT_FLAGS" ./autogen.sh $CONFIG_FLAGS +fi + +CFLAGS="$RPM_OPT_FLAGS" ./configure $CONFIG_FLAGS + +make + +%install +rm -rf $RPM_BUILD_ROOT + +DESTDIR="$RPM_BUILD_ROOT" make install + +strip $RPM_BUILD_ROOT%{_libdir}/*.so* +strip $RPM_BUILD_ROOT%{_libdir}/*.a + +%clean +rm -rf %{buildroot} +rm -rf $RPM_BUILD_DIR/%{name}-%{version} + +%post -p /sbin/ldconfig + +%postun -p /sbin/ldconfig + + +%files +%defattr(-, root, root) + +%doc AUTHORS ChangeLog NEWS README COPYING.LIB +%{_libdir}/lib*.so* + +%files devel +%defattr(-, root, root) + +%doc doc/html/*.html doc/html/*.png doc/html/*.css +%{_libdir}/pkgconfig/*.pc +%{_libdir}/*a +%{_includedir}/* diff --git a/libxklavier/.cvsignore b/libxklavier/.cvsignore new file mode 100644 index 0000000..4e38b02 --- /dev/null +++ b/libxklavier/.cvsignore @@ -0,0 +1 @@ +Makefile Makefile.in *.o *.lo .libs .deps *.la diff --git a/libxklavier/Makefile.am b/libxklavier/Makefile.am new file mode 100644 index 0000000..f905e1a --- /dev/null +++ b/libxklavier/Makefile.am @@ -0,0 +1,13 @@ +INCLUDES = -I. -I$(includedir) $(XML_CFLAGS) + +lib_LTLIBRARIES = libxklavier.la +noinst_HEADERS = xklavier_private.h + +xklavierincdir = $(includedir)/libxklavier +xklavierinc_HEADERS = xklavier.h xklavier_config.h + +libxklavier_la_SOURCES = xklavier.c xklavier_evt.c xklavier_util.c xklavier_config.c xklavier_config_i18n.c xklavier_props.c xklavier_dump.c \ + $(noinst_HEADERS) $(xklavierinc_HEADERS) +libxklavier_la_LDFLAGS = -version-info @VERSION_INFO@ $(XML_LIBS) -lxkbfile -L$(x_libraries) + + diff --git a/libxklavier/xklavier.c b/libxklavier/xklavier.c new file mode 100644 index 0000000..de0703d --- /dev/null +++ b/libxklavier/xklavier.c @@ -0,0 +1,859 @@ +#include <stdio.h> +#include <time.h> + +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#include <X11/Xlibint.h> + +#include "xklavier_private.h" + +Display *_xklDpy; + +Bool _xklXkbExtPresent; + +XkbDescPtr _xklXkb; + +XklState _xklCurState; + +Window _xklCurClient; + +Status _xklLastErrorCode; + +const char *_xklLastErrorMsg; + +XErrorHandler _xklDefaultErrHandler; + +char *_xklIndicatorNames[XkbNumIndicators]; + +Atom _xklIndicatorAtoms[XkbNumIndicators]; + +unsigned _xklPhysIndicatorsMask; + +int _xklXkbEventType, _xklXkbError; + +Atom _xklAtoms[TOTAL_ATOMS]; + +Window _xklRootWindow; + +Bool _xklAllowSecondaryGroupOnce; + +int _xklDefaultGroup; + +Bool _xklSkipOneRestore; + +int _xklSecondaryGroupsMask; + +int _xklDebugLevel = 160; + +Window _xklPrevAppWindow; + +static XklConfigCallback configCallback = NULL; +static void *configCallbackData; + +static XklStateCallback stateCallback = NULL; +static void *stateCallbackData; + +static XklWinCallback winCallback = NULL; +static void *winCallbackData; + +static XklLogAppender logAppender = XklDefaultLogAppender; + +static char *groupNames[XkbNumKbdGroups]; + +static Bool groupPerApp = True; + +static Bool handleIndicators = False; + +void XklSetIndicatorsHandling( Bool whetherHandle ) +{ + handleIndicators = whetherHandle; +} + +Bool XklGetIndicatorsHandling( void ) +{ + return handleIndicators; +} + +const char **XklGetGroupNames( void ) +{ + return ( const char ** ) groupNames; +} + +void XklSetDebugLevel( int level ) +{ + _xklDebugLevel = level; +} + +void XklSetGroupPerApp( Bool isSet ) +{ + groupPerApp = isSet; +} + +Bool XklIsGroupPerApp( void ) +{ + return groupPerApp; +} + +void XklAllowOneSwitchToSecondaryGroup( void ) +{ + XklDebug( 150, "Setting allowOneSwitchToSecondaryGroup flag\n" ); + _xklAllowSecondaryGroupOnce = True; +} + +void XklSetDefaultGroup( int group ) +{ + _xklDefaultGroup = group; +} + +int XklGetDefaultGroup( void ) +{ + return _xklDefaultGroup; +} + +void XklSetSecondaryGroupsMask( int mask ) +{ + _xklSecondaryGroupsMask = mask; +} + +int XklGetSecondaryGroupsMask( void ) +{ + return _xklSecondaryGroupsMask; +} + +int XklRegisterConfigCallback( XklConfigCallback fun, void *data ) +{ + configCallback = fun; + configCallbackData = data; + return 0; +} + +int XklRegisterStateCallback( XklStateCallback fun, void *data ) +{ + stateCallback = fun; + stateCallbackData = data; + return 0; +} + +int XklRegisterWindowCallback( XklWinCallback fun, void *data ) +{ + winCallback = fun; + winCallbackData = data; + return 0; +} + +void XklSetLogAppender( XklLogAppender fun ) +{ + logAppender = fun; +} + +int XklInit( Display * a_dpy ) +{ + int opcode; + int scr; + + _xklDefaultErrHandler = + XSetErrorHandler( ( XErrorHandler ) _XklErrHandler ); + + /* Lets begin */ + _xklXkbExtPresent = XkbQueryExtension( _xklDpy = a_dpy, + &opcode, &_xklXkbEventType, + &_xklXkbError, NULL, NULL ); + if( !_xklXkbExtPresent ) + { + return -1; + } + + scr = DefaultScreen( _xklDpy ); + _xklRootWindow = RootWindow( _xklDpy, scr ); + XklDebug( 160, + "xkbEvenType: %X, xkbError: %X, display: %p, root: " WINID_FORMAT + "\n", _xklXkbEventType, _xklXkbError, _xklDpy, _xklRootWindow ); + + _xklAtoms[WM_NAME] = XInternAtom( _xklDpy, "WM_NAME", False ); + _xklAtoms[WM_STATE] = XInternAtom( _xklDpy, "WM_STATE", False ); + _xklAtoms[XKLAVIER_STATE] = XInternAtom( _xklDpy, "XKLAVIER_STATE", False ); + _xklAtoms[XKLAVIER_TRANSPARENT] = + XInternAtom( _xklDpy, "XKLAVIER_TRANSPARENT", False ); + _xklAtoms[XKB_RF_NAMES_PROP_ATOM] = + XInternAtom( _xklDpy, _XKB_RF_NAMES_PROP_ATOM, False ); + _xklAtoms[XKB_RF_NAMES_PROP_ATOM_BACKUP] = + XInternAtom( _xklDpy, "_XKB_RULES_NAMES_BACKUP", False ); + + _xklAllowSecondaryGroupOnce = False; + _xklSkipOneRestore = False; + _xklDefaultGroup = -1; + _xklSecondaryGroupsMask = 0L; + _xklPrevAppWindow = 0; + + return _XklLoadAllInfo( )? 0 : _xklLastErrorCode; +} + +int XklStartListen( ) +{ + XklResumeListen( ); + _XklLoadWindowTree( ); + XFlush( _xklDpy ); + return 0; +} + +int XklPauseListen( ) +{ + XkbSelectEvents( _xklDpy, XkbUseCoreKbd, XkbAllEventsMask, 0 ); +// XkbSelectEventDetails( _xklDpy, + // XkbUseCoreKbd, + // XkbStateNotify, + // 0, + // 0 ); + + //!!_XklSelectInput( _xklRootWindow, 0 ); + return 0; +} + +int XklResumeListen( ) +{ + /* What events we want */ +#define XKB_EVT_MASK \ + (XkbStateNotifyMask| \ + XkbNamesNotifyMask| \ + XkbControlsNotifyMask| \ + XkbIndicatorStateNotifyMask| \ + XkbIndicatorMapNotifyMask| \ + XkbNewKeyboardNotifyMask) + + XkbSelectEvents( _xklDpy, XkbUseCoreKbd, XKB_EVT_MASK, XKB_EVT_MASK ); + +#define XKB_STATE_EVT_DTL_MASK \ + (XkbGroupStateMask) + + XkbSelectEventDetails( _xklDpy, + XkbUseCoreKbd, + XkbStateNotify, + XKB_STATE_EVT_DTL_MASK, XKB_STATE_EVT_DTL_MASK ); + +#define XKB_NAMES_EVT_DTL_MASK \ + (XkbGroupNamesMask|XkbIndicatorNamesMask) + + XkbSelectEventDetails( _xklDpy, + XkbUseCoreKbd, + XkbNamesNotify, + XKB_NAMES_EVT_DTL_MASK, XKB_NAMES_EVT_DTL_MASK ); + + _XklSelectInputMerging( _xklRootWindow, + SubstructureNotifyMask | PropertyChangeMask ); + _XklGetRealState( &_xklCurState ); + return 0; +} + +int XklStopListen( ) +{ + XklPauseListen( ); + return 0; +} + +int XklTerm( ) +{ + XSetErrorHandler( ( XErrorHandler ) _xklDefaultErrHandler ); + configCallback = NULL; + stateCallback = NULL; + winCallback = NULL; + + logAppender = XklDefaultLogAppender; + _XklFreeAllInfo( ); + + return 0; +} + +Bool XklGrabKey( int key, unsigned modifiers ) +{ + int keyCode; + Bool retCode; + char *keyName; + + keyCode = XKeysymToKeycode( _xklDpy, key ); + keyName = XKeysymToString( key ); + + XklDebug( 100, "listen to the key %X(%d/%s)/%d\n", key, keyCode, keyName, + modifiers ); + + if( ( KeyCode ) NULL == keyCode ) + return False; + + _xklLastErrorCode = Success; + + retCode = XGrabKey( _xklDpy, keyCode, modifiers, _xklRootWindow, + True, GrabModeAsync, GrabModeAsync ); + XSync( _xklDpy, False ); + + XklDebug( 100, "trying to listen: %d/%d\n", retCode, _xklLastErrorCode ); + + retCode = ( _xklLastErrorCode == Success ); + + if( !retCode ) + _xklLastErrorMsg = "Could not grab the key"; + + return retCode; +} + +Bool XklUngrabKey( int key, unsigned modifiers ) +{ + int keyCode; + + keyCode = XKeysymToKeycode( _xklDpy, key ); + + if( ( KeyCode ) NULL == keyCode ) + return False; + + return Success == XUngrabKey( _xklDpy, keyCode, 0, _xklRootWindow ); +} + +unsigned XklGetNumGroups( ) +{ + return _xklXkb->ctrls->num_groups; +} + +int XklGetNextGroup( ) +{ + return ( _xklCurState.group + 1 ) % _xklXkb->ctrls->num_groups; +} + +int XklGetPrevGroup( ) +{ + int n = _xklXkb->ctrls->num_groups; + return ( _xklCurState.group + n - 1 ) % n; +} + +int XklGetRestoreGroup( ) +{ + XklState state; + if( _xklCurClient == ( Window ) NULL ) + { + XklDebug( 150, "cannot restore without current client\n" ); + } else if( XklGetState( _xklCurClient, &state ) ) + { + return state.group; + } else + XklDebug( 150, + "Unbelievable: current client " WINID_FORMAT + ", '%s' has no group\n", _xklCurClient, + _XklGetDebugWindowTitle( _xklCurClient ) ); + return 0; +} + +void XklSetTransparent( Window win, Bool transparent ) +{ + Window appWin; + Bool wasTransparent; + XklDebug( 150, "setting transparent flag %d for " WINID_FORMAT "\n", + transparent, win ); + + if( !_XklGetAppWindow( win, &appWin ) ) + { + XklDebug( 150, "No app window!\n" ); + appWin = win; +// return; + } + + wasTransparent = XklIsTransparent( appWin ); + XklDebug( 150, "appwin " WINID_FORMAT " was %stransparent\n", appWin, + wasTransparent ? "" : "not " ); + if( transparent && !wasTransparent ) + { + CARD32 prop = 1; + XChangeProperty( _xklDpy, appWin, _xklAtoms[XKLAVIER_TRANSPARENT], + XA_INTEGER, 32, PropModeReplace, + ( const unsigned char * ) &prop, 1 ); + } else if( !transparent && wasTransparent ) + { + XDeleteProperty( _xklDpy, appWin, _xklAtoms[XKLAVIER_TRANSPARENT] ); + } +} + +Bool XklIsTransparent( Window win ) +{ + Window appWin; + + if( !_XklGetAppWindow( win, &appWin ) ) + return False; + return _XklIsTransparentAppWindow( appWin ); + +} + +/** + * "Adds" app window to the set of managed windows. + * Actually, no data structures involved. The only thing we do is save app state + * and register ourselves us listeners. + * Note: User's callback is called + */ +void _XklAddAppWindow( Window appWin, Window parent, Bool ignoreExistingState, + XklState * initState ) +{ + XklState state = *initState; + int defGroupToUse = -1; + + if( appWin == _xklRootWindow ) + XklDebug( 150, "??? root app win ???\n" ); + + XklDebug( 150, "Trying to add window " WINID_FORMAT "/%s with group %d\n", + appWin, _XklGetDebugWindowTitle( appWin ), initState->group ); + + if( !ignoreExistingState ) + { + Bool have_state = _XklGetAppState( appWin, &state ); + + if( have_state ) + { + XklDebug( 150, + "The window " WINID_FORMAT + " does not require to be added, it already has the xklavier state \n", + appWin ); + return; + } + } + + if( winCallback != NULL ) + defGroupToUse = ( *winCallback ) ( appWin, parent, winCallbackData ); + + if( defGroupToUse == -1 ) + defGroupToUse = _xklDefaultGroup; + + if( defGroupToUse != -1 ) + state.group = defGroupToUse; + + _XklSaveAppState( appWin, &state ); + _XklSelectInputMerging( appWin, FocusChangeMask | PropertyChangeMask ); + + if( defGroupToUse != -1 ) + { + if( _xklCurClient == appWin ) + { + if( ( _xklSecondaryGroupsMask & ( 1 << defGroupToUse ) ) != 0 ) + XklAllowOneSwitchToSecondaryGroup( ); + XklLockGroup( defGroupToUse ); + } + } + + if( parent == ( Window ) NULL ) + parent = _XklGetRegisteredParent( appWin ); + + XklDebug( 150, "done\n" ); +} + +/** + * Checks the window and goes up + */ +Bool _XklGetAppWindowBottomToTop( Window win, Window * appWin_return ) +{ + Window parent = ( Window ) NULL, rwin = ( Window ) NULL, *children = NULL; + int num = 0; + + if( win == ( Window ) NULL || win == _xklRootWindow ) + { + *appWin_return = win; + _xklLastErrorMsg = "The window is either 0 or root"; + return False; + } + + if( _XklHasWmState( win ) ) + { + *appWin_return = win; + return True; + } + + _xklLastErrorCode = + _XklStatusQueryTree( _xklDpy, win, &rwin, &parent, &children, &num ); + + if( _xklLastErrorCode != Success ) + { + *appWin_return = ( Window ) NULL; + return False; + } + + if( children != NULL ) + XFree( children ); + + return _XklGetAppWindowBottomToTop( parent, appWin_return ); +} + +/** + * Recursively finds "App window" (window with WM_STATE) for given window. + * First, checks the window itself + * Then, for first level of recursion, checks childen, + * Then, goes to parent. + * NOTE: root window cannot be "App window" under normal circumstances + */ +Bool _XklGetAppWindow( Window win, Window * appWin_return ) +{ + Window parent = ( Window ) NULL, + rwin = ( Window ) NULL, *children = NULL, *child; + int num = 0; + Bool rv; + + if( win == ( Window ) NULL || win == _xklRootWindow ) + { + *appWin_return = ( Window ) NULL; + _xklLastErrorMsg = "The window is either 0 or root"; + XklDebug( 150, + "Window " WINID_FORMAT + " is either 0 or root so could not get the app window for it\n", + win ); + return False; + } + + if( _XklHasWmState( win ) ) + { + *appWin_return = win; + return True; + } + + _xklLastErrorCode = + _XklStatusQueryTree( _xklDpy, win, &rwin, &parent, &children, &num ); + + if( _xklLastErrorCode != Success ) + { + *appWin_return = ( Window ) NULL; + XklDebug( 150, + "Could not get tree for window " WINID_FORMAT + " so could not get the app window for it\n", win ); + return False; + } + + /** + * Here we first check the children (in case win is just above some "App Window") + * and then go upstairs + */ + child = children; + while( num ) + { + if( _XklHasWmState( *child ) ) + { + *appWin_return = *child; + if( children != NULL ) + XFree( children ); + return True; + } + child++; + num--; + } + + if( children != NULL ) + XFree( children ); + + rv = _XklGetAppWindowBottomToTop( parent, appWin_return ); + + if( !rv ) + XklDebug( 200, "Could not get the app window for " WINID_FORMAT "/%s\n", + win, _XklGetDebugWindowTitle( win ) ); + + return rv; +} + +/** + * Loads the tree recursively. + */ +Bool _XklLoadWindowTree( ) +{ + Window focused; + int revert; + Bool retval, haveAppWindow; + + retval = _XklLoadSubtree( _xklRootWindow, 0, &_xklCurState ); + + XGetInputFocus( _xklDpy, &focused, &revert ); + + XklDebug( 160, "initially focused: " WINID_FORMAT ", '%s'\n", focused ); + + haveAppWindow = _XklGetAppWindow( focused, &_xklCurClient ); + + if( haveAppWindow ) + { + Bool haveState = _XklGetAppState( _xklCurClient, &_xklCurState ); + XklDebug( 160, + "initial _xklCurClient: " WINID_FORMAT + ", '%s' %s state %d/%X\n", _xklCurClient, + _XklGetDebugWindowTitle( _xklCurClient ), + ( haveState ? "with" : "without" ), + ( haveState ? _xklCurState.group : -1 ), + ( haveState ? _xklCurState.indicators : -1 ) ); + } else + { + XklDebug( 160, + "could not find initial app. Probably, focus belongs to some WM service window. Will try to survive:)" ); + } + + return retval; +} + +#define KBD_MASK \ + ( 0 ) +#define CTRLS_MASK \ + ( XkbSlowKeysMask ) +#define NAMES_MASK \ + ( XkbGroupNamesMask | XkbIndicatorNamesMask ) + +void _XklFreeAllInfo( ) +{ + if( _xklXkb != NULL ) + { + int i; + char **groupName = groupNames; + for( i = _xklXkb->ctrls->num_groups; --i >= 0; groupName++ ) + if( *groupName ) + XFree( *groupName ); + XkbFreeKeyboard( _xklXkb, XkbAllComponentsMask, True ); + _xklXkb = NULL; + } +} + +/** + * Load some XKB parameters + */ +Bool _XklLoadAllInfo( ) +{ + int i; + unsigned bit; + Atom *gna; + char **groupName; + + _xklXkb = XkbGetMap( _xklDpy, KBD_MASK, XkbUseCoreKbd ); + if( _xklXkb == NULL ) + { + _xklLastErrorMsg = "Could not load keyboard"; + return False; + } + + _xklLastErrorCode = XkbGetControls( _xklDpy, CTRLS_MASK, _xklXkb ); + + if( _xklLastErrorCode != Success ) + { + _xklLastErrorMsg = "Could not load controls"; + return False; + } + + XklDebug( 200, "found %d groups\n", _xklXkb->ctrls->num_groups ); + + _xklLastErrorCode = XkbGetNames( _xklDpy, NAMES_MASK, _xklXkb ); + + if( _xklLastErrorCode != Success ) + { + _xklLastErrorMsg = "Could not load names"; + return False; + } + + gna = _xklXkb->names->groups; + groupName = groupNames; + for( i = _xklXkb->ctrls->num_groups; --i >= 0; gna++, groupName++ ) + { + *groupName = XGetAtomName( _xklDpy, + *gna == None ? + XInternAtom( _xklDpy, "-", False ) : *gna ); + XklDebug( 200, "group %d has name [%s]\n", i, *groupName ); + } + + _xklLastErrorCode = + XkbGetIndicatorMap( _xklDpy, XkbAllIndicatorsMask, _xklXkb ); + + if( _xklLastErrorCode != Success ) + { + _xklLastErrorMsg = "Could not load indicator map"; + return False; + } + + for( i = 0, bit = 1; i < XkbNumIndicators; i++, bit <<= 1 ) + { + Atom a = _xklXkb->names->indicators[i]; + if( a != None ) + _xklIndicatorNames[i] = XGetAtomName( _xklDpy, a ); + else + _xklIndicatorNames[i] = ""; + + XklDebug( 200, "Indicator[%d] is %s\n", i, _xklIndicatorNames[i] ); + } + + XklDebug( 200, "Real indicators are %X\n", + _xklXkb->indicators->phys_indicators ); + + if( configCallback != NULL ) + ( *configCallback ) ( configCallbackData ); + + return True; +} + +void _XklDebug( const char file[], const char function[], int level, + const char format[], ... ) +{ + va_list lst; + + if( level > _xklDebugLevel ) + return; + + va_start( lst, format ); + if( logAppender != NULL ) + ( *logAppender ) ( file, function, level, format, lst ); + va_end( lst ); +} + +void XklDefaultLogAppender( const char file[], const char function[], + int level, const char format[], va_list args ) +{ + time_t now = time( NULL ); + fprintf( stdout, "[%08ld,%03d,%s:%s/] \t", now, level, file, function ); + vfprintf( stdout, format, args ); +} + +#define PROP_LENGTH 2 + +/** + * Gets the state from the window property + */ +Bool _XklGetAppState( Window appWin, XklState * state_return ) +{ + Atom type_ret; + int format_ret; + unsigned long nitems, rest; + CARD32 *prop = NULL; + Bool ret = False; + + int grp = -1; + unsigned inds = -1; + + if( ( XGetWindowProperty + ( _xklDpy, appWin, _xklAtoms[XKLAVIER_STATE], 0L, PROP_LENGTH, False, + XA_INTEGER, &type_ret, &format_ret, &nitems, &rest, + ( unsigned char ** ) &prop ) == Success ) + && ( type_ret == XA_INTEGER ) && ( format_ret == 32 ) ) + { + grp = prop[0]; + if( grp >= _xklXkb->ctrls->num_groups || grp < 0 ) + grp = 0; + + inds = prop[1]; + + if( state_return != NULL ) + { + state_return->group = grp; + state_return->indicators = inds; + } + if( prop != NULL ) + XFree( prop ); + + ret = True; + } + + if( ret ) + XklDebug( 150, + "Appwin " WINID_FORMAT + ", '%s' has the group %d, indicators %X\n", appWin, + _XklGetDebugWindowTitle( appWin ), grp, inds ); + else + XklDebug( 150, "Appwin " WINID_FORMAT ", '%s' does not have state\n", + appWin, _XklGetDebugWindowTitle( appWin ) ); + + return ret; +} + +/** + * Deletes the state from the window properties + */ +void _XklDelAppState( Window appWin ) +{ + XDeleteProperty( _xklDpy, appWin, _xklAtoms[XKLAVIER_STATE] ); +} + +/** + * Saves the state into the window properties + */ +void _XklSaveAppState( Window appWin, XklState * state ) +{ + CARD32 prop[PROP_LENGTH]; + + prop[0] = state->group; + prop[1] = state->indicators; + + XChangeProperty( _xklDpy, appWin, _xklAtoms[XKLAVIER_STATE], XA_INTEGER, + 32, PropModeReplace, ( const unsigned char * ) prop, + PROP_LENGTH ); + + XklDebug( 160, + "Saved the group %d, indicators %X for appwin " WINID_FORMAT "\n", + state->group, state->indicators, appWin ); +} + +/** + * Just selects some events from the window. + */ +void _XklSelectInput( Window win, long mask ) +{ + if( _xklRootWindow == win ) + XklDebug( 160, + "Someone is looking for " WINID_FORMAT " on root window ***\n", + mask ); + + XSelectInput( _xklDpy, win, mask ); +} + +void _XklSelectInputMerging( Window win, long mask ) +{ + XWindowAttributes attrs; + long oldmask = 0L, newmask; + memset( &attrs, 0, sizeof( attrs ) ); + if( XGetWindowAttributes( _xklDpy, win, &attrs ) ) + oldmask = attrs.your_event_mask; + + newmask = oldmask | mask; + if( newmask != oldmask ) + _XklSelectInput( win, newmask ); +} + +void _XklTryCallStateCallback( XklStateChange changeType, + XklState * oldState ) +{ + int group = _xklCurState.group; + Bool restore = oldState->group == group; + + XklDebug( 150, + "changeType: %d, group: %d, secondaryGroupMask: %X, allowsecondary: %d\n", + changeType, group, _xklSecondaryGroupsMask, + _xklAllowSecondaryGroupOnce ); + + if( changeType == GROUP_CHANGED ) + { + if( !restore ) + { + if( ( _xklSecondaryGroupsMask & ( 1 << group ) ) != 0 && + !_xklAllowSecondaryGroupOnce ) + { + XklDebug( 150, "secondary -> go next\n" ); + group = XklGetNextGroup( ); + XklLockGroup( group ); + return; // we do not need to revalidate + } + } + _xklAllowSecondaryGroupOnce = False; + } + if( stateCallback != NULL ) + { + + ( *stateCallback ) ( changeType, _xklCurState.group, + restore, stateCallbackData ); + } +} + +Bool _XklIsTransparentAppWindow( Window appWin ) +{ + Atom type_ret; + int format_ret; + unsigned long nitems, rest; + CARD32 *prop = NULL; + Bool retVal; + if( ( XGetWindowProperty + ( _xklDpy, appWin, _xklAtoms[XKLAVIER_TRANSPARENT], 0L, 1, False, + XA_INTEGER, &type_ret, &format_ret, &nitems, &rest, + ( unsigned char ** ) &prop ) == Success ) + && ( type_ret == XA_INTEGER ) && ( format_ret == 32 ) ) + { + if( prop != NULL ) + XFree( prop ); + return True; + } + return False; +} diff --git a/libxklavier/xklavier.h b/libxklavier/xklavier.h new file mode 100644 index 0000000..51fb087 --- /dev/null +++ b/libxklavier/xklavier.h @@ -0,0 +1,469 @@ +/** + * @file xklavier.h + */ + +#ifndef __XKLAVIER_H__ +#define __XKLAVIER_H__ + +#include <stdarg.h> + +#include <X11/Xlib.h> +#include <X11/XKBlib.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef enum + { +/** + * Group was changed + */ + GROUP_CHANGED, +/** + * Indicators were changed + */ + INDICATORS_CHANGED + } + XklStateChange; + +/** + * XKB state. Can be global or per-window + */ + typedef struct + { +/** selected group */ + int group; +/** set of active indicators */ + unsigned indicators; + } + XklState; + +/** + * @defgroup xklinitterm Library initialization and termination + * @{ + */ + +/** + * Initializes internal structures. Does not start actual listening though. + * Some apps can use Xklavier for information retrieval but not for actual + * processing. + * @param dpy is an open display, will be tested for XKB extension + * @return 0 if OK, otherwise last X error + * (special case: -1 if XKB extension is not present) + */ + extern int XklInit( Display * dpy ); + +/** + * Terminates everything... + */ + extern int XklTerm( void ); + +/** @} */ + +/** + * @defgroup xkbevents XKB event handling and management + * @{ + */ + +/** + * Starts listening for XKB-related events + * @return 0 + */ + extern int XklStartListen( void ); + +/** + * Stops listening for XKB-related events + * @return 0 + */ + extern int XklStopListen( void ); + +/** + * Temporary pauses listening for XKB-related events + * @return 0 + */ + extern int XklPauseListen( void ); + +/** + * Resumes listening for XKB-related events + * @return 0 + */ + extern int XklResumeListen( void ); + +/** + * Grabs some key + * @param key is a keysym + * @param modifiers is a bitmask of modifiers + * @return True on success + */ + extern Bool XklGrabKey( int key, unsigned modifiers ); + +/** + * Ungrabs some key + * @param key is a keysym + * @param modifiers is a bitmask of modifiers + * @return True on success + */ + extern Bool XklUngrabKey( int key, unsigned modifiers ); + +/** + * Processes X events. Should be included into the main event cycle of an + * application. One of the most important functions. + * @param evt is delivered X event + * @return 0 if the event it processed - 1 otherwise + * @see XklStartListen + */ + extern int XklFilterEvents( XEvent * evt ); + +/** + * Allows to switch (once) to the secondary group + */ + extern void XklAllowOneSwitchToSecondaryGroup( void ); + +/** @} */ + +/** + * @defgroup currents Current state of the library + * @{ + */ + +/** + * @return currently focused window + */ + extern Window XklGetCurrentWindow( void ); + +/** + * @return current state of the keyboard (in XKB terms). + * Returned value is a statically allocated buffer, should not be freed. + */ + extern XklState *XklGetCurrentState( void ); + +/** @} */ + +/** + * @defgroup wininfo Per-window information + * @{ + */ + +/** + * @return the window title of some window or NULL. + * If not NULL, it should be freed with XFree + */ + extern char *XklGetWindowTitle( Window w ); + +/** + * Finds the state for a given window (for its "App window"). + * @param win is a target window + * @param state_return is a structure to store the state + * @return True on success, otherwise False + * (the error message can be obtained using XklGetLastError). + */ + extern Bool XklGetState( Window win, XklState * state_return ); + +/** + * Drops the state of a given window (of its "App window"). + * @param win is a target window + */ + extern void XklDelState( Window win ); + +/** + * Stores ths state for a given window + * @param win is a target window + * @param state is a new state of the window + */ + extern void XklSaveState( Window win, XklState * state ); + +/** + * Sets the "transparent" flag. It means focus switching onto + * this window will never change the state. + * @param win is the window do set the flag for. + * @param transparent - if true, the windows is transparent. + * @see XklIsTranspatent + */ + extern void XklSetTransparent( Window win, Bool transparent ); + +/** + * Returns "transparent" flag. + * @param win is the window to get the transparent flag from. + * @see XklSetTranspatent + */ + extern Bool XklIsTransparent( Window win ); + +/** + * Checks whether 2 windows have the same App Window + * @param win1 is first window + * @param win2 is second window + * @return True is windows are in the same application + */ + extern Bool XklIsSameApp( Window win1, Window win2 ); + +/** @} */ + +/** + * @defgroup xkbinfo Various XKB configuration info + * @{ + */ + +/** + * @return the total number of groups in the current XKB configuration + * (keyboard) + */ + extern unsigned XklGetNumGroups( void ); + +/** + * @return the array of group names for the current XKB configuration + * (keyboard). + * This array is static, should not be freed + */ + extern const char **XklGetGroupNames( void ); + +/** + * @return the array of indicator names for the current XKB configuration + * (keyboard). + * This array is static, should not be freed + */ + extern const char **XklGetIndicatorNames( void ); + +/** @} */ + +/** + * @defgroup xkbgroup XKB group calculation and change + * @{ + */ + +/** + * Calculates next group id. Does not change the state of anything. + * @return next group id + */ + extern int XklGetNextGroup( void ); + +/** + * Calculates prev group id. Does not change the state of anything. + * @return prev group id + */ + extern int XklGetPrevGroup( void ); + +/** + * @return saved group id of the current client. + * Does not change the state of anything. + */ + extern int XklGetRestoreGroup( void ); + +/** + * Locks the group. Can be used after XklGetXXXGroup functions + * @param group is a group number for locking + * @see XklGetNextGroup + * @see XklGetPrevGroup + * @see XklGetRestoreGroup + */ + extern void XklLockGroup( int group ); + +/** @} */ + +/** + * @defgroup callbacks Application callbacks support + * @{ + */ + +/** + * Used for notifying application of the XKB configuration change. + * @param userData is anything which can be stored into the pointer + * @see XklRegisterConfigCallback + */ + typedef void ( *XklConfigCallback ) ( void *userData ); + +/** + * Registers user callback. Only one callback can be registered at a time + * @param fun is the function to call + * @param userData is the data to pass + * @see XklConfigCallback + */ + extern int XklRegisterConfigCallback( XklConfigCallback fun, + void *userData ); + +/** + * Used for notifying application of new window creation (actually, + * registration). + * @param win is a new window + * @param parent is a new window's parent + * @param userData is anything which can be stored into the pointer + * @return the initial group id for the window (-1 to use the default value) + * @see XklRegisterConfigCallback + * @see XklSetDefaultGroup + * @see XklGetDefaultGroup + */ + typedef int ( *XklWinCallback ) ( Window win, Window parent, + void *userData ); + +/** + * Registers user callback. Only one callback can be registered at a time + * @param fun is the function to call + * @param userData is the data to pass + * @see XklWindowCallback + */ + extern int XklRegisterWindowCallback( XklWinCallback fun, void *userData ); + +/** + * Used for notifying application of the window state change. + * @param changeType is a mask of changes + * @param group is a new group + * @param restore is indicator of whether this state is restored from + * saved state of set as new. + * @param userData is anything which can be stored into the pointer + * @see XklRegisterConfigCallback + */ + typedef void ( *XklStateCallback ) ( XklStateChange changeType, int group, + Bool restore, void *userData ); + +/** + * Registers user callback. Only one callback can be registered at a time + * @param fun is the function to call + * @param userData is the data to pass + * @see XklStateCallback + */ + extern int XklRegisterStateCallback( XklStateCallback fun, void *userData ); + +/** @} */ + +/** + * @defgroup settings Settings for event processing + * @{ + */ + +/** + * Sets the configuration parameter: group per application + * @param isGlobal is a new parameter value + */ + extern void XklSetGroupPerApp( Bool isGlobal ); + +/** + * @return the value of the parameter: group per application + */ + extern Bool XklIsGroupPerApp( void ); + +/** + * Sets the configuration parameter: perform indicators handling + * @param whetherHandle is a new parameter value + */ + extern void XklSetIndicatorsHandling( Bool whetherHandle ); + +/** + * @return the value of the parameter: perform indicator handling + */ + extern Bool XklGetIndicatorsHandling( void ); + +/** + * Sets the secondary groups (one bit per group). + * Secondary groups require explicit "allowance" for switching + * @param mask is a new group mask + * @see XklAllowOneSwitchToSecondaryGroup + */ + extern void XklSetSecondaryGroupsMask( int mask ); + +/** + * @return the secondary group mask + */ + extern int XklGetSecondaryGroupsMask( void ); + +/** + * Configures the default group set on window creation. + * If -1, no default group is used + * @param group the default group + */ + extern void XklSetDefaultGroup( int group ); + +/** + * Returns the default group set on window creation + * If -1, no default group is used + * @return the default group + */ + extern int XklGetDefaultGroup( void ); + +/** @} */ + +/** + * @defgroup debugerr Debugging, error processing + * @{ + */ + +/** + * @return the text message (statically allocated) of the last error + */ + extern const char *XklGetLastError( void ); + +/** + * Output (optionally) some debug info + * @param file is the name of the source file. + * Preprocessor symbol__FILE__ should be used here + * @param function is a name of the function + * Preprocessor symbol__func__ should be used here + * @param level is a level of the message + * @param format is a format (like in printf) + * @see XklDebug + */ + extern void _XklDebug( const char file[], const char function[], int level, + const char format[], ... ); + +/** + * Custom log output method for _XklDebug. This appender is NOT called if the + * level of the message is greater than currently set debug level. + * + * @param file is the name of the source file. + * Preprocessor symbol__FILE__ should be used here + * @param function is a name of the function + * Preprocessor symbol__func__ should be used here + * @param level is a level of the message + * @param format is a format (like in printf) + * @param args is the list of parameters + * @see _XklDebug + * @see XklSetDebugLevel + */ + typedef void ( *XklLogAppender ) ( const char file[], const char function[], + int level, const char format[], + va_list args ); + +/** + * Default log output method. Sends everything to stdout. + * + * @param file is the name of the source file. + * Preprocessor symbol__FILE__ should be used here + * @param function is a name of the function + * Preprocessor symbol__func__ should be used here + * @param level is a level of the message + * @param format is a format (like in printf) + * @param args is the list of parameters + */ + extern void XklDefaultLogAppender( const char file[], const char function[], + int level, const char format[], + va_list args ); + +/** + * Installs the custom log appender.function + * @param fun is the new log appender + */ + extern void XklSetLogAppender( XklLogAppender fun ); + +/** + * Sets maximum debug level. + * Message of the level more than the one set here - will be ignored + * @param level is a new debug level + */ + extern void XklSetDebugLevel( int level ); + +/** + * Output (optionally) some debug info + * @param level is a level of the message + * @param format is a format (like in printf) + * @see _XklDebug + */ +#define XklDebug( level, format, args... ) \ + _XklDebug( __FILE__, __func__, level, format, ## args ) + +/** @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/libxklavier/xklavier_config.c b/libxklavier/xklavier_config.c new file mode 100644 index 0000000..9c705e3 --- /dev/null +++ b/libxklavier/xklavier_config.c @@ -0,0 +1,708 @@ +#include <errno.h> +#include <string.h> +#include <locale.h> +#include <sys/stat.h> + +#include <libxml/xpath.h> + +#include "config.h" + +#include "xklavier_private.h" + +#include <X11/extensions/XKBfile.h> +#include <X11/extensions/XKM.h> + +#define RULES_FILE "xfree86" + +#define RULES_PATH ( XKB_BASE "/rules/" RULES_FILE ) + +#define XML_CFG_PATH ( XKB_BASE "/rules/xfree86.xml" ) + +#define MULTIPLE_LAYOUTS_CHECK_PATH ( XKB_BASE "/symbols/pc/en_US" ) + +#define XK_XKB_KEYS +#include <X11/keysymdef.h> + +typedef struct _XklConfigRegistry +{ + xmlDocPtr doc; + xmlXPathContextPtr xpathContext; +} +XklConfigRegistry; + +XkbRF_VarDefsRec _xklVarDefs; + +static XklConfigRegistry theRegistry; + +static xmlXPathCompExprPtr modelsXPath; +static xmlXPathCompExprPtr layoutsXPath; +static xmlXPathCompExprPtr optionGroupsXPath; + +static XkbRF_RulesPtr rules; +static XkbComponentNamesRec componentNames; +static char *locale; + +static Bool _XklConfigInitialized( ) +{ + return theRegistry.xpathContext != NULL; +} + +static xmlChar *_XklNodeGetXmlLangAttr( xmlNodePtr nptr ) +{ + if( nptr->properties != NULL && + !strcmp( "lang", nptr->properties[0].name ) && + nptr->properties[0].ns != NULL && + !strcmp( "xml", nptr->properties[0].ns->prefix ) && + nptr->properties[0].children != NULL ) + return nptr->properties[0].children->content; + else + return NULL; +} + +static Bool _XklReadConfigItem( xmlNodePtr iptr, XklConfigItemPtr pci ) +{ + xmlNodePtr nameElement, descElement = NULL, ntDescElement = + NULL, nptr, ptr, shortDescElement = NULL, ntShortDescElement = NULL; + int maxDescPriority = -1; + int maxShortDescPriority = -1; + + *pci->name = 0; + *pci->shortDescription = 0; + *pci->description = 0; + if( iptr->type != XML_ELEMENT_NODE ) + return False; + ptr = iptr->children; + while( ptr != NULL ) + { + switch ( ptr->type ) + { + case XML_ELEMENT_NODE: + if( !strcmp( ptr->name, "configItem" ) ) + break; + return False; + case XML_TEXT_NODE: + ptr = ptr->next; + continue; + default: + return False; + } + break; + } + if( ptr == NULL ) + return False; + + nptr = ptr->children; + + if( nptr->type == XML_TEXT_NODE ) + nptr = nptr->next; + nameElement = nptr; + nptr = nptr->next; + + while( nptr != NULL ) + { + if( nptr->type != XML_TEXT_NODE ) + { + xmlChar *lang = _XklNodeGetXmlLangAttr( nptr ); + + if( lang != NULL ) + { + int priority = _XklGetLanguagePriority( lang ); + if( !strcmp( nptr->name, "description" ) && ( priority > maxDescPriority ) ) // higher priority + { + descElement = nptr; + maxDescPriority = priority; + } else if( !strcmp( nptr->name, "shortDescription" ) && ( priority > maxShortDescPriority ) ) // higher priority + { + shortDescElement = nptr; + maxShortDescPriority = priority; + } + } else + { + if( !strcmp( nptr->name, "description" ) ) + ntDescElement = nptr; + else if( !strcmp( nptr->name, "shortDescription" ) ) + ntShortDescElement = nptr; + } + } + nptr = nptr->next; + } + + // if no language-specific description found - use the ones without lang + if( descElement == NULL ) + descElement = ntDescElement; + + if( shortDescElement == NULL ) + shortDescElement = ntShortDescElement; + + // + // Actually, here we should have some code to find the correct localized description... + // + + if( nameElement != NULL && nameElement->children != NULL ) + strncat( pci->name, nameElement->children->content, + XKL_MAX_CI_NAME_LENGTH - 1 ); + + if( shortDescElement != NULL && shortDescElement->children != NULL ) + strncat( pci->shortDescription, + _XklLocaleFromUtf8( shortDescElement->children->content ), + XKL_MAX_CI_SHORT_DESC_LENGTH - 1 ); + + if( descElement != NULL && descElement->children != NULL ) + strncat( pci->description, + _XklLocaleFromUtf8( descElement->children->content ), + XKL_MAX_CI_DESC_LENGTH - 1 ); + return True; +} + +static void _XklConfigEnumFromNodeSet( xmlNodeSetPtr nodes, + ConfigItemProcessFunc func, + void *userData ) +{ + int i; + if( nodes != NULL ) + { + xmlNodePtr *theNodePtr = nodes->nodeTab; + for( i = nodes->nodeNr; --i >= 0; ) + { + XklConfigItem ci; + if( _XklReadConfigItem( *theNodePtr, &ci ) ) + func( &ci, userData ); + + theNodePtr++; + } + } +} + +static void _XklConfigEnumSimple( xmlXPathCompExprPtr xpathCompExpr, + ConfigItemProcessFunc func, void *userData ) +{ + xmlXPathObjectPtr xpathObj; + + if( !_XklConfigInitialized( ) ) + return; + xpathObj = xmlXPathCompiledEval( xpathCompExpr, theRegistry.xpathContext ); + if( xpathObj != NULL ) + { + _XklConfigEnumFromNodeSet( xpathObj->nodesetval, func, userData ); + xmlXPathFreeObject( xpathObj ); + } +} + +static void _XklConfigEnumDirect( const char *format, + const char *value, + ConfigItemProcessFunc func, void *userData ) +{ + char xpathExpr[1024]; + xmlXPathObjectPtr xpathObj; + + if( !_XklConfigInitialized( ) ) + return; + snprintf( xpathExpr, sizeof xpathExpr, format, value ); + xpathObj = xmlXPathEval( xpathExpr, theRegistry.xpathContext ); + if( xpathObj != NULL ) + { + _XklConfigEnumFromNodeSet( xpathObj->nodesetval, func, userData ); + xmlXPathFreeObject( xpathObj ); + } +} + +static Bool _XklConfigFindObject( const char *format, + const char *arg1, + XklConfigItemPtr ptr /* in/out */ , + xmlNodePtr * nodePtr /* out */ ) +{ + xmlXPathObjectPtr xpathObj; + xmlNodeSetPtr nodes; + Bool rv = False; + char xpathExpr[1024]; + + if( !_XklConfigInitialized( ) ) + return False; + + snprintf( xpathExpr, sizeof xpathExpr, format, arg1, ptr->name ); + xpathObj = xmlXPathEval( xpathExpr, theRegistry.xpathContext ); + if( xpathObj == NULL ) + return False; + + nodes = xpathObj->nodesetval; + if( nodes != NULL && nodes->nodeTab != NULL ) + { + rv = _XklReadConfigItem( *nodes->nodeTab, ptr ); + if( nodePtr != NULL ) + { + *nodePtr = *nodes->nodeTab; + } + } + + xmlXPathFreeObject( xpathObj ); + return rv; +} + +char *_XklConfigRecMergeLayouts( const XklConfigRecPtr data ) +{ + return _XklConfigRecMergeByComma( ( const char ** ) data->layouts, + data->numLayouts ); +} + +char *_XklConfigRecMergeVariants( const XklConfigRecPtr data ) +{ + return _XklConfigRecMergeByComma( ( const char ** ) data->variants, + data->numVariants ); +} + +char *_XklConfigRecMergeOptions( const XklConfigRecPtr data ) +{ + return _XklConfigRecMergeByComma( ( const char ** ) data->options, + data->numOptions ); +} + +char *_XklConfigRecMergeByComma( const char **array, const int arrayLength ) +{ + int len = 0; + int i; + char *merged; + const char **theString; + + if( ( theString = array ) == NULL ) + return NULL; + + for( i = arrayLength; --i >= 0; theString++ ) + { + if( *theString != NULL ) + len += strlen( *theString ); + len++; + } + + if( len < 1 ) + return NULL; + + merged = ( char * ) malloc( len ); + merged[0] = '\0'; + + theString = array; + for( i = arrayLength; --i >= 0; theString++ ) + { + if( *theString != NULL ) + strcat( merged, *theString ); + if( i != 0 ) + strcat( merged, "," ); + } + return merged; +} + +void _XklConfigRecSplitLayouts( XklConfigRecPtr data, const char *merged ) +{ + _XklConfigRecSplitByComma( &data->layouts, &data->numLayouts, merged ); +} + +void _XklConfigRecSplitVariants( XklConfigRecPtr data, const char *merged ) +{ + _XklConfigRecSplitByComma( &data->variants, &data->numVariants, merged ); +} + +void _XklConfigRecSplitOptions( XklConfigRecPtr data, const char *merged ) +{ + _XklConfigRecSplitByComma( &data->options, &data->numOptions, merged ); +} + +void _XklConfigRecSplitByComma( char ***array, + int *arraySize, const char *merged ) +{ + const char *pc = merged; + char **ppc, *npc; + *arraySize = 0; + *array = NULL; + + if( merged == NULL || merged[0] == '\0' ) + return; + + // first count the elements + while( ( npc = strchr( pc, ',' ) ) != NULL ) + { + ( *arraySize )++; + pc = npc + 1; + } + ( *arraySize )++; + + if( ( *arraySize ) != 0 ) + { + int len; + *array = ( char ** ) malloc( ( sizeof( char * ) ) * ( *arraySize ) ); + + ppc = *array; + pc = merged; + while( ( npc = strchr( pc, ',' ) ) != NULL ) + { + int len = npc - pc; + *ppc = ( char * ) strndup( pc, len ); + ppc++; + pc = npc + 1; + } + + len = npc - pc; + *ppc = ( char * ) strndup( pc, len ); + } +} + +static Bool _XklConfigPrepareBeforeKbd( const XklConfigRecPtr data ) +{ + memset( &_xklVarDefs, 0, sizeof( _xklVarDefs ) ); + + _xklVarDefs.model = ( char * ) data->model; + + if( data->layouts != NULL ) + _xklVarDefs.layout = _XklConfigRecMergeLayouts( data ); + + if( data->variants != NULL ) + _xklVarDefs.variant = _XklConfigRecMergeVariants( data ); + + if( data->options != NULL ) + _xklVarDefs.options = _XklConfigRecMergeOptions( data ); + + locale = setlocale( LC_ALL, NULL ); + if( locale != NULL ) + locale = strdup( locale ); + + rules = XkbRF_Load( RULES_PATH, locale, True, True ); + + if( rules == NULL ) + { + _xklLastErrorMsg = "Could not load rules"; + return False; + } + + if( !XkbRF_GetComponents( rules, &_xklVarDefs, &componentNames ) ) + { + _xklLastErrorMsg = "Could not translate rules into components"; + return False; + } + + return True; +} + +static void _XklConfigCleanAfterKbd( ) +{ + XkbRF_Free( rules, True ); + + if( locale != NULL ) + { + free( locale ); + locale = NULL; + } + if( _xklVarDefs.layout != NULL ) + { + free( _xklVarDefs.layout ); + _xklVarDefs.layout = NULL; + } + if( _xklVarDefs.options != NULL ) + { + free( _xklVarDefs.options ); + _xklVarDefs.options = NULL; + } +} + +static void _XklApplyFun2XkbDesc( XkbDescPtr xkb, XkbDescModifierFunc fun, + void *userData, Bool activeInServer ) +{ + int mask; + // XklDumpXkbDesc( "comp.xkb", xkb ); + if( fun == NULL ) + return; + + if( activeInServer ) + { + mask = ( *fun ) ( NULL, NULL ); + if( mask == 0 ) + return; + XkbGetUpdatedMap( _xklDpy, mask, xkb ); + // XklDumpXkbDesc( "restored1.xkb", xkb ); + } + + mask = ( *fun ) ( xkb, userData ); + if( activeInServer ) + { + // XklDumpXkbDesc( "comp+.xkb", xkb ); + XkbSetMap( _xklDpy, mask, xkb ); + XSync( _xklDpy, False ); + + // XkbGetUpdatedMap( _xklDpy, XkbAllMapComponentsMask, xkb ); + // XklDumpXkbDesc( "restored2.xkb", xkb ); + } +} + +void XklConfigInit( void ) +{ + xmlXPathInit( ); + modelsXPath = xmlXPathCompile( "/xkbConfigRegistry/modelList/model" ); + layoutsXPath = xmlXPathCompile( "/xkbConfigRegistry/layoutList/layout" ); + optionGroupsXPath = + xmlXPathCompile( "/xkbConfigRegistry/optionList/group" ); + _XklI18NInit( ); +} + +void XklConfigTerm( void ) +{ + if( modelsXPath != NULL ) + xmlXPathFreeCompExpr( modelsXPath ); + if( layoutsXPath != NULL ) + xmlXPathFreeCompExpr( layoutsXPath ); + if( optionGroupsXPath != NULL ) + xmlXPathFreeCompExpr( optionGroupsXPath ); +} + +Bool XklConfigLoadRegistry( void ) +{ + theRegistry.doc = xmlParseFile( XML_CFG_PATH ); + if( theRegistry.doc == NULL ) + { + theRegistry.xpathContext = NULL; + _xklLastErrorMsg = "Could not parse XKB configuration registry"; + } else + theRegistry.xpathContext = xmlXPathNewContext( theRegistry.doc ); + return _XklConfigInitialized( ); +} + +void XklConfigFreeRegistry( void ) +{ + if( _XklConfigInitialized( ) ) + { + xmlXPathFreeContext( theRegistry.xpathContext ); + xmlFreeDoc( theRegistry.doc ); + } +} + +void XklConfigEnumModels( ConfigItemProcessFunc func, void *userData ) +{ + _XklConfigEnumSimple( modelsXPath, func, userData ); +} + +void XklConfigEnumLayouts( ConfigItemProcessFunc func, void *userData ) +{ + _XklConfigEnumSimple( layoutsXPath, func, userData ); +} + +void XklConfigEnumLayoutVariants( const char *layoutName, + ConfigItemProcessFunc func, void *userData ) +{ + _XklConfigEnumDirect + ( "/xkbConfigRegistry/layoutList/layout/variantList/variant[../../configItem/name = '%s']", + layoutName, func, userData ); +} + +void XklConfigEnumOptionGroups( GroupProcessFunc func, void *userData ) +{ + xmlXPathObjectPtr xpathObj; + int i; + + if( !_XklConfigInitialized( ) ) + return; + xpathObj = + xmlXPathCompiledEval( optionGroupsXPath, theRegistry.xpathContext ); + if( xpathObj != NULL ) + { + xmlNodeSetPtr nodes = xpathObj->nodesetval; + xmlNodePtr *theNodePtr = nodes->nodeTab; + for( i = nodes->nodeNr; --i >= 0; ) + { + XklConfigItem ci; + + if( _XklReadConfigItem( *theNodePtr, &ci ) ) + { + Bool allowMC = True; + xmlChar *allowMCS = + xmlGetProp( *theNodePtr, "allowMultipleSelection" ); + if( allowMCS != NULL ) + { + allowMC = strcmp( "false", allowMCS ); + xmlFree( allowMCS ); + } + + func( &ci, allowMC, userData ); + } + + theNodePtr++; + } + xmlXPathFreeObject( xpathObj ); + } +} + +void XklConfigEnumOptions( const char *optionGroupName, + ConfigItemProcessFunc func, void *userData ) +{ + _XklConfigEnumDirect + ( "/xkbConfigRegistry/optionList/group/option[../configItem/name = '%s']", + optionGroupName, func, userData ); +} + +Bool XklConfigFindModel( XklConfigItemPtr ptr /* in/out */ ) +{ + return + _XklConfigFindObject + ( "/xkbConfigRegistry/modelList/model[configItem/name = '%s%s']", "", + ptr, NULL ); +} + +Bool XklConfigFindLayout( XklConfigItemPtr ptr /* in/out */ ) +{ + return + _XklConfigFindObject + ( "/xkbConfigRegistry/layoutList/layout[configItem/name = '%s%s']", "", + ptr, NULL ); +} + +Bool XklConfigFindVariant( const char *layoutName, + XklConfigItemPtr ptr /* in/out */ ) +{ + return + _XklConfigFindObject + ( "/xkbConfigRegistry/layoutList/layout/variantList/variant" + "[../../configItem/name = '%s' and configItem/name = '%s']", + layoutName, ptr, NULL ); +} + +Bool XklConfigFindOptionGroup( XklConfigItemPtr ptr /* in/out */ , + Bool * allowMultipleSelection /* out */ ) +{ + xmlNodePtr node; + Bool rv = + _XklConfigFindObject + ( "/xkbConfigRegistry/optionList/group[configItem/name = '%s%s']", "", + ptr, &node ); + + if( rv && allowMultipleSelection != NULL ) + { + xmlChar *val = xmlGetProp( node, "allowMultipleSelection" ); + *allowMultipleSelection = False; + if( val != NULL ) + { + *allowMultipleSelection = !strcmp( val, "true" ); + xmlFree( val ); + } + } + return rv; +} + +Bool XklConfigFindOption( const char *optionGroupName, + XklConfigItemPtr ptr /* in/out */ ) +{ + return + _XklConfigFindObject + ( "/xkbConfigRegistry/optionList/group/option" + "[../configItem/name = '%s' and configItem/name = '%s']", + optionGroupName, ptr, NULL ); +} + +Bool XklMultipleLayoutsSupported( void ) +{ + struct stat buf; + return 0 == stat( MULTIPLE_LAYOUTS_CHECK_PATH, &buf ); +} + +Bool XklConfigActivate( const XklConfigRecPtr data, XkbDescModifierFunc fun, + void *userData ) +{ + Bool rv = False; + if( _XklConfigPrepareBeforeKbd( data ) ) + { + XkbDescPtr xkb; + xkb = + XkbGetKeyboardByName( _xklDpy, XkbUseCoreKbd, &componentNames, + XkbGBN_AllComponentsMask, + XkbGBN_AllComponentsMask & + ( ~XkbGBN_GeometryMask ), True ); + + //!! Do I need to free it anywhere? + if( xkb != NULL ) + { + _XklApplyFun2XkbDesc( xkb, fun, userData, True ); +#if 0 + XklDumpXkbDesc( "config.xkb", xkb ); + int i; + XklDebug( 150, "New model: [%s]\n", data->model ); + XklDebug( 150, "New layout: [%s]\n", data->layout ); + XklDebug( 150, "New variant: [%s]\n", data->variant ); + for( i = data->numOptions; --i >= 0; ) + XklDebug( 150, "New option[%d]: [%s]\n", i, data->options[i] ); +#endif + + if( XklSetNamesProp + ( _xklAtoms[XKB_RF_NAMES_PROP_ATOM], RULES_FILE, data ) ) + rv = True; + else + _xklLastErrorMsg = "Could not set names property"; + XkbFreeKeyboard( xkb, XkbAllComponentsMask, True ); + } else + { + _xklLastErrorMsg = "Could not load keyboard description"; + } + } + _XklConfigCleanAfterKbd( ); + return rv; +} + +int XklSetKeyAsSwitcher( XkbDescPtr kbd, void *userData ) +{ + if( kbd != NULL ) + { + XkbClientMapPtr map = kbd->map; + if( map != NULL ) + { + KeySym keysym = ( KeySym ) userData; + KeySym *psym = map->syms; + int symno; + + for( symno = map->num_syms; --symno >= 0; psym++ ) + { + if( *psym == keysym ) + { + XklDebug( 160, "Changing %s to %s at %d\n", + XKeysymToString( *psym ), + XKeysymToString( XK_ISO_Next_Group ), psym - map->syms ); + *psym = XK_ISO_Next_Group; + break; + } + } + } else + XklDebug( 160, "No client map in the keyboard description?\n" ); + } + return XkbKeySymsMask | XkbKeyTypesMask | XkbKeyActionsMask; +} + +Bool XklConfigWriteXKMFile( const char *fileName, const XklConfigRecPtr data, + XkbDescModifierFunc fun, void *userData ) +{ + Bool rv = False; + + FILE *output = fopen( fileName, "w" ); + XkbFileInfo dumpInfo; + + if( output == NULL ) + { + _xklLastErrorMsg = "Could not open the XKB file"; + return False; + } + + if( _XklConfigPrepareBeforeKbd( data ) ) + { + XkbDescPtr xkb; + xkb = + XkbGetKeyboardByName( _xklDpy, XkbUseCoreKbd, &componentNames, + XkbGBN_AllComponentsMask, + XkbGBN_AllComponentsMask & + ( ~XkbGBN_GeometryMask ), False ); + if( xkb != NULL ) + { + _XklApplyFun2XkbDesc( xkb, fun, userData, False ); + + dumpInfo.defined = 0; + dumpInfo.xkb = xkb; + dumpInfo.type = XkmKeymapFile; + rv = XkbWriteXKMFile( output, &dumpInfo ); + XkbFreeKeyboard( xkb, XkbGBN_AllComponentsMask, True ); + } else + _xklLastErrorMsg = "Could not load keyboard description"; + } + _XklConfigCleanAfterKbd( ); + fclose( output ); + return rv; +} diff --git a/libxklavier/xklavier_config.h b/libxklavier/xklavier_config.h new file mode 100644 index 0000000..18fd71d --- /dev/null +++ b/libxklavier/xklavier_config.h @@ -0,0 +1,389 @@ +/** + * @file xklavier_config.h + */ + +#ifndef __XKLABIER_CONFIG_H__ +#define __XKLABIER_CONFIG_H__ + +#include <libxklavier/xklavier.h> + +/** + * Maximum name length, including '\'0' character + */ +#define XKL_MAX_CI_NAME_LENGTH 32 + +/** + * Maximum short description length, including '\\0' character. + * Important: this length is in bytes, so for unicode (UTF-8 encoding in + * XML file) the actual maximum length can be smaller. + */ +#define XKL_MAX_CI_SHORT_DESC_LENGTH 10 + +/** + * Maximum description length, including '\\0' character. + * Important: this length is in bytes, so for unicode (UTF-8 encoding in + * XML file) the actual maximum length can be smaller. + */ +#define XKL_MAX_CI_DESC_LENGTH 192 + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/** + * The configuration item. Corresponds to XML element "configItem". + */ + typedef struct _XklConfigItem + { +/** + * The configuration item name. Corresponds to XML element "name". + */ + char name[XKL_MAX_CI_NAME_LENGTH]; + +/** + * The configuration item short description. Corresponds to XML element "shortDescription". + */ + char shortDescription[XKL_MAX_CI_DESC_LENGTH]; + +/** + * The configuration item description. Corresponds to XML element "description". + */ + char description[XKL_MAX_CI_DESC_LENGTH]; + } + XklConfigItem, *XklConfigItemPtr; + +/** + * Basic configuration params + */ + typedef struct _XklConfigRec + { +/** + * The keyboard model + */ + char *model; +/** + * The number of keyboard layouts + */ + int numLayouts; +/** + * The array of keyboard layouts + */ + char **layouts; +/** + * The number of keyboard layout variants + */ + int numVariants; +/** + * The array of keyboard layout variants (if any) + */ + char **variants; +/** + * The number of keyboard layout options + */ + int numOptions; +/** + * The array of keyboard layout options + */ + char **options; + } + XklConfigRec, *XklConfigRecPtr; +/** + * @defgroup xklconfiginitterm XKB configuration handling initialization and termination + * @{ + */ + +/** + * Initializes XML configuration-related structures + */ + extern void XklConfigInit( void ); + +/** + * Cleans XML configuration-related structures + */ + extern void XklConfigTerm( void ); + +/** + * Loads XML configuration registry + * @return true on success + */ + extern Bool XklConfigLoadRegistry( void ); + +/** + * Frees XML configuration registry + */ + extern void XklConfigFreeRegistry( void ); +/** @} */ + +/** + * @defgroup enum XKB configuration elements enumeration functions + * @{ + */ + +/** + * Callback type used for enumerating keyboard models, layouts, variants, options + * @param configItem is the item from registry + * @param userData is anything which can be stored into the pointer + */ + typedef void ( *ConfigItemProcessFunc ) ( const XklConfigItemPtr configItem, + void *userData ); + +/** + * Callback type used for enumerating keyboard option groups + * @param configItem is the item from registry + * @param allowMultipleSelection is a flag whether this group allows multiple selection + * @param userData is anything which can be stored into the pointer + */ + typedef void ( *GroupProcessFunc ) ( const XklConfigItemPtr configItem, + Bool allowMultipleSelection, + void *userData ); +/** + * Callback used to modify/patch the keyboard description before the + * activation. The function should be able to work without kbd ( kbd = NULL ). + * In this case, it should just return the mask of possible changes. + * @param kbd is the keyboard description + * @param userData is anything which can be stored into the pointer + * @return the mask of the changes + */ + typedef int ( *XkbDescModifierFunc ) ( XkbDescPtr kbd, void *userData ); +/** + * Enumerates keyboard models from the XML configuration registry + * @param func is a callback to call for every model + * @param userData is anything which can be stored into the pointer + */ + extern void XklConfigEnumModels( ConfigItemProcessFunc func, + void *userData ); + +/** + * Enumerates keyboard layouts from the XML configuration registry + * @param func is a callback to call for every layout + * @param userData is anything which can be stored into the pointer + */ + extern void XklConfigEnumLayouts( ConfigItemProcessFunc func, + void *userData ); + +/** + * Enumerates keyboard layout variants from the XML configuration registry + * @param layoutName is the layout name for which variants will be listed + * @param func is a callback to call for every layout variant + * @param userData is anything which can be stored into the pointer + */ + extern void XklConfigEnumLayoutVariants( const char *layoutName, + ConfigItemProcessFunc func, + void *userData ); + +/** + * Enumerates keyboard option groups from the XML configuration registry + * @param func is a callback to call for every option group + * @param userData is anything which can be stored into the pointer + */ + extern void XklConfigEnumOptionGroups( GroupProcessFunc func, + void *userData ); + +/** + * Enumerates keyboard options from the XML configuration registry + * @param optionGroupName is the option group name for which variants + * will be listed + * @param func is a callback to call for every option + * @param userData is anything which can be stored into the pointer + */ + extern void XklConfigEnumOptions( const char *optionGroupName, + ConfigItemProcessFunc func, + void *userData ); + +/** @} */ + +/** + * @defgroup lookup XKB configuration element lookup functions + * @{ + */ + +/** + * Loads a keyboard model information from the XML configuration registry. + * @param ptr is a pointer to a XklConfigItem containing the name of the + * keyboard model. On successfull return, the descriptions are filled. + * @return True if appropriate element was found and loaded + */ + extern Bool XklConfigFindModel( XklConfigItemPtr ptr ); + +/** + * Loads a keyboard layout information from the XML configuration registry. + * @param ptr is a pointer to a XklConfigItem containing the name of the + * keyboard layout. On successfull return, the descriptions are filled. + * @return True if appropriate element was found and loaded + */ + extern Bool XklConfigFindLayout( XklConfigItemPtr ptr ); + +/** + * Loads a keyboard layout variant information from the XML configuration + * registry. + * @param layoutName is a name of the parent layout + * @param ptr is a pointer to a XklConfigItem containing the name of the + * keyboard layout variant. On successfull return, the descriptions are filled. + * @return True if appropriate element was found and loaded + */ + extern Bool XklConfigFindVariant( const char *layoutName, + XklConfigItemPtr ptr ); + +/** + * Loads a keyboard option group information from the XML configuration + * registry. + * @param ptr is a pointer to a XklConfigItem containing the name of the + * keyboard option group. On successfull return, the descriptions are filled. + * @param allowMultipleSelection is a pointer to some Bool variable to fill + * the corresponding attribute of XML element "group". + * @return True if appropriate element was found and loaded + */ + extern Bool XklConfigFindOptionGroup( XklConfigItemPtr ptr, + Bool * allowMultipleSelection ); + +/** + * Loads a keyboard option information from the XML configuration + * registry. + * @param optionGroupName is a name of the option group + * @param ptr is a pointer to a XklConfigItem containing the name of the + * keyboard option. On successfull return, the descriptions are filled. + * @return True if appropriate element was found and loaded + */ + extern Bool XklConfigFindOption( const char *optionGroupName, + XklConfigItemPtr ptr ); +/** @} */ + +/** + * @defgroup activation XKB configuration activation + * @{ + */ + +/** + * Determines whether multiple layouts (by Ivan Pascal) are supported. + * @return True if so. + */ + extern Bool XklMultipleLayoutsSupported( void ); +/** + * Activates some XKB configuration + * @param data is a valid XKB configuration + * @param fun is a callback function for modifying the XKB keyboard + * description. Can be NULL + * @param userData is a data to pass to the callback + * @return True on success + * @see XklSetKeyAsSwitcher + * At the moment, accepts only _ONE_ layout. Later probably I'll improve this.. + */ + extern Bool XklConfigActivate( const XklConfigRecPtr data, + XkbDescModifierFunc fun, void *userData ); + +/** + * One of possible XkbDescModifierFunc + * Handy for setting one key as a group switcher + * @param kbd is the keyboard to modify + * @param userData is a keysym to make a group switcher + */ + extern int XklSetKeyAsSwitcher( XkbDescPtr kbd, void *userData ); + +/** + * Loads the current XKB configuration (from X server) + * @param data is a buffer for XKB configuration + * @return True on success + */ + extern Bool XklConfigGetFromServer( XklConfigRecPtr data ); + +/** + * Loads the current XKB configuration (from backup) + * @param data is a buffer for XKB configuration + * @return True on success + * @see XklBackupNamesProp + */ + extern Bool XklConfigGetFromBackup( XklConfigRecPtr data ); + +/** + * Writes some XKB configuration into XKM file + * @param fileName is a name of the file to create + * @param data is a valid XKB configuration + * @param fun is a callback function for modifying the XKB keyboard + * description. Can be NULL + * @param userData is a data to pass to the callback + * @return True on success + * At the moment, accepts only _ONE_ layout. Later probably I'll improve this.. + */ + extern Bool XklConfigWriteXKMFile( const char *fileName, + const XklConfigRecPtr data, + XkbDescModifierFunc fun, + void *userData ); + +/** @} */ + +/** + * @defgroup props Saving and restoring XKB configuration into X root window properties + * Generalizes XkbRF_GetNamesProp and XkbRF_SetNamesProp. + * @{ + */ + +/** + * Gets the XKB configuration from any root window property + * @param rulesAtomName is an atom name of the root window property to read + * @param rulesFileOut is a pointer to hold the file name + * @param configOut is a buffer to hold the result - + * all records are allocated using standard malloc + * @return True on success + */ + extern Bool XklGetNamesProp( Atom rulesAtomName, + char **rulesFileOut, + XklConfigRecPtr configOut ); + +/** + * Saves the XKB configuration into any root window property + * @param rulesAtomName is an atom name of the root window property to write + * @param rulesFile is a rules file name + * @param config is a configuration to save + * @return True on success + */ + extern Bool XklSetNamesProp( Atom rulesAtomName, + char *rulesFile, XklConfigRecPtr config ); + +/** + * Backups current XKB configuration into some property - + * if this property is not defined yet. + * @return True on success + */ + extern Bool XklBackupNamesProp( ); + +/** + * Restores XKB from the property saved by XklBackupNamesProp + * @return True on success + * @see XklBackupNamesProp + */ + extern Bool XklRestoreNamesProp( ); + +/** @} */ + +/** + * @defgroup xklconfig XklConfigRec management utilities + * Little utilities for initing/destroying/resetting XklConfigRec. + * @{ + */ + +/** + * Initializes the record (actually, fills it with 0-s + * @param data is a record to initialize + */ + extern void XklConfigRecInit( XklConfigRecPtr data ); + +/** + * Resets the record (equal to Destroy and Init) + * @param data is a record to reset + */ + extern void XklConfigRecReset( XklConfigRecPtr data ); + +/** + * Cleans the record (frees all the non-null members) + * @param data is a record to clean + */ + extern void XklConfigRecDestroy( XklConfigRecPtr data ); + +/** @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/libxklavier/xklavier_config_i18n.c b/libxklavier/xklavier_config_i18n.c new file mode 100644 index 0000000..c8eef15 --- /dev/null +++ b/libxklavier/xklavier_config_i18n.c @@ -0,0 +1,237 @@ +#include <stdlib.h> +#include <string.h> +#include <iconv.h> + +#include "config.h" + +#ifdef HAVE_LANGINFO_CODESET +# include <langinfo.h> +#endif + +#ifdef HAVE_SETLOCALE +# include <locale.h> +#endif + +#include "xklavier_private.h" + +#define MAX_LOCALE_LEN 128 + +static char localeSubStrings[3][MAX_LOCALE_LEN]; + +/* + * some bad guys create LC_ALL=LC_CTYPE=ru_RU.UTF-8;LC_NUMERIC=C;LC_TIME=ru_RU.UTF-8;LC_COLLATE=ru_RU.UTF-8;LC_MONETARY=ru_RU.UTF-8;LC_MESSAGES=ru_RU.UTF-8;LC_PAPER=ru_RU.UTF-8;LC_NAME=ru_RU.UTF-8;LC_ADDRESS=ru_RU.UTF-8;LC_TELEPHONE=ru_RU.UTF-8;LC_MEASUREMENT=ru_RU.UTF-8;LC_IDENTIFICATION=ru_RU.UTF-8 + */ +static const char *_XklParseLC_ALL2LC_MESSAGES( const char *lcAll ) +{ + const char *lcMsgPos = strstr( lcAll, "LC_MESSAGES=" ); + const char *lcMsgEnd; + size_t len; + static char buf[128]; + if( lcMsgPos == NULL ) + return lcAll; + lcMsgPos += 12; + lcMsgEnd = strchr( lcMsgPos, ';' ); + if( lcMsgEnd == NULL ) // LC_MESSAGES is the last piece of LC_ALL + { + return lcMsgPos; //safe to return! + } + len = lcMsgEnd - lcMsgPos; + if( len > sizeof( buf ) ) + len = sizeof( buf ); + strncpy( buf, lcMsgPos, len ); + buf[sizeof( buf ) - 1] = '\0'; + return buf; +} + +// Taken from gnome-vfs +static Bool _XklGetCharset( const char **a ) +{ + static const char *charset = NULL; + + if( charset == NULL ) + { + charset = getenv( "CHARSET" ); + + if( charset == NULL || charset[0] == '\0' ) + { +// taken from gnome-vfs +#ifdef HAVE_LANGINFO_CODESET + charset = nl_langinfo( CODESET ); + if( charset == NULL || charset[0] == '\0' ) + { +#endif +#ifdef HAVE_SETLOCALE + charset = setlocale( LC_CTYPE, NULL ); + if( charset == NULL || charset[0] == '\0' ) + { +#endif + charset = getenv( "LC_ALL" ); + if( charset == NULL || charset[0] == '\0' ) + { + charset = getenv( "LC_CTYPE" ); + if( charset == NULL || charset[0] == '\0' ) + charset = getenv( "LANG" ); + } +#ifdef HAVE_SETLOCALE + } else + { + XklDebug( 150, "Using charset from setlocale: [%s]\n", charset ); + } +#endif +#ifdef HAVE_LANGINFO_CODESET + } else + { + XklDebug( 150, "Using charset from nl_langinfo: [%s]\n", charset ); + } +#endif + } + } + + if( charset != NULL && *charset != '\0' ) + { + *a = charset; + return ( charset != NULL && strstr( charset, "UTF-8" ) != NULL ); + } + /* Assume this for compatibility at present. */ + *a = "US-ASCII"; + XklDebug( 150, "Using charset fallback: [%s]\n", *a ); + + return False; +} + +char *_XklLocaleFromUtf8( const char *utf8string ) +{ + int len; + int bytesRead; + int bytesWritten; + + iconv_t converter; + static char converted[XKL_MAX_CI_DESC_LENGTH]; + char *convertedStart = converted; + char *utfStart = ( char * ) utf8string; + int clen = XKL_MAX_CI_DESC_LENGTH - 1; + const char *charset; + + static Bool alreadyWarned = False; + + if( utf8string == NULL ) + return NULL; + + len = strlen( utf8string ); + + if( _XklGetCharset( &charset ) ) + return strdup( utf8string ); + + converter = iconv_open( charset, "UTF-8" ); + if( converter == ( iconv_t ) - 1 ) + { + if( !alreadyWarned ) + { + alreadyWarned = True; + XklDebug( 0, + "Unable to convert MIME info from UTF-8 to the current locale %s. MIME info will probably display wrong.", + charset ); + } + return strdup( utf8string ); + } + //converted = convert_with_iconv( utf8string, + // len, converter, &bytesRead, &bytesWritten ); + + if( iconv( converter, &utfStart, &len, &convertedStart, &clen ) == -1 ) + { + XklDebug( 0, + "Unable to convert %s from UTF-8 to %s, this string will probably display wrong.", + utf8string, charset ); + return strdup( utf8string ); + } + *convertedStart = '\0'; + + iconv_close( converter ); + + return converted; +} + +/* + * country[_LANG[.ENCODING]] - any other ideas? + */ +void _XklI18NInit( ) +{ + char *dotPos; + char *underscorePos; + const char *locale = NULL; + char *curSubstring; + + localeSubStrings[0][0] = localeSubStrings[1][0] = + localeSubStrings[2][0] = '\0'; + +#ifdef HAVE_SETLOCALE + locale = setlocale( LC_MESSAGES, NULL ); +#endif + if( locale == NULL || locale[0] == '\0' ) + { + locale = getenv( "LC_MESSAGES" ); + if( locale == NULL || locale[0] == '\0' ) + { + locale = getenv( "LC_ALL" ); + if( locale == NULL || locale[0] == '\0' ) + locale = getenv( "LANG" ); + else + locale = _XklParseLC_ALL2LC_MESSAGES( locale ); + } + } + + if( locale == NULL ) + { + XklDebug( 0, "Could not find locale - can be problems with i18n" ); + return; + } + + strncpy( localeSubStrings[0], locale, MAX_LOCALE_LEN ); + + curSubstring = localeSubStrings[1]; + + dotPos = strchr( locale, '.' ); + if( dotPos != NULL ) + { + int idx = dotPos - locale; + if( idx >= MAX_LOCALE_LEN ) + idx = MAX_LOCALE_LEN - 1; + strncpy( curSubstring, locale, idx ); + curSubstring[idx] = '\0'; + curSubstring += MAX_LOCALE_LEN; + } + + underscorePos = strchr( locale, '_' ); + if( underscorePos != NULL && ( dotPos == NULL || dotPos > underscorePos ) ) + { + int idx = underscorePos - locale; + if( idx >= MAX_LOCALE_LEN ) + idx = MAX_LOCALE_LEN - 1; + strncpy( curSubstring, locale, idx ); + curSubstring[idx] = '\0'; + } + + XklDebug( 150, "Locale search order:\n" ); + XklDebug( 150, " 0: %s\n", localeSubStrings[0] ); // full locale - highest priority + XklDebug( 150, " 1: %s\n", localeSubStrings[1] ); + XklDebug( 150, " 2: %s\n", localeSubStrings[2] ); +} + +int _XklGetLanguagePriority( const char *lang ) +{ + int i, priority = -1; + + for( i = sizeof( localeSubStrings ) / sizeof( localeSubStrings[0] ); + --i >= 0; ) + { + if( localeSubStrings[0][0] == '\0' ) + continue; + + if( !strcmp( lang, localeSubStrings[i] ) ) + { + priority = i; + break; + } + } + return priority; +} diff --git a/libxklavier/xklavier_dump.c b/libxklavier/xklavier_dump.c new file mode 100644 index 0000000..bc7ab08 --- /dev/null +++ b/libxklavier/xklavier_dump.c @@ -0,0 +1,276 @@ +/* + * 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 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdio.h> +#include <string.h> + +#include "xklavier_private.h" + +static void _XkbModsRecDump( FILE * fs, XkbModsRec * mods ) +{ + fprintf( fs, "flags: 0x%X\n", mods->mask ); + fprintf( fs, "real_mods: 0x%X\n", mods->real_mods ); + fprintf( fs, "vmods: 0x%X\n", mods->vmods ); +} + +static void _XkbControlsDump( FILE * fs, XkbControlsPtr ctrls ) +{ + int i; + char buf[1024]; + fprintf( fs, "mk_dflt_btn: %d\n", ctrls->mk_dflt_btn ); + fprintf( fs, "num_groups: %d\n", ctrls->num_groups ); + fprintf( fs, "groups_wrap: %d\n", ctrls->groups_wrap ); + fprintf( fs, "internal: \n" ); + _XkbModsRecDump( fs, &ctrls->internal ); + fprintf( fs, "ignore_lock: \n" ); + _XkbModsRecDump( fs, &ctrls->ignore_lock ); + fprintf( fs, "enabled_ctrls: 0x%X\n", ctrls->enabled_ctrls ); + fprintf( fs, "repeat_delay: %d\n", ctrls->repeat_delay ); + fprintf( fs, "repeat_interval: %d\n", ctrls->repeat_interval ); + fprintf( fs, "slow_keys_delay: %d\n", ctrls->slow_keys_delay ); + fprintf( fs, "debounce_delay: %d\n", ctrls->debounce_delay ); + fprintf( fs, "mk_delay: %d\n", ctrls->mk_delay ); + fprintf( fs, "mk_interval: %d\n", ctrls->mk_interval ); + fprintf( fs, "mk_time_to_max: %d\n", ctrls->mk_time_to_max ); + fprintf( fs, "mk_max_speed: %d\n", ctrls->mk_max_speed ); + fprintf( fs, "mk_curve: %d\n", ctrls->mk_curve ); + fprintf( fs, "ax_options: %d\n", ctrls->ax_options ); + fprintf( fs, "ax_timeout: %d\n", ctrls->ax_timeout ); + fprintf( fs, "axt_opts_mask: 0x%X\n", ctrls->axt_opts_mask ); + fprintf( fs, "axt_opts_values: 0x%X\n", ctrls->axt_opts_values ); + fprintf( fs, "axt_ctrls_mask: 0x%X\n", ctrls->axt_ctrls_mask ); + fprintf( fs, "axt_ctrls_values: 0x%X\n", ctrls->axt_ctrls_values ); + fprintf( fs, "axt_ctrls_values: 0x%X\n", ctrls->axt_ctrls_values ); + fprintf( fs, "per_key_repeat:\n" ); + buf[0] = 0; + for( i = 0; i < XkbPerKeyBitArraySize; i++ ) + { + char b[5]; + snprintf( b, sizeof( b ), "%d ", ctrls->per_key_repeat[i] ); + strcat( buf, b ); + } + fprintf( fs, " %s\n", buf ); +} + +static const char *actionTypeNames[] = { + "XkbSA_NoAction", + "XkbSA_SetMods", + "XkbSA_LatchMods", + "XkbSA_LockMods", + "XkbSA_SetGroup", + "XkbSA_LatchGroup", + "XkbSA_LockGroup", + "XkbSA_MovePtr", + "XkbSA_PtrBtn", + "XkbSA_LockPtrBtn", + "XkbSA_SetPtrDflt", + "XkbSA_ISOLock", + "XkbSA_Terminate", + "XkbSA_SwitchScreen", + "XkbSA_SetControls", + "XkbSA_LockControls", + "XkbSA_ActionMessage", + "XkbSA_RedirectKey", + "XkbSA_DeviceBtn", + "XkbSA_LockDeviceBtn", + "XkbSA_DeviceValuator" +}; + +static void _XkbActionDump( FILE * fs, int level, XkbAction * act ) +{ + XkbGroupAction *ga; + fprintf( fs, "%*stype: %d(%s)\n", level, "", act->type, + actionTypeNames[act->type] ); + switch ( act->type ) + { + case XkbSA_SetGroup: + case XkbSA_LatchGroup: + case XkbSA_LockGroup: + ga = ( XkbGroupAction * ) act; + fprintf( fs, "%*sXkbGroupAction: \n", level, "" ); + fprintf( fs, "%*sflags: %d\n", level, "", ga->flags ); + fprintf( fs, "%*sgroup_XXX: %d\n", level, "", ga->group_XXX ); + break; + } +} + +static void _XkbBehaviorDump( FILE * fs, int level, XkbBehavior * b ) +{ + fprintf( fs, "%*stype: %d\n", level, "", b->type ); + fprintf( fs, "%*sdata: %d\n", level, "", b->data ); +} + +static void _XkbServerMapDump( FILE * fs, int level, XkbServerMapPtr server, + XkbDescPtr kbd ) +{ + int i; + XkbAction *pa = server->acts; + XkbBehavior *pb = server->behaviors; + fprintf( fs, "%*snum_acts: %d\n", level, "", server->num_acts ); + fprintf( fs, "%*ssize_acts: %d\n", level, "", server->size_acts ); + if( server->acts != NULL ) + { + for( i = 0; i < server->num_acts; i++ ) + { + fprintf( fs, "%*sacts[%d]:\n", level, "", i ); + _XkbActionDump( fs, level + 2, pa++ ); + } + } else + fprintf( fs, "%*sNO acts\n", level, "" ); + + if( server->key_acts != NULL ) + { + for( i = 0; i <= kbd->max_key_code; i++ ) + { + fprintf( fs, "%*skey_acts[%d]: offset %d, total %d\n", level, "", i, + server->key_acts[i], XkbKeyNumSyms(kbd,i) ); + } + } else + fprintf( fs, "%*sNO key_acts\n", level, "" ); + + for( i = 0; i < XkbNumVirtualMods; i++ ) + { + fprintf( fs, "%*svmod[%d]: %X\n", level, "", i, server->vmods[i] ); + } + + if( server->behaviors != NULL ) + { + for( i = 0; i <= kbd->max_key_code; i++ ) + { + fprintf( fs, "%*sbehaviors[%d]:\n", level, "", i ); + _XkbBehaviorDump( fs, level + 2, pb++ ); + } + } else + fprintf( fs, "%*sNO behaviors\n", level, "" ); + + if( server->explicit != NULL ) + { + for( i = 0; i <= kbd->max_key_code; i++ ) + { + fprintf( fs, "%*sexplicit[%d]: %d\n", level, "", i, + server->explicit[i] ); + } + } else + fprintf( fs, "%*sNO explicit\n", level, "" ); + + if( server->vmodmap != NULL ) + { + for( i = 0; i <= kbd->max_key_code; i++ ) + { + fprintf( fs, "%*svmodmap[%d]: %d\n", level, "", i, server->vmodmap[i] ); + } + } else + fprintf( fs, "%*sNO vmodmap\n", level, "" ); +} + +static void _XkbKeyTypeDump( FILE * fs, int level, XkbKeyTypePtr type ) +{ + char *z = type->name == None ? NULL : XGetAtomName( _xklDpy, type->name ); + fprintf( fs, "%*sname: 0x%X(%s)\n", level, "", type->name, z ); + if( z != NULL ) + XFree( z ); +} + +static void _XkbSymMapDump( FILE * fs, int level, XkbSymMapPtr ksm ) +{ + int i; + fprintf( fs, "%*skt_index: ", level, "" ); + for( i = 0; i < XkbNumKbdGroups; i++ ) + { + fprintf( fs, "%d ", ksm->kt_index[i] ); + } + fprintf( fs, "\n%*sgroup_info: %d\n", level, "", ksm->group_info ); + fprintf( fs, "%*swidth: %d\n", level, "", ksm->width ); + fprintf( fs, "%*soffset: %d\n", level, "", ksm->offset ); +} + +static void _XkbClientMapDump( FILE * fs, int level, XkbClientMapPtr map, + XkbDescPtr kbd ) +{ + int i; + fprintf( fs, "%*ssize_types: %d\n", level, "", map->size_types ); + fprintf( fs, "%*snum_types: %d\n", level, "", map->num_types ); + if( map->types != NULL ) + { + XkbKeyTypePtr type = map->types; + for( i = 0; i < map->num_types; i++ ) + { + fprintf( fs, "%*stypes[%d]:\n", level, "", i ); + _XkbKeyTypeDump( fs, level + 2, type++ ); + } + } else + fprintf( fs, "%*sNO types\n", level, "" ); + + fprintf( fs, "%*ssize_syms: %d\n", level, "", map->size_syms ); + fprintf( fs, "%*snum_syms: %d\n", level, "", map->num_syms ); + if( map->syms != NULL ) + { + for( i = 0; i < map->num_syms; i++ ) + fprintf( fs, "%*ssyms[%d]:0x%lX(%s)\n", level, "", i, map->syms[i], + XKeysymToString( map->syms[i] ) ); + } else + fprintf( fs, "%*sNO syms\n", level, "" ); + if( map->key_sym_map != NULL ) + { + XkbSymMapPtr ksm = map->key_sym_map; + for( i = 0; i <= kbd->max_key_code; i++ ) + { + fprintf( fs, "%*skey_sym_map[%d]:\n", level, "", i ); + _XkbSymMapDump( fs, level + 2, ksm++ ); + } + } else + fprintf( fs, "%*sNO key_sym_map\n", level, "" ); +} + +void _XkbDescDump( FILE * fs, int level, XkbDescPtr kbd ) +{ + fprintf( fs, "%*sflags: 0x%X\n", level, "", kbd->flags ); + fprintf( fs, "%*sdevice_spec: %d\n", level, "", kbd->device_spec ); + fprintf( fs, "%*smin_key_code: %d\n", level, "", kbd->min_key_code ); + fprintf( fs, "%*smax_key_code: %d\n", level, "", kbd->max_key_code ); +#if 0 + if( kbd->ctrls != NULL ) + { + fprintf( fs, "ctrls:\n" ); + _XkbControlsDump( fs, kbd->ctrls ); + } else + fprintf( fs, "NO server\n" ); +#endif + if( kbd->server != NULL ) + { + fprintf( fs, "%*sserver:\n", level, "" ); + _XkbServerMapDump( fs, level + 2, kbd->server, kbd ); + } else + fprintf( fs, "%*sNO server\n", level, "" ); + + if( kbd->map != NULL ) + { + fprintf( fs, "%*smap:\n", level, "" ); + _XkbClientMapDump( fs, level + 2, kbd->map, kbd ); + } else + fprintf( fs, "%*sNO map\n", level, "" ); +} + +void XklDumpXkbDesc( const char *filename, XkbDescPtr kbd ) +{ + FILE *fs = fopen( filename, "w+" ); + if( fs != NULL ) + { + _XkbDescDump( fs, 0, kbd == NULL ? _xklXkb : kbd ); + fclose( fs ); + } + +} diff --git a/libxklavier/xklavier_evt.c b/libxklavier/xklavier_evt.c new file mode 100644 index 0000000..8ae9944 --- /dev/null +++ b/libxklavier/xklavier_evt.c @@ -0,0 +1,473 @@ +#include <time.h> + +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#include <X11/Xlibint.h> + +#include "xklavier_private.h" + +int XklFilterEvents( XEvent * xev ) +{ + XAnyEvent *pe = ( XAnyEvent * ) xev; + XklDebug( 400, "**> Filtering event %d of type %d from window %d\n", + pe->serial, pe->type, pe->window ); + if( xev->type == _xklXkbEventType ) + { + _XklXkbEvHandler( ( XkbEvent * ) xev ); + } else + switch ( xev->type ) + { /* core events */ + case FocusIn: + _XklFocusInEvHandler( &xev->xfocus ); + break; + case FocusOut: + _XklFocusOutEvHandler( &xev->xfocus ); + break; + case PropertyNotify: + _XklPropertyEvHandler( &xev->xproperty ); + break; + case CreateNotify: + _XklCreateEvHandler( &xev->xcreatewindow ); + break; + case DestroyNotify: + XklDebug( 150, "Window " WINID_FORMAT " destroyed\n", + xev->xdestroywindow.window ); + break; + case UnmapNotify: + XklDebug( 200, "UnmapNotify\n" ); + break; + case MapNotify: + XklDebug( 200, "MapNotify\n" ); + break; + case MappingNotify: + XklDebug( 200, "MappingNotify\n" ); + _XklFreeAllInfo( ); + _XklLoadAllInfo( ); + break; + case GravityNotify: + XklDebug( 200, "GravityNotify\n" ); + break; + case ReparentNotify: + XklDebug( 200, "ReparentNotify\n" ); + break; /* Ignore these events */ + default: + { + const char *name = _XklGetEventName( xev->type ); + XklDebug( 200, "Unknown event %d [%s]\n", xev->type, + ( name == NULL ? "??" : name ) ); + return 1; + } + } + XklDebug( 400, "Filtered event %d of type %d from window %d **>\n", + pe->serial, pe->type, pe->window ); + return 0; +} + +/** + * Some common functionality for Xkb handler + */ +void _XklStdXkbHandler( int grp, XklStateChange changeType, unsigned inds, + Bool setInds ) +{ + Window focused, focusedApp; + XklState oldState; + int revert; + Bool haveState; + Bool setGroup = changeType == GROUP_CHANGED; + + XGetInputFocus( _xklDpy, &focused, &revert ); + + if( ( focused == None ) || ( focused == PointerRoot ) ) + { + XklDebug( 160, "Something with focus: " WINID_FORMAT "\n", focused ); + return; + } + + if( !_XklGetAppWindow( focused, &focusedApp ) ) + focusedApp = _xklCurClient; //what else can I do + + XklDebug( 150, "Focused window: " WINID_FORMAT ", '%s'\n", focusedApp, + _XklGetDebugWindowTitle( focusedApp ) ); + XklDebug( 150, "CurClient: " WINID_FORMAT ", '%s'\n", _xklCurClient, + _XklGetDebugWindowTitle( _xklCurClient ) ); + + if( focusedApp != _xklCurClient ) + { + _xklCurState.group = grp; + _xklCurState.indicators = inds; + + _XklAddAppWindow( focusedApp, ( Window ) NULL, False, &_xklCurState ); + _xklCurClient = focusedApp; + XklDebug( 160, "CurClient:changed to " WINID_FORMAT ", '%s'\n", + _xklCurClient, _XklGetDebugWindowTitle( _xklCurClient ) ); + } + // if the window already has this this state - we are just restoring it! + // (see the second parameter of stateCallback + haveState = _XklGetAppState( _xklCurClient, &oldState ); + + if( setGroup || haveState ) + { + _xklCurState.group = setGroup ? grp : oldState.group; + _xklCurState.indicators = setInds ? inds : oldState.indicators; + } + + if( haveState ) + _XklTryCallStateCallback( changeType, &oldState ); + + _XklSaveAppState( _xklCurClient, &_xklCurState ); +} + +/** + * XKB event handler + */ +void _XklXkbEvHandler( XkbEvent * kev ) +{ + int i; + unsigned bit; + unsigned inds; + + XklDebug( 150, "Xkb event detected\n" ); + + switch ( kev->any.xkb_type ) + { + case XkbStateNotify: +#define GROUP_CHANGE_MASK \ + ( XkbGroupStateMask | XkbGroupBaseMask | XkbGroupLatchMask | XkbGroupLockMask ) + + XklDebug( 150, + "XkbStateNotify detected, changes: %X/(mask %X), new group %d\n", + kev->state.changed, GROUP_CHANGE_MASK, + kev->state.locked_group ); + + if( kev->state.changed & GROUP_CHANGE_MASK ) + _XklStdXkbHandler( kev->state.locked_group, GROUP_CHANGED, 0, False ); + else + XklDebug( 200, + "This type of state notification is not regarding groups\n" ); + + break; + + case XkbIndicatorStateNotify: + + XklDebug( 150, "XkbIndicatorStateNotify\n" ); + + inds = _xklCurState.indicators; + + ForPhysIndicators( i, bit ) if( kev->indicators.changed & bit ) + { + if( kev->indicators.state & bit ) + inds |= bit; + else + inds &= ~bit; + } + + _XklStdXkbHandler( 0, INDICATORS_CHANGED, inds, True ); + break; + + case XkbIndicatorMapNotify: + XklDebug( 150, "XkbIndicatorMapNotify\n" ); + _XklFreeAllInfo( ); + _XklLoadAllInfo( ); + break; + + case XkbControlsNotify: + XklDebug( 150, "XkbControlsNotify\n" ); + _XklFreeAllInfo( ); + _XklLoadAllInfo( ); + break; + + case XkbNamesNotify: + XklDebug( 150, "XkbNamesNotify\n" ); + _XklFreeAllInfo( ); + _XklLoadAllInfo( ); + break; + + case XkbNewKeyboardNotify: + XklDebug( 150, "XkbNewKeyboardNotify\n" ); + _XklFreeAllInfo( ); + _XklLoadAllInfo( ); + break; + + default: + XklDebug( 150, "Unknown xkb event %d\n", kev->any.xkb_type ); + break; + } +} + +/** + * FocusIn handler + */ +void _XklFocusInEvHandler( XFocusChangeEvent * fev ) +{ + Window win; + Window appWin; + XklState selectedWindowState; + + win = fev->window; + + switch ( fev->mode ) + { + case NotifyNormal: + case NotifyWhileGrabbed: + break; + default: + XklDebug( 160, + "Window " WINID_FORMAT + " has got focus during special action %d\n", win, fev->mode ); + return; + } + + XklDebug( 150, "Window " WINID_FORMAT ", '%s' has got focus\n", win, + _XklGetDebugWindowTitle( win ) ); + + if( !_XklGetAppWindow( win, &appWin ) ) + { + return; + } + + XklDebug( 150, "Appwin " WINID_FORMAT ", '%s' has got focus\n", appWin, + _XklGetDebugWindowTitle( appWin ) ); + + if( XklGetState( appWin, &selectedWindowState ) ) + { + if( _xklCurClient != appWin ) + { + Bool transparent; + _xklCurClient = appWin; + XklDebug( 150, "CurClient:changed to " WINID_FORMAT ", '%s'\n", + _xklCurClient, _XklGetDebugWindowTitle( _xklCurClient ) ); + + transparent = _XklIsTransparentAppWindow( appWin ); + if( transparent ) + XklDebug( 150, "Entering transparent window\n" ); + if( XklIsGroupPerApp( ) && !transparent ) + { + // We skip restoration only if we return to the same app window + Bool doSkip = False; + if( _xklSkipOneRestore ) + { + _xklSkipOneRestore = False; + if( appWin == _xklPrevAppWindow ) + doSkip = True; + } + + if( doSkip ) + { + XklDebug( 150, + "Skipping one restore as requested - instead, saving the current group into the window state\n" ); + _XklSaveAppState( appWin, &_xklCurState ); + } else + { + if( _xklCurState.group != selectedWindowState.group ) + { + XklDebug( 150, + "Restoring the group from %d to %d after gaining focus\n", + _xklCurState.group, selectedWindowState.group ); + XklLockGroup( selectedWindowState.group ); + } else + { + XklDebug( 150, + "Both old and new focused window have state %d so no point restoring it\n", + selectedWindowState.group ); + _xklAllowSecondaryGroupOnce = False; + } + } + + if( XklGetIndicatorsHandling( ) ) + { + int i; + unsigned bit; + + XklDebug( 150, + "Restoring the indicators from %X to %X after gaining focus\n", + _xklCurState.indicators, selectedWindowState.indicators ); + ForPhysIndicators( i, + bit ) if( _xklXkb->names->indicators[i] != None ) + { + Bool status; + status = + _XklSetIndicator( i, + ( selectedWindowState.indicators & bit ) != + 0 ); + XklDebug( 150, "Set indicator \"%s\"/%d to %d: %d\n", + _xklIndicatorNames[i], _xklXkb->names->indicators[i], + selectedWindowState.indicators & bit, status ); + } + } else + XklDebug( 150, + "Not restoring the indicators %X after gaining focus: indicator handling is not enabled\n", + _xklCurState.indicators ); + } else + XklDebug( 150, + "Not restoring the group %d after gaining focus: global layout (or transparent window)\n", + _xklCurState.group ); + } else + XklDebug( 150, "Same app window - just do nothing\n" ); + } else + { + XklDebug( 150, "But it does not have xklavier_state\n" ); + if( _XklHasWmState( win ) ) + { + XklDebug( 150, "But it does have wm_state so we'll add it\n" ); + _xklCurClient = appWin; + XklDebug( 150, "CurClient:changed to " WINID_FORMAT ", '%s'\n", + _xklCurClient, _XklGetDebugWindowTitle( _xklCurClient ) ); + _XklAddAppWindow( _xklCurClient, ( Window ) NULL, False, + &_xklCurState ); + } else + XklDebug( 150, "And it does have wm_state either\n" ); + } +} + +/** + * FocusOut handler + */ +void _XklFocusOutEvHandler( XFocusChangeEvent * fev ) +{ + if( fev->mode != NotifyNormal ) + { + XklDebug( 200, + "Window " WINID_FORMAT + " has lost focus during special action %d\n", fev->window, + fev->mode ); + return; + } + + XklDebug( 160, "Window " WINID_FORMAT ", '%s' has lost focus\n", + fev->window, _XklGetDebugWindowTitle( fev->window ) ); + + if( XklIsTransparent( fev->window ) ) + { + + XklDebug( 150, "Leaving transparent window!\n" ); +/** + * If we are leaving the transparent window - we skip the restore operation. + * This is useful for secondary groups switching from the transparent control + * window. + */ + _xklSkipOneRestore = True; + } else + { + Window p; + if( _XklGetAppWindow( fev->window, &p ) ) + _xklPrevAppWindow = p; + } + +} + +/** + * PropertyChange handler + * Interested in WM_STATE property only + */ +void _XklPropertyEvHandler( XPropertyEvent * pev ) +{ + if( 400 <= _xklDebugLevel ) + { + char *atomName = XGetAtomName( _xklDpy, pev->atom ); + if( atomName != NULL ) + { + XklDebug( 400, "The property '%s' changed for " WINID_FORMAT "\n", + atomName, pev->window ); + XFree( atomName ); + } else + { + XklDebug( 200, "Some magic property changed for " WINID_FORMAT "\n", + pev->window ); + } + } + + if( pev->atom == _xklAtoms[WM_STATE] ) + { + Bool hasXklState = XklGetState( pev->window, NULL ); + + if( pev->state == PropertyNewValue ) + { + XklDebug( 160, "New value of WM_STATE on window " WINID_FORMAT "\n", + pev->window ); + if( !hasXklState ) /* Is this event the first or not? */ + { + _XklAddAppWindow( pev->window, ( Window ) NULL, False, + &_xklCurState ); + } + } else + { /* ev->xproperty.state == PropertyDelete, either client or WM can remove it, ICCCM 4.1.3.1 */ + XklDebug( 160, "Something (%d) happened to WM_STATE of window 0x%x\n", + pev->state, pev->window ); + _XklSelectInputMerging( pev->window, PropertyChangeMask ); + if( hasXklState ) + { + XklDelState( pev->window ); + } + } + } else + if( pev->atom == _xklAtoms[XKB_RF_NAMES_PROP_ATOM] + && pev->window == _xklRootWindow ) + { + if( pev->state == PropertyNewValue ) + { + XklDebug( 160, "New value of XKB_RF_NAMES_PROP_ATOM on root window\n" ); + // If root window got new _XKB_RF_NAMES_PROP_ATOM - + // it most probably means new xkb is loaded by somebody + _XklFreeAllInfo( ); + _XklLoadAllInfo( ); + } + } +} + +/** + * * CreateNotify handler. Just interested in properties and focus events... + * */ +void _XklCreateEvHandler( XCreateWindowEvent * cev ) +{ + long newmask; + XklDebug( 200, + "Under-root window " WINID_FORMAT + "/%s (%d,%d,%d x %d) is created\n", cev->window, + _XklGetDebugWindowTitle( cev->window ), cev->x, cev->y, + cev->width, cev->height ); + + if( !cev->override_redirect ) + { +/* ICCCM 4.1.6: override-redirect is NOT private to +* client (and must not be changed - am I right?) +* We really need only PropertyChangeMask on this window but even in the case of +* local server we can lose PropertyNotify events (the trip time for this CreateNotify +* event + SelectInput request is not zero) and we definitely will (my system DO) +* lose FocusIn/Out events after the following call of PropertyNotifyHandler. +* So I just decided to purify this extra FocusChangeMask in the FocusIn/OutHandler. */ + _XklSelectInputMerging( cev->window, + PropertyChangeMask | FocusChangeMask ); + + if( _XklHasWmState( cev->window ) ) + { + XklDebug( 200, + "Just created window already has WM_STATE - so I'll add it" ); + _XklAddAppWindow( cev->window, ( Window ) NULL, False, &_xklCurState ); + } + } +} + +/** + * Just error handler - sometimes we get BadWindow error for already gone + * windows, so we'll just ignore + */ +void _XklErrHandler( Display * dpy, XErrorEvent * evt ) +{ + _xklLastErrorCode = evt->error_code; + switch ( _xklLastErrorCode ) + { + case BadWindow: + case BadAccess: + { + // in most cases this means we are late:) + XklDebug( 200, "ERROR: %p, " WINID_FORMAT ", %d, %d, %d\n", + dpy, + ( unsigned long ) evt->resourceid, + ( int ) evt->error_code, + ( int ) evt->request_code, ( int ) evt->minor_code ); + break; + } + default: + ( *_xklDefaultErrHandler ) ( dpy, evt ); + } +} diff --git a/libxklavier/xklavier_private.h b/libxklavier/xklavier_private.h new file mode 100644 index 0000000..7735f86 --- /dev/null +++ b/libxklavier/xklavier_private.h @@ -0,0 +1,139 @@ +#ifndef __XKLAVIER_PRIVATE_H__ +#define __XKLAVIER_PRIVATE_H__ + +#include <stdio.h> + +#include <libxklavier/xklavier_config.h> + +#include <X11/extensions/XKBrules.h> + +extern void _XklGetRealState( XklState * curState_return ); +extern void _XklAddAppWindow( Window win, Window parent, Bool force, + XklState * initState ); +extern Bool _XklGetAppWindowBottomToTop( Window win, Window * appWin_return ); +extern Bool _XklGetAppWindow( Window win, Window * appWin_return ); + +extern void _XklStdXkbHandler( int grp, XklStateChange changeType, + unsigned inds, Bool setInds ); +extern void _XklXkbEvHandler( XkbEvent * kev ); +extern void _XklFocusInEvHandler( XFocusChangeEvent * fev ); +extern void _XklFocusOutEvHandler( XFocusChangeEvent * fev ); +extern void _XklPropertyEvHandler( XPropertyEvent * rev ); +extern void _XklCreateEvHandler( XCreateWindowEvent * cev ); + +extern void _XklErrHandler( Display * dpy, XErrorEvent * evt ); + +extern Window _XklGetRegisteredParent( Window win ); +extern Bool _XklLoadAllInfo( void ); +extern void _XklFreeAllInfo( void ); +extern Bool _XklLoadWindowTree( void ); +extern Bool _XklLoadSubtree( Window window, int level, XklState * initState ); + +extern Bool _XklHasWmState( Window win ); + +extern Bool _XklGetAppState( Window appWin, XklState * state_return ); +extern void _XklDelAppState( Window appWin ); +extern void _XklSaveAppState( Window appWin, XklState * state ); + +extern void _XklSelectInputMerging( Window win, long mask ); + +extern char *_XklGetDebugWindowTitle( Window win ); + +extern Status _XklStatusQueryTree( Display * display, + Window w, + Window * root_return, + Window * parent_return, + Window ** children_return, + signed int *nchildren_return ); + +extern Bool _XklSetIndicator( int indicatorNum, Bool set ); + +extern void _XklTryCallStateCallback( XklStateChange changeType, + XklState * oldState ); + +extern void _XklI18NInit( ); + +extern char *_XklLocaleFromUtf8( const char *utf8string ); + +extern int _XklGetLanguagePriority( const char *language ); + +extern char *_XklConfigRecMergeByComma( const char **array, + const int arrayLength ); + +extern char *_XklConfigRecMergeLayouts( const XklConfigRecPtr data ); + +extern char *_XklConfigRecMergeVariants( const XklConfigRecPtr data ); + +extern char *_XklConfigRecMergeOptions( const XklConfigRecPtr data ); + +extern void _XklConfigRecSplitByComma( char ***array, + int *arraySize, const char *merged ); + +extern void _XklConfigRecSplitLayouts( XklConfigRecPtr data, + const char *merged ); + +extern void _XklConfigRecSplitVariants( XklConfigRecPtr data, + const char *merged ); + +extern void _XklConfigRecSplitOptions( XklConfigRecPtr data, + const char *merged ); + +extern void XklDumpXkbDesc( const char *filename, XkbDescPtr kbd ); + +extern const char *_XklGetEventName( int type ); + +extern Bool _XklIsTransparentAppWindow( Window appWin ); + +extern Display *_xklDpy; + +extern Bool _xklXkbExtPresent; + +extern Window _xklRootWindow; + +extern XkbDescPtr _xklXkb; + +extern XklState _xklCurState; + +extern Window _xklCurClient; + +extern Status _xklLastErrorCode; + +extern const char *_xklLastErrorMsg; + +extern XErrorHandler _xklDefaultErrHandler; + +extern char *_xklIndicatorNames[]; + +#define ForPhysIndicators( i, bit ) \ + for ( i=0, bit=1; i<XkbNumIndicators; i++, bit<<=1 ) \ + if ( _xklXkb->indicators->phys_indicators & bit ) + +extern int _xklXkbEventType, _xklXkbError; + +#define WM_NAME 0 +#define WM_STATE 1 +#define XKLAVIER_STATE 2 +#define XKLAVIER_TRANSPARENT 3 +#define XKB_RF_NAMES_PROP_ATOM 4 +#define XKB_RF_NAMES_PROP_ATOM_BACKUP 5 +#define TOTAL_ATOMS 6 + +extern Atom _xklAtoms[]; + +extern XkbRF_VarDefsRec _xklVarDefs; + +extern Bool _xklAllowSecondaryGroupOnce; + +extern int _xklDefaultGroup; + +extern Bool _xklSkipOneRestore; + +extern int _xklSecondaryGroupMask; + +extern int _xklDebugLevel; + +extern Window _xklPrevAppWindow; + +#define WINID_FORMAT "%lx" + +#endif diff --git a/libxklavier/xklavier_props.c b/libxklavier/xklavier_props.c new file mode 100644 index 0000000..79b305a --- /dev/null +++ b/libxklavier/xklavier_props.c @@ -0,0 +1,359 @@ +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <locale.h> + +#include <X11/Xlib.h> +#include <X11/XKBlib.h> +#include <X11/Xatom.h> +#include <X11/extensions/XKBfile.h> +#include <X11/extensions/XKBrules.h> +#include <X11/extensions/XKM.h> + +#include <libxml/xpath.h> + +#include "config.h" + +#include "xklavier.h" +#include "xklavier_config.h" +#include "xklavier_private.h" + +void XklConfigRecInit( XklConfigRecPtr data ) +{ + // clear the structure VarDefsPtr... + memset( ( void * ) data, 0, sizeof( XklConfigRec ) ); +} + +void XklConfigRecDestroy( XklConfigRecPtr data ) +{ + int i; + char **p; + + if( data->model != NULL ) + free( data->model ); + + if( ( p = data->layouts ) != NULL ) + { + for( i = data->numLayouts; --i >= 0; ) + free( *p++ ); + free( data->layouts ); + } + + if( ( p = data->variants ) != NULL ) + { + for( i = data->numVariants; --i >= 0; ) + free( *p++ ); + free( data->variants ); + } + + if( ( p = data->options ) != NULL ) + { + for( i = data->numOptions; --i >= 0; ) + free( *p++ ); + free( data->options ); + } +} + +void XklConfigRecReset( XklConfigRecPtr data ) +{ + XklConfigRecDestroy( data ); + XklConfigRecInit( data ); +} + +Bool XklConfigGetFromServer( XklConfigRecPtr data ) +{ + char *rulesFile = NULL; + Bool rv = + XklGetNamesProp( _xklAtoms[XKB_RF_NAMES_PROP_ATOM], &rulesFile, data ); + if( rulesFile != NULL ) + free( rulesFile ); + + return rv; +} + +Bool XklConfigGetFromBackup( XklConfigRecPtr data ) +{ + char *rulesFile = NULL; + Bool rv = + XklGetNamesProp( _xklAtoms[XKB_RF_NAMES_PROP_ATOM_BACKUP], &rulesFile, + data ); + if( rulesFile != NULL ) + free( rulesFile ); + + return rv; +} + +// taken from XFree86 maprules.c +Bool XklGetNamesProp( Atom rulesAtom, + char **rulesFileOut, XklConfigRecPtr data ) +{ + Atom realPropType; + int fmt; + unsigned long nitems, extraBytes; + char *propData, *out; + Status rtrn; + + // no such atom! + if( rulesAtom == None ) /* property cannot exist */ + { + _xklLastErrorMsg = "Could not find the atom"; + return False; + } + + rtrn = + XGetWindowProperty( _xklDpy, _xklRootWindow, rulesAtom, 0L, + _XKB_RF_NAMES_PROP_MAXLEN, False, XA_STRING, + &realPropType, &fmt, &nitems, &extraBytes, + ( unsigned char ** ) &propData ); + // property not found! + if( rtrn != Success ) + { + _xklLastErrorMsg = "Could not get the property"; + return False; + } + // set rules file to "" + if( rulesFileOut ) + *rulesFileOut = NULL; + + // has to be array of strings + if( ( extraBytes > 0 ) || ( realPropType != XA_STRING ) || ( fmt != 8 ) ) + { + if( propData ) + XFree( propData ); + _xklLastErrorMsg = "Wrong property format"; + return False; + } + // rules file + out = propData; + if( out && ( *out ) && rulesFileOut ) + *rulesFileOut = strdup( out ); + out += strlen( out ) + 1; + + if( ( out - propData ) < nitems ) + { + if( *out ) + data->model = strdup( out ); + out += strlen( out ) + 1; + } + + if( ( out - propData ) < nitems ) + { + _XklConfigRecSplitLayouts( data, out ); + out += strlen( out ) + 1; + } + + if( ( out - propData ) < nitems ) + { + int i; + char **theLayout, **theVariant; + _XklConfigRecSplitVariants( data, out ); + /* + Now have to ensure that number of variants matches the number of layouts + The 'remainder' is filled with NULLs (not ""s!) + */ + if( data->numVariants < data->numLayouts ) + { + data->variants = + realloc( data->variants, data->numLayouts * sizeof( char * ) ); + memset( data->variants + data->numVariants, 0, + ( data->numLayouts - data->numVariants ) * sizeof( char * ) ); + data->numVariants = data->numLayouts; + } + // take variants from layouts like ru(winkeys) + theLayout = data->layouts; + theVariant = data->variants; + for( i = data->numLayouts; --i >= 0; theLayout++, theVariant++ ) + { + if( *theLayout != NULL ) + { + char *varstart = strchr( *theLayout, '(' ); + if( varstart != NULL ) + { + char *varend = strchr( varstart, ')' ); + if( varend != NULL ) + { + int varlen = varend - varstart; + int laylen = varstart - *theLayout; + // I am not sure - but I assume variants in layout have priority + char *var = *theVariant = ( *theVariant != NULL ) ? + realloc( *theVariant, varlen ) : malloc( varlen ); + memcpy( var, varstart + 1, --varlen ); + var[varlen] = '\0'; + realloc( *theLayout, laylen + 1 ); + ( *theLayout )[laylen] = '\0'; + } + } + } + } + out += strlen( out ) + 1; + } + + if( ( out - propData ) < nitems ) + { + _XklConfigRecSplitOptions( data, out ); +// out += strlen( out ) + 1; + } + XFree( propData ); + return True; +} + +// taken from XFree86 maprules.c +Bool XklSetNamesProp( Atom rulesAtom, + char *rulesFile, const XklConfigRecPtr data ) +{ + int len, i, rv; + char *pval; + char *next; + char *allLayouts = _XklConfigRecMergeLayouts( data ); + char *allVariants = _XklConfigRecMergeVariants( data ); + char *allOptions = _XklConfigRecMergeOptions( data ); + + len = ( rulesFile ? strlen( rulesFile ) : 0 ); + len += ( data->model ? strlen( data->model ) : 0 ); + len += ( allLayouts ? strlen( allLayouts ) : 0 ); + len += ( allVariants ? strlen( allVariants ) : 0 ); + len += ( allOptions ? strlen( allOptions ) : 0 ); + if( len < 1 ) + return True; + + len += 5; /* trailing NULs */ + + pval = next = ( char * ) malloc( len + 1 ); + if( !pval ) + { + _xklLastErrorMsg = "Could not allocate buffer"; + return False; + } + if( rulesFile ) + { + strcpy( next, rulesFile ); + next += strlen( rulesFile ); + } + *next++ = '\0'; + if( data->model ) + { + strcpy( next, data->model ); + next += strlen( data->model ); + } + *next++ = '\0'; + if( data->layouts ) + { + strcpy( next, allLayouts ); + next += strlen( allLayouts ); + } + *next++ = '\0'; + if( data->variants ) + { + strcpy( next, allVariants ); + next += strlen( allVariants ); + } + *next++ = '\0'; + if( data->options ) + { + strcpy( next, allOptions ); + next += strlen( allOptions ); + } + *next++ = '\0'; + if( ( next - pval ) != len ) + { + XklDebug( 150, "Illegal final position: %d/%d\n", ( next - pval ), len ); + if( allOptions != NULL ) + free( allOptions ); + free( pval ); + _xklLastErrorMsg = "Internal property parsing error"; + return False; + } + + rv = XChangeProperty( _xklDpy, _xklRootWindow, rulesAtom, XA_STRING, 8, + PropModeReplace, ( unsigned char * ) pval, len ); + XSync( _xklDpy, False ); +#if 0 + for( i = len - 1; --i >= 0; ) + if( pval[i] == '\0' ) + pval[i] = '?'; + XklDebug( 150, "Stored [%s] of length %d to [%s] of %X: %d\n", pval, len, + propName, _xklRootWindow, rv ); +#endif + if( allOptions != NULL ) + free( allOptions ); + free( pval ); + return True; +} + +Bool XklBackupNamesProp( ) +{ + char *rf; + XklConfigRec data; + Bool rv = True; + + XklConfigRecInit( &data ); + if( XklGetNamesProp + ( _xklAtoms[XKB_RF_NAMES_PROP_ATOM_BACKUP], &rf, &data ) ) + { + XklConfigRecDestroy( &data ); + if( rf != NULL ) + free( rf ); + return True; + } + // "backup" property is not defined + XklConfigRecReset( &data ); + if( XklGetNamesProp( _xklAtoms[XKB_RF_NAMES_PROP_ATOM], &rf, &data ) ) + { +#if 0 + int i; + XklDebug( 150, "Original model: [%s]\n", data.model ); + + XklDebug( 150, "Original layouts(%d):\n", data.numLayouts ); + for( i = data.numLayouts; --i >= 0; ) + XklDebug( 150, "%d: [%s]\n", i, data.layouts[i] ); + + XklDebug( 150, "Original variants(%d):\n", data.numVariants ); + for( i = data.numVariants; --i >= 0; ) + XklDebug( 150, "%d: [%s]\n", i, data.variants[i] ); + + XklDebug( 150, "Original options(%d):\n", data.numOptions ); + for( i = data.numOptions; --i >= 0; ) + XklDebug( 150, "%d: [%s]\n", i, data.options[i] ); +#endif + if( !XklSetNamesProp( _xklAtoms[XKB_RF_NAMES_PROP_ATOM_BACKUP], rf, &data ) ) + { + XklDebug( 150, "Could not backup the configuration" ); + rv = False; + } + if( rf != NULL ) + free( rf ); + } else + { + XklDebug( 150, "Could not get the configuration for backup" ); + rv = False; + } + XklConfigRecDestroy( &data ); + + return rv; +} + +Bool XklRestoreNamesProp( ) +{ + char *rf; + XklConfigRec data; + Bool rv = True; + + XklConfigRecInit( &data ); + if( !XklGetNamesProp( _xklAtoms[XKB_RF_NAMES_PROP_ATOM_BACKUP], &rf, &data ) ) + { + XklConfigRecDestroy( &data ); + return False; + } + + if( rf != NULL ) + free( rf ); + + if( !XklSetNamesProp( _xklAtoms[XKB_RF_NAMES_PROP_ATOM], rf, &data ) ) + { + XklDebug( 150, "Could not backup the configuration" ); + rv = False; + } + XklConfigRecDestroy( &data ); + + return rv; +} diff --git a/libxklavier/xklavier_util.c b/libxklavier/xklavier_util.c new file mode 100644 index 0000000..37b9595 --- /dev/null +++ b/libxklavier/xklavier_util.c @@ -0,0 +1,441 @@ +#include <time.h> + +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#include <X11/Xlibint.h> + +#include "xklavier_private.h" + +XklState *XklGetCurrentState( ) +{ + return &_xklCurState; +} + +const char *XklGetLastError( ) +{ + return _xklLastErrorMsg; +} + +char *XklGetWindowTitle( Window w ) +{ + Atom type_ret; + int format_ret; + unsigned long nitems, rest; + unsigned char *prop; + + if( Success == XGetWindowProperty( _xklDpy, w, _xklAtoms[WM_NAME], 0L, + -1L, False, XA_STRING, &type_ret, + &format_ret, &nitems, &rest, &prop ) ) + return prop; + else + return NULL; +} + +void XklLockGroup( int group ) +{ + XklDebug( 100, "Posted request for change the group to %d ##\n", group ); + XkbLockGroup( _xklDpy, XkbUseCoreKbd, group ); + XSync( _xklDpy, False ); +} + +Bool XklIsSameApp( Window win1, Window win2 ) +{ + Window app1, app2; + return _XklGetAppWindow( win1, &app1 ) && + _XklGetAppWindow( win2, &app2 ) && app1 == app2; +} + +Bool XklGetState( Window win, XklState * state_return ) +{ + Window appWin; + + if( !_XklGetAppWindow( win, &appWin ) ) + { + if( state_return != NULL ) + state_return->group = -1; + return False; + } + + return _XklGetAppState( appWin, state_return ); +} + +void XklDelState( Window win ) +{ + Window appWin; + + if( _XklGetAppWindow( win, &appWin ) ) + _XklDelAppState( appWin ); +} + +void XklSaveState( Window win, XklState * state ) +{ + Window appWin; + + if( _XklGetAppWindow( win, &appWin ) ) + _XklSaveAppState( appWin, state ); +} + +/** + * Updates current internal state from X state + */ +void _XklGetRealState( XklState * curState_return ) +{ + XkbStateRec state; + + curState_return->group = 0; + if( Success == XkbGetState( _xklDpy, XkbUseCoreKbd, &state ) ) + curState_return->group = state.locked_group; + + if( Success == + XkbGetIndicatorState( _xklDpy, XkbUseCoreKbd, + &curState_return->indicators ) ) + curState_return->indicators &= _xklXkb->indicators->phys_indicators; + else + curState_return->indicators = 0; + +} + +/** + * Prepares the name of window suitable for debugging (32characters long). + */ +char *_XklGetDebugWindowTitle( Window win ) +{ + static char sname[33]; + char *name; + strcpy( sname, "NULL" ); + if( win != ( Window ) NULL ) + { + name = XklGetWindowTitle( win ); + if( name != NULL ) + { + snprintf( sname, sizeof( sname ), "%.32s", name ); + XFree( name ); + } + } + return sname; +} + +Window XklGetCurrentWindow( ) +{ + return _xklCurClient; +} + +/** + * Loads subtree. + * All the windows with WM_STATE are added. + * All the windows within level 0 are listened for focus and property + */ +Bool _XklLoadSubtree( Window window, int level, XklState * initState ) +{ + Window rwin = ( Window ) NULL, + parent = ( Window ) NULL, *children = NULL, *child; + int num = 0; + Bool retval = True; + + _xklLastErrorCode = + _XklStatusQueryTree( _xklDpy, window, &rwin, &parent, &children, &num ); + + if( _xklLastErrorCode != Success ) + { + return False; + } + + child = children; + while( num ) + { + XklDebug( 150, "Looking at child " WINID_FORMAT " '%s'\n", *child, + _XklGetDebugWindowTitle( *child ) ); + if( _XklHasWmState( *child ) ) + { + XklDebug( 150, "It has WM_STATE so we'll add it\n" ); + _XklAddAppWindow( *child, window, True, initState ); + } else + { + XklDebug( 150, "It does not have have WM_STATE so we'll not add it\n" ); + + if( level == 0 ) + { + XklDebug( 150, "But we are at level 0 so we'll spy on it\n" ); + _XklSelectInputMerging( *child, + FocusChangeMask | PropertyChangeMask ); + } else + XklDebug( 150, "And we are at level %d so we'll not spy on it\n", + level ); + + retval = _XklLoadSubtree( *child, level + 1, initState ); + } + + child++; + num--; + } + + if( children != NULL ) + XFree( children ); + + return retval; +} + +/** + * Checks whether given window has WM_STATE property (i.e. "App window"). + */ +Bool _XklHasWmState( Window win ) +{ /* ICCCM 4.1.3.1 */ + Atom type = None; + int format; + unsigned long nitems; + unsigned long after; + unsigned char *data = NULL; /* Helps in the case of BadWindow error */ + + XGetWindowProperty( _xklDpy, win, _xklAtoms[WM_STATE], 0, 0, False, + _xklAtoms[WM_STATE], &type, &format, &nitems, &after, + &data ); + if( data != NULL ) + XFree( data ); /* To avoid an one-byte memory leak because after successfull return + * data array always contains at least one nul byte (NULL-equivalent) */ + return type != None; +} + +/** + * Finds out the official parent window (accortind to XQueryTree) + */ +Window _XklGetRegisteredParent( Window win ) +{ + Window parent = ( Window ) NULL, rw = ( Window ) NULL, *children = NULL; + unsigned nchildren = 0; + + _xklLastErrorCode = + _XklStatusQueryTree( _xklDpy, win, &rw, &parent, &children, &nchildren ); + + if( children != NULL ) + XFree( children ); + + return _xklLastErrorCode == Success ? parent : ( Window ) NULL; +} + +/** + * Make sure about the result. Origial XQueryTree is pretty stupid beast:) + */ +Status _XklStatusQueryTree( Display * display, + Window w, + Window * root_return, + Window * parent_return, + Window ** children_return, + signed int *nchildren_return ) +{ + Bool result; + + result = ( Bool ) XQueryTree( display, + w, + root_return, + parent_return, + children_return, nchildren_return ); + if( !result ) + { + XklDebug( 160, + "Could not get tree info for window " WINID_FORMAT ": %d\n", w, + result ); + _xklLastErrorMsg = "Could not get the tree info"; + } + + return result ? Success : FirstExtensionError; +} + +/* + * Actually taken from mxkbledpanel, valueChangedProc + */ +Bool _XklSetIndicator( int indicatorNum, Bool set ) +{ + XkbIndicatorMapPtr map; + + map = _xklXkb->indicators->maps + indicatorNum; + + /* The 'flags' field tells whether this indicator is automatic + * (XkbIM_NoExplicit - 0x80), explicit (XkbIM_NoAutomatic - 0x40), + * or neither (both - 0xC0). + * + * If NoAutomatic is set, the server ignores the rest of the + * fields in the indicator map (i.e. it disables automatic control + * of the LED). If NoExplicit is set, the server prevents clients + * from explicitly changing the value of the LED (using the core + * protocol *or* XKB). If NoAutomatic *and* NoExplicit are set, + * the LED cannot be changed (unless you change the map first). + * If neither NoAutomatic nor NoExplicit are set, the server will + * change the LED according to the indicator map, but clients can + * override that (until the next automatic change) using the core + * protocol or XKB. + */ + switch ( map->flags & ( XkbIM_NoExplicit | XkbIM_NoAutomatic ) ) + { + case XkbIM_NoExplicit | XkbIM_NoAutomatic: + { + // Can do nothing. Just ignore the indicator + return True; + } + + case XkbIM_NoAutomatic: + { + if( _xklXkb->names->indicators[indicatorNum] != None ) + XkbSetNamedIndicator( _xklDpy, XkbUseCoreKbd, + _xklXkb->names->indicators[indicatorNum], set, + False, NULL ); + else + { + XKeyboardControl xkc; + xkc.led = indicatorNum; + xkc.led_mode = set ? LedModeOn : LedModeOff; + XChangeKeyboardControl( _xklDpy, KBLed | KBLedMode, &xkc ); + XSync( _xklDpy, 0 ); + } + + return True; + } + + case XkbIM_NoExplicit: + break; + } + + /* The 'ctrls' field tells what controls tell this indicator to + * to turn on: RepeatKeys (0x1), SlowKeys (0x2), BounceKeys (0x4), + * StickyKeys (0x8), MouseKeys (0x10), AccessXKeys (0x20), + * TimeOut (0x40), Feedback (0x80), ToggleKeys (0x100), + * Overlay1 (0x200), Overlay2 (0x400), GroupsWrap (0x800), + * InternalMods (0x1000), IgnoreLockMods (0x2000), + * PerKeyRepeat (0x3000), or ControlsEnabled (0x4000) + */ + if( map->ctrls ) + { + unsigned long which = map->ctrls; + + XkbGetControls( _xklDpy, XkbAllControlsMask, _xklXkb ); + if( set ) + _xklXkb->ctrls->enabled_ctrls |= which; + else + _xklXkb->ctrls->enabled_ctrls &= ~which; + XkbSetControls( _xklDpy, which | XkbControlsEnabledMask, _xklXkb ); + } + + /* The 'which_groups' field tells when this indicator turns on + * * for the 'groups' field: base (0x1), latched (0x2), locked (0x4), + * * or effective (0x8). + * */ + if( map->groups ) + { + int i; + unsigned int group = 1; + + /* Turning on a group indicator is kind of tricky. For + * now, we will just Latch or Lock the first group we find + * if that is what this indicator does. Otherwise, we're + * just going to punt and get out of here. + */ + if( set ) + { + for( i = XkbNumKbdGroups; --i >= 0; ) + if( ( 1 << i ) & map->groups ) + { + group = i; + break; + } + if( map->which_groups & ( XkbIM_UseLocked | XkbIM_UseEffective ) ) + { + // Important: Groups should be ignored here - because they are handled separately! + // XklLockGroup( group ); + } else if( map->which_groups & XkbIM_UseLatched ) + XkbLatchGroup( _xklDpy, XkbUseCoreKbd, group ); + else + { + // Can do nothing. Just ignore the indicator + return True; + } + } else + /* Turning off a group indicator will mean that we just + * Lock the first group that this indicator doesn't watch. + */ + { + for( i = XkbNumKbdGroups; --i >= 0; ) + if( !( ( 1 << i ) & map->groups ) ) + { + group = i; + break; + } + XklLockGroup( group ); + } + } + + /* The 'which_mods' field tells when this indicator turns on + * for the modifiers: base (0x1), latched (0x2), locked (0x4), + * or effective (0x8). + * + * The 'real_mods' field tells whether this turns on when one of + * the real X modifiers is set: Shift (0x1), Lock (0x2), Control (0x4), + * Mod1 (0x8), Mod2 (0x10), Mod3 (0x20), Mod4 (0x40), or Mod5 (0x80). + * + * The 'virtual_mods' field tells whether this turns on when one of + * the virtual modifiers is set. + * + * The 'mask' field tells what real X modifiers the virtual_modifiers + * map to? + */ + if( map->mods.real_mods || map->mods.mask ) + { + unsigned int affect, mods; + + affect = ( map->mods.real_mods | map->mods.mask ); + + mods = set ? affect : 0; + + if( map->which_mods & ( XkbIM_UseLocked | XkbIM_UseEffective ) ) + XkbLockModifiers( _xklDpy, XkbUseCoreKbd, affect, mods ); + else if( map->which_mods & XkbIM_UseLatched ) + XkbLatchModifiers( _xklDpy, XkbUseCoreKbd, affect, mods ); + else + { + return True; + } + } + + return True; +} + +const char *_XklGetEventName( int type ) +{ + // Not really good to use the fact of consecutivity + // but X protocol is already standartized so... + static const char *evtNames[] = { + "KeyPress", + "KeyRelease", + "ButtonPress", + "ButtonRelease", + "MotionNotify", + "EnterNotify", + "LeaveNotify", + "FocusIn", + "FocusOut", + "KeymapNotify", + "Expose", + "GraphicsExpose", + "NoExpose", + "VisibilityNotify", + "CreateNotify", + "DestroyNotify", + "UnmapNotify", + "MapNotify", + "MapRequest", + "ReparentNotify", + "ConfigureNotify", + "ConfigureRequest", + "GravityNotify", + "ResizeRequest", + "CirculateNotify", + "CirculateRequest", + "PropertyNotify", + "SelectionClear", + "SelectionRequest", + "SelectionNotify", + "ColormapNotify", "ClientMessage", "MappingNotify", "LASTEvent" + }; + type -= KeyPress; + if( type < 0 || type > ( sizeof( evtNames ) / sizeof( evtNames[0] ) ) ) + return NULL; + return evtNames[type]; +} |