Compare commits
37 Commits
5ae0945015
...
e69883d0ad
| Author | SHA1 | Date | |
|---|---|---|---|
| e69883d0ad | |||
| 9051c69b49 | |||
| 9566995c07 | |||
| 1f9af6ee3e | |||
| bae39c0188 | |||
| 4e90fdd5c7 | |||
| de536da0bb | |||
| 8d68e00f2b | |||
| a8a6c3b455 | |||
| 0d06ba43cd | |||
| 91931196e2 | |||
| 211a6917b2 | |||
| 4e8720a40d | |||
| 71af269efe | |||
| 3f5e107230 | |||
| c41a1b4227 | |||
| 368e6adcd4 | |||
| 5927f6a5e1 | |||
| 9d60c4bbb0 | |||
| c7d4f16d3e | |||
| a03efcf6bf | |||
| 432df3730c | |||
| 4120f90736 | |||
| 9c43bcb75c | |||
| 1d8d711882 | |||
| 1295480858 | |||
| 103f2ec448 | |||
| cec62cc40f | |||
| bc5712b6ac | |||
| 5ba5cf209c | |||
| 9c244d1dd1 | |||
| fabce14933 | |||
| d0def814ef | |||
| d2fa51ceba | |||
| f0ba7fc5f6 | |||
| 6dfbc08704 | |||
| ec7e6eb992 |
65
.gitignore
vendored
65
.gitignore
vendored
@@ -1,59 +1,10 @@
|
|||||||
strans
|
/*.o
|
||||||
strans-xim
|
/strans
|
||||||
|
/tests/*.o
|
||||||
/tests/unit_test
|
/tests/unit_test
|
||||||
|
/xim/*.o
|
||||||
|
/xim/strans-xim
|
||||||
|
/xim/xim_test
|
||||||
|
/gtk/im-strans.so
|
||||||
/bench/bench
|
/bench/bench
|
||||||
|
/bench/perf.data*
|
||||||
# Prerequisites
|
|
||||||
*.d
|
|
||||||
|
|
||||||
# Object files
|
|
||||||
*.o
|
|
||||||
*.ko
|
|
||||||
*.obj
|
|
||||||
*.elf
|
|
||||||
|
|
||||||
# Linker output
|
|
||||||
*.ilk
|
|
||||||
*.exp
|
|
||||||
|
|
||||||
# Precompiled Headers
|
|
||||||
*.gch
|
|
||||||
*.pch
|
|
||||||
|
|
||||||
# Libraries
|
|
||||||
*.lib
|
|
||||||
*.a
|
|
||||||
*.la
|
|
||||||
*.lo
|
|
||||||
|
|
||||||
# Shared objects (inc. Windows DLLs)
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.so.*
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Executables
|
|
||||||
*.exe
|
|
||||||
*.out
|
|
||||||
*.app
|
|
||||||
*.i*86
|
|
||||||
*.x86_64
|
|
||||||
*.hex
|
|
||||||
|
|
||||||
# Debug files
|
|
||||||
*.dSYM/
|
|
||||||
*.su
|
|
||||||
*.idb
|
|
||||||
*.pdb
|
|
||||||
|
|
||||||
# Kernel Module Compile Results
|
|
||||||
*.mod*
|
|
||||||
*.cmd
|
|
||||||
.tmp_versions/
|
|
||||||
modules.order
|
|
||||||
Module.symvers
|
|
||||||
Mkfile.old
|
|
||||||
dkms.conf
|
|
||||||
|
|
||||||
# debug information files
|
|
||||||
*.dwo
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM archlinux:base
|
FROM archlinux:base@sha256:b0deabeb3d283da2c7f7dbf0eea051b7b2cd0554e0b737cc457fd21683bdcdd1
|
||||||
|
|
||||||
RUN pacman -Syu --noconfirm --needed \
|
RUN pacman -Syu --noconfirm --needed \
|
||||||
diffutils \
|
diffutils \
|
||||||
|
|||||||
339
LICENSES/GPL-2.0-or-later.txt
Normal file
339
LICENSES/GPL-2.0-or-later.txt
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Lesser General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
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 software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, 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 redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
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 Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
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 Program, 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 Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) 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; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, 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 executable. 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.
|
||||||
|
|
||||||
|
If distribution of executable or 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 counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program 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.
|
||||||
|
|
||||||
|
5. 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 Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program 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.
|
||||||
|
|
||||||
|
7. 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 Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program 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 Program.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program 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.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies 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 Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, 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
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. 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 PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
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 program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 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 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.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License.
|
||||||
11
LICENSES/README.md
Normal file
11
LICENSES/README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# License inventory
|
||||||
|
|
||||||
|
- `GPL-2.0-or-later.txt` contains GNU GPL version 2. The header of
|
||||||
|
`map/kanji.dict` grants the option to use GPL version 2 or any later
|
||||||
|
version.
|
||||||
|
- Font license texts are stored in `font/` and associated with exact files
|
||||||
|
by `font/PROVENANCE`.
|
||||||
|
- `stb_truetype.h` includes its complete dual MIT/Public Domain terms.
|
||||||
|
- Generated Wayland files include their MIT-style notices in each file.
|
||||||
|
|
||||||
|
No repository-wide license has been declared for original strans source.
|
||||||
30
Makefile
30
Makefile
@@ -5,6 +5,8 @@ PKG_LIBS = $(shell pkg-config --libs dbus-1 wayland-client xkbcommon)
|
|||||||
CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS)
|
CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS)
|
||||||
PROG = strans
|
PROG = strans
|
||||||
DOCKER_IMAGE = strans-build
|
DOCKER_IMAGE = strans-build
|
||||||
|
DOCKER_RUN = docker run --rm --user "$$(id -u):$$(id -g)" \
|
||||||
|
-v "$(CURDIR):/src" $(DOCKER_IMAGE)
|
||||||
|
|
||||||
SRCS = $(wildcard *.c)
|
SRCS = $(wildcard *.c)
|
||||||
OBJS = $(SRCS:.c=.o)
|
OBJS = $(SRCS:.c=.o)
|
||||||
@@ -12,7 +14,7 @@ OBJS = $(SRCS:.c=.o)
|
|||||||
all: $(PROG) xim gtk
|
all: $(PROG) xim gtk
|
||||||
|
|
||||||
$(PROG): $(OBJS)
|
$(PROG): $(OBJS)
|
||||||
$(LD) -o $@ $(OBJS) -lthread -lString -lbio -lxcb -lm $(PKG_LIBS)
|
$(LD) -o $@ $(OBJS) -lthread -lbio -lxcb -lm $(PKG_LIBS)
|
||||||
|
|
||||||
$(OBJS): dat.h fn.h ipc.h
|
$(OBJS): dat.h fn.h ipc.h
|
||||||
# Avoid Arch's GLIBC_2.43 sqrtf optimization for older glibc runtimes.
|
# Avoid Arch's GLIBC_2.43 sqrtf optimization for older glibc runtimes.
|
||||||
@@ -37,18 +39,23 @@ gtk:
|
|||||||
bench:
|
bench:
|
||||||
$(MAKE) -C bench/
|
$(MAKE) -C bench/
|
||||||
|
|
||||||
docker: Dockerfile
|
docker-image: Dockerfile
|
||||||
docker build -t $(DOCKER_IMAGE) - < Dockerfile
|
docker build -t $(DOCKER_IMAGE) - < Dockerfile
|
||||||
docker run --rm --user "$$(id -u):$$(id -g)" \
|
|
||||||
-v "$(CURDIR):/src" $(DOCKER_IMAGE) \
|
|
||||||
sh -c 'make clean && make all'
|
|
||||||
|
|
||||||
docker-check: docker
|
docker-build:
|
||||||
docker run --rm --user "$$(id -u):$$(id -g)" \
|
$(DOCKER_RUN) make -B all
|
||||||
-e TESTARGS="$(TESTARGS)" -v "$(CURDIR):/src" \
|
|
||||||
$(DOCKER_IMAGE) make check
|
docker-check:
|
||||||
|
$(DOCKER_RUN) make -B check TESTARGS="$(TESTARGS)"
|
||||||
|
|
||||||
|
docker-bench:
|
||||||
|
$(DOCKER_RUN) make -B bench
|
||||||
|
|
||||||
|
docker: docker-image
|
||||||
|
$(MAKE) docker-build
|
||||||
|
|
||||||
check: verify-map test
|
check: verify-map test
|
||||||
|
$(MAKE) -C xim check
|
||||||
|
|
||||||
test:
|
test:
|
||||||
$(MAKE) -C tests check TESTARGS="$(TESTARGS)"
|
$(MAKE) -C tests check TESTARGS="$(TESTARGS)"
|
||||||
@@ -56,6 +63,9 @@ test:
|
|||||||
verify-map:
|
verify-map:
|
||||||
python3 map/mktelex.py | cmp - map/telex.map
|
python3 map/mktelex.py | cmp - map/telex.map
|
||||||
python3 -B map/mkemoji | cmp - map/emoji.dict
|
python3 -B map/mkemoji | cmp - map/emoji.dict
|
||||||
|
python3 -B map/verifymap.py map/*.map map/*.dict
|
||||||
python3 -B tests/mkemoji_test.py
|
python3 -B tests/mkemoji_test.py
|
||||||
|
python3 -B tests/skk2ktrans_test.py
|
||||||
|
|
||||||
.PHONY: all check test verify-map clean xim gtk bench docker docker-check
|
.PHONY: all check test verify-map clean xim gtk bench docker docker-image \
|
||||||
|
docker-build docker-check docker-bench
|
||||||
|
|||||||
188
README.md
188
README.md
@@ -1,47 +1,105 @@
|
|||||||
# strans
|
# strans
|
||||||
|
|
||||||
A small input method daemon for Japanese, Korean, Vietnamese, emoji, and
|
strans is a small, single-user input method for Korean, Japanese, English,
|
||||||
symbols on X11 and Wayland. Inspired by 9front's ktrans.
|
and emoji on X11 and Wayland. It provides one engine shared by XIM, GTK 3,
|
||||||
|
IBus, and Wayland input-method-v2 frontends. The existing Vietnamese Telex
|
||||||
|
mode remains available for compatibility, but is not an actively developed
|
||||||
|
language mode.
|
||||||
|
|
||||||
## Build
|
## Input behavior
|
||||||
|
|
||||||
The recommended build needs only Docker and Make on the host:
|
English is direct passthrough. Korean uses 2-beolsik composition. Japanese
|
||||||
|
Hiragana keeps the complete kana reading until it is committed, so `kanji`
|
||||||
|
forms `かんじ` and offers whole-reading candidates such as `漢字` and `幹事`.
|
||||||
|
An apostrophe resolves an ambiguous `n` without forwarding the apostrophe:
|
||||||
|
for example, `n'ya` forms `んや`. Katakana is direct Katakana composition
|
||||||
|
and does not perform Kanji dictionary conversion.
|
||||||
|
|
||||||
```sh
|
A Japanese composition is committed by Enter, candidate selection, a real
|
||||||
make docker
|
input boundary, or an explicit mode change. Backspace first removes pending
|
||||||
```
|
romaji, then accumulated kana. The Japanese modes support doubled consonants
|
||||||
|
and combinations such as `nya`, `nnya`, and `matcha`.
|
||||||
|
|
||||||
This creates an Arch Linux build image, compiles all runtime components in one
|
Modes are selected with Ctrl and one letter:
|
||||||
disposable container, and writes three artifacts into the source tree:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
strans daemon, IBus and Wayland frontend
|
Ctrl+N Japanese Hiragana and Kanji conversion
|
||||||
|
Ctrl+K Japanese Katakana
|
||||||
|
Ctrl+S Korean Hangul
|
||||||
|
Ctrl+T English passthrough
|
||||||
|
Ctrl+V Vietnamese Telex compatibility mode
|
||||||
|
Ctrl+E Emoji and symbol search
|
||||||
|
Ctrl+P Toggle popup preedit
|
||||||
|
```
|
||||||
|
|
||||||
|
While candidates are visible, Up and Down move the selection, Enter accepts
|
||||||
|
the selected candidate, and an unmodified `1`-`9` selects the corresponding
|
||||||
|
visible row. Tab cycles emoji search results. `0` commits an unconverted
|
||||||
|
language reading. Escape cancels the current composition. Ctrl, Alt, or
|
||||||
|
Super combined with a number does not select a candidate.
|
||||||
|
|
||||||
|
Emoji search is temporary and returns to the previous mode. Queries may use
|
||||||
|
English aliases or the active language's input rules, including Japanese
|
||||||
|
`egao` becoming `えがお` or `エガオ`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Ctrl+E → smile → Enter → 😀
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build and test
|
||||||
|
|
||||||
|
The supported build uses Docker so host and container toolchains are not
|
||||||
|
mixed. Only Docker and Make are needed on the host:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git submodule update --init
|
||||||
|
make docker-image
|
||||||
|
make docker-build
|
||||||
|
make docker-check
|
||||||
|
```
|
||||||
|
|
||||||
|
`docker-image` creates the Arch Linux dependency image, `docker-build`
|
||||||
|
compiles the runtime components, and `docker-check` runs the map verifiers and
|
||||||
|
test suites. Docker targets force their own compilation, so newer host
|
||||||
|
objects cannot be reused. `docker` is a convenience alias for the image and
|
||||||
|
build steps.
|
||||||
|
The resulting runtime artifacts are:
|
||||||
|
|
||||||
|
```text
|
||||||
|
strans daemon, IBus and Wayland frontends
|
||||||
xim/strans-xim XIM frontend
|
xim/strans-xim XIM frontend
|
||||||
gtk/im-strans.so GTK 3 frontend
|
gtk/im-strans.so GTK 3 frontend
|
||||||
```
|
```
|
||||||
|
|
||||||
The build starts clean so host and container object files are never mixed. It
|
Use `make docker-check TESTARGS=hangul` to filter the C unit tests. A native
|
||||||
removes existing build and test outputs; the `strans-build` dependency image is
|
build is still possible with the dependencies and Plan 9 setup recorded in
|
||||||
kept as a cache. No development packages are required on the host.
|
`Dockerfile`; use `make all`, `make check`, and `make bench` inside that
|
||||||
|
environment.
|
||||||
|
|
||||||
Void Linux runtime dependencies:
|
After editing `map/emoji.src`, regenerate and verify its dictionary with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo xbps-install -S dbus-libs wayland libxkbcommon libxcb xcb-imdkit gtk+3
|
map/mkemoji >map/emoji.dict
|
||||||
|
make verify-map
|
||||||
```
|
```
|
||||||
|
|
||||||
For a native Arch Linux build, use `Dockerfile` as the dependency and Plan 9
|
Japanese dictionary regeneration and verification are documented in
|
||||||
setup checklist, then run `make`. Build the benchmark separately with
|
[`map/README`](map/README). The converter output is deterministic UTF-8,
|
||||||
`make bench`.
|
tab-separated data consumable by the runtime dictionary reader.
|
||||||
|
|
||||||
## Run
|
## Run and install
|
||||||
|
|
||||||
Start the daemon:
|
Start the daemon with the installed map and font directories:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./strans map font &
|
./strans map font &
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The IPC socket is placed at `$XDG_RUNTIME_DIR/strans.sock` when
|
||||||
|
`XDG_RUNTIME_DIR` is an absolute path. Otherwise strans uses the per-user
|
||||||
|
fallback `/tmp/strans.<uid>`. A second daemon will not replace a live
|
||||||
|
daemon's socket.
|
||||||
|
|
||||||
For XIM applications:
|
For XIM applications:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -50,11 +108,19 @@ export XMODIFIERS=@im=strans
|
|||||||
xterm
|
xterm
|
||||||
```
|
```
|
||||||
|
|
||||||
For GTK 3 applications:
|
For GTK 3 applications, install the module built by Docker. Installation
|
||||||
|
needs the GTK 3 runtime, but not its development package:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo make -C gtk install
|
doas make -C gtk install
|
||||||
GTK_IM_MODULE=strans gedit
|
```
|
||||||
|
|
||||||
|
`LIBDIR` defaults to `/usr/lib` and may be overridden for another system.
|
||||||
|
Normal `DESTDIR` semantics are supported, and a staged install does not update
|
||||||
|
the host GTK module cache:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make -C gtk install DESTDIR="$pkgdir"
|
||||||
```
|
```
|
||||||
|
|
||||||
For IBus clients such as kitty:
|
For IBus clients such as kitty:
|
||||||
@@ -63,56 +129,38 @@ For IBus clients such as kitty:
|
|||||||
GLFW_IM_MODULE=ibus kitty
|
GLFW_IM_MODULE=ibus kitty
|
||||||
```
|
```
|
||||||
|
|
||||||
Wayland needs no environment variable. Start `strans` after a wlroots
|
Wayland needs no environment variable. Start `strans` after a compositor
|
||||||
compositor that supports input-method-v2. Neither ibus-daemon nor fcitx is
|
that supports input-method-v2. Neither ibus-daemon nor fcitx is required.
|
||||||
required.
|
Starting without `DISPLAY` is supported: the optional X popup is disabled,
|
||||||
|
while input processing and non-X frontends continue to run.
|
||||||
|
|
||||||
## Keys
|
## Ownership and rendering constraints
|
||||||
|
|
||||||
Input modes are selected with Ctrl and one letter:
|
There is one global composition, not one independent engine per frontend
|
||||||
|
context. The first meaningful key from a new context resets the previous
|
||||||
|
composition and transfers ownership. Reset, focus loss, context destruction,
|
||||||
|
and disconnect affect the engine only when they come from its active owner;
|
||||||
|
stale events cannot clear a newer owner's preedit.
|
||||||
|
|
||||||
```text
|
The popup is an optional X window, including when the active frontend is
|
||||||
Ctrl+N Hiragana
|
Wayland. It uses deterministic font fallback and fixed-width rune cells;
|
||||||
Ctrl+K Katakana
|
U+FE0E and U+FE0F variation selectors use zero cells. It does not perform
|
||||||
Ctrl+S Hangul
|
OpenType shaping, color-emoji rendering, or general grapheme clustering, and
|
||||||
Ctrl+T English
|
there is no native Wayland popup renderer. Multi-rune emoji are committed
|
||||||
Ctrl+V Vietnamese Telex
|
intact but are drawn as their constituent rune cells under this boundary. A
|
||||||
Ctrl+E Emoji and symbol search
|
supplied caret rectangle is used when available, with the pointer as fallback.
|
||||||
Ctrl+P Toggle popup preedit
|
The X renderer accepts only a 24-depth, 32-bpp TrueColor root format and
|
||||||
```
|
disables itself cleanly on other visuals.
|
||||||
|
|
||||||
Use `1`-`9` or the arrow keys to select a candidate. Tab moves through
|
## Data, third-party code, and licensing
|
||||||
candidates, Enter commits, and Escape cancels.
|
|
||||||
|
|
||||||
Emoji search is temporary and returns to the previous input mode:
|
Data and third-party provenance is recorded in
|
||||||
|
[`docs/PROVENANCE.md`](docs/PROVENANCE.md), with font-specific details in
|
||||||
|
[`font/PROVENANCE`](font/PROVENANCE). The historical SKK-derived Kanji
|
||||||
|
dictionary is distributed under GPL version 2 or later; its license text is
|
||||||
|
in [`LICENSES/GPL-2.0-or-later.txt`](LICENSES/GPL-2.0-or-later.txt).
|
||||||
|
|
||||||
```text
|
The repository does not currently declare a license for the strans project
|
||||||
Ctrl+E → smile → Enter → 😀
|
source as a whole. The licenses of bundled data, fonts, generated protocol
|
||||||
```
|
code, and third-party components do not by themselves license the original
|
||||||
|
strans source.
|
||||||
## Tests
|
|
||||||
|
|
||||||
Tests use the sibling `cutest` repository as a submodule. Its relative URL
|
|
||||||
expects repository mirrors and forks to provide the same sibling. Initialize
|
|
||||||
it once, then run the checks in the build container:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git submodule update --init
|
|
||||||
make docker-check
|
|
||||||
```
|
|
||||||
|
|
||||||
The native `make check` command needs plan9port and Python 3, but not the GUI
|
|
||||||
development packages. Use `make test TESTARGS=hangul` or
|
|
||||||
`make docker-check TESTARGS=hangul` to filter unit tests. After editing
|
|
||||||
`map/emoji.src`, regenerate and verify the dictionary with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
map/mkemoji > map/emoji.dict
|
|
||||||
make verify-map
|
|
||||||
```
|
|
||||||
|
|
||||||
## Design
|
|
||||||
|
|
||||||
`strans` owns the input engine and candidate window. The XIM and GTK frontends
|
|
||||||
forward keys to it over a Unix socket; the IBus and Wayland frontends are built
|
|
||||||
into the daemon. Internal threads communicate through CSP channels.
|
|
||||||
|
|||||||
38
bench.sh
38
bench.sh
@@ -1,23 +1,43 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
pkill strans
|
set -eu
|
||||||
pkill strans-xim
|
|
||||||
sleep 1
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
strans_pid=
|
||||||
|
perf_pid=
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
trap - 0 1 2 15
|
||||||
|
if test -n "$perf_pid"; then
|
||||||
|
kill -INT "$perf_pid" 2>/dev/null || true
|
||||||
|
wait "$perf_pid" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
if test -n "$strans_pid"; then
|
||||||
|
kill "$strans_pid" 2>/dev/null || true
|
||||||
|
wait "$strans_pid" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup 0 1 2 15
|
||||||
|
|
||||||
./strans map font &
|
./strans map font &
|
||||||
|
strans_pid=$!
|
||||||
sleep 1
|
sleep 1
|
||||||
|
kill -0 "$strans_pid"
|
||||||
|
|
||||||
# warm up glyph cache
|
# warm up glyph cache
|
||||||
./bench/bench bench/bench.keys 100
|
./bench/bench bench/bench.keys 100
|
||||||
echo "cache warmed up"
|
echo "cache warmed up"
|
||||||
|
|
||||||
STRANS_PID=$(pgrep -x strans)
|
perf record -g -o bench/perf.data -p "$strans_pid" &
|
||||||
perf record -g -o bench/perf.data -p "$STRANS_PID" &
|
perf_pid=$!
|
||||||
PERF_PID=$!
|
|
||||||
sleep 1
|
sleep 1
|
||||||
|
kill -0 "$perf_pid"
|
||||||
|
|
||||||
./bench/bench bench/bench.keys 100
|
./bench/bench bench/bench.keys 100
|
||||||
|
|
||||||
kill -INT $PERF_PID
|
kill -INT "$perf_pid"
|
||||||
wait $PERF_PID 2>/dev/null
|
wait "$perf_pid" 2>/dev/null || true
|
||||||
pkill strans
|
perf_pid=
|
||||||
|
|||||||
69
bench/main.c
69
bench/main.c
@@ -1,11 +1,12 @@
|
|||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include <errno.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/un.h>
|
|
||||||
#include "../ipc.h"
|
#include "../ipc.h"
|
||||||
|
|
||||||
typedef struct Key Key;
|
typedef struct Key Key;
|
||||||
@@ -15,7 +16,7 @@ struct Key {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static Key *keys;
|
static Key *keys;
|
||||||
static int nkeys;
|
static size_t nkeys;
|
||||||
static int fd;
|
static int fd;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -28,13 +29,19 @@ die(char *msg)
|
|||||||
static void
|
static void
|
||||||
addkey(int k, int mod)
|
addkey(int k, int mod)
|
||||||
{
|
{
|
||||||
static int cap;
|
static size_t cap;
|
||||||
|
Key *p;
|
||||||
|
|
||||||
if(nkeys >= cap){
|
if(nkeys >= cap){
|
||||||
|
if(cap > SIZE_MAX / 2 / sizeof(Key)){
|
||||||
|
fprintf(stderr, "too many keys\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
cap = cap ? cap * 2 : 256;
|
cap = cap ? cap * 2 : 256;
|
||||||
keys = realloc(keys, cap * sizeof(Key));
|
p = realloc(keys, cap * sizeof(Key));
|
||||||
if(!keys)
|
if(!p)
|
||||||
die("realloc");
|
die("realloc");
|
||||||
|
keys = p;
|
||||||
}
|
}
|
||||||
keys[nkeys].k = k;
|
keys[nkeys].k = k;
|
||||||
keys[nkeys].mod = mod;
|
keys[nkeys].mod = mod;
|
||||||
@@ -76,20 +83,35 @@ loadkeys(char *file)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
if(nkeys == 0){
|
||||||
|
fprintf(stderr, "%s: no keys\n", file);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t
|
||||||
|
iterations(char *s)
|
||||||
|
{
|
||||||
|
uintmax_t n;
|
||||||
|
char *end;
|
||||||
|
|
||||||
|
if(*s == '\0' || *s == '-')
|
||||||
|
goto Bad;
|
||||||
|
errno = 0;
|
||||||
|
n = strtoumax(s, &end, 10);
|
||||||
|
if(errno != 0 || *end != '\0' || n == 0 || n > UINT64_MAX)
|
||||||
|
goto Bad;
|
||||||
|
return n;
|
||||||
|
Bad:
|
||||||
|
fprintf(stderr, "invalid iteration count: %s\n", s);
|
||||||
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dial(void)
|
dial(void)
|
||||||
{
|
{
|
||||||
struct sockaddr_un addr;
|
fd = ipcconnect();
|
||||||
|
|
||||||
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
||||||
if(fd < 0)
|
if(fd < 0)
|
||||||
die("socket");
|
|
||||||
memset(&addr, 0, sizeof(addr));
|
|
||||||
addr.sun_family = AF_UNIX;
|
|
||||||
snprintf(addr.sun_path, sizeof(addr.sun_path), IPCPATH, getuid());
|
|
||||||
if(connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
|
|
||||||
die("connect");
|
die("connect");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,31 +146,38 @@ now(void)
|
|||||||
int
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int i, j, niter;
|
size_t j;
|
||||||
|
uint64_t i, niter, total;
|
||||||
double t0, t1, dt;
|
double t0, t1, dt;
|
||||||
|
|
||||||
if(argc < 2){
|
if(argc != 2 && argc != 3){
|
||||||
fprintf(stderr, "usage: bench file [niter]\n");
|
fprintf(stderr, "usage: bench file [niter]\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadkeys(argv[1]);
|
loadkeys(argv[1]);
|
||||||
niter = argc > 2 ? atoi(argv[2]) : 1000;
|
niter = argc == 3 ? iterations(argv[2]) : 1000;
|
||||||
|
if(nkeys > UINT64_MAX || niter > UINT64_MAX / nkeys){
|
||||||
|
fprintf(stderr, "iteration count overflows key total\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
total = niter * nkeys;
|
||||||
dial();
|
dial();
|
||||||
t0 = now();
|
t0 = now();
|
||||||
for(i = 0; i < niter; i++)
|
for(i = 0; i < niter; i++)
|
||||||
for(j = 0; j < nkeys; j++){
|
for(j = 0; j < nkeys; j++){
|
||||||
sendkey(keys[j].k, keys[j].mod);
|
sendkey(keys[j].k, keys[j].mod);
|
||||||
if(readresp() < 0){
|
if(readresp() < 0){
|
||||||
fprintf(stderr, "failed at iter %d key %d\n", i, j);
|
fprintf(stderr, "failed at iter %" PRIu64 " key %zu\n", i, j);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t1 = now();
|
t1 = now();
|
||||||
dt = t1 - t0;
|
dt = t1 - t0;
|
||||||
printf("%d iters x %d keys = %d keys\n", niter, nkeys, niter * nkeys);
|
printf("%" PRIu64 " iters x %zu keys = %" PRIu64 " keys\n",
|
||||||
|
niter, nkeys, total);
|
||||||
printf("%.3f ms total, %.3f us/key, %.3f us/iter\n",
|
printf("%.3f ms total, %.3f us/key, %.3f us/iter\n",
|
||||||
dt * 1000, dt * 1e6 / (niter * nkeys), dt * 1e6 / niter);
|
dt * 1000, dt * 1e6 / total, dt * 1e6 / niter);
|
||||||
close(fd);
|
close(fd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
bench/perf.data
BIN
bench/perf.data
Binary file not shown.
Binary file not shown.
36
dat.h
36
dat.h
@@ -17,7 +17,6 @@ enum
|
|||||||
LangVI = 0x16,
|
LangVI = 0x16,
|
||||||
|
|
||||||
Fontsz = 32,
|
Fontsz = 32,
|
||||||
Fontbase = 4,
|
|
||||||
Nglyphs = 0x20000,
|
Nglyphs = 0x20000,
|
||||||
Maxrunes = 64,
|
Maxrunes = 64,
|
||||||
Maxutf = Maxrunes * UTFmax + 1,
|
Maxutf = Maxrunes * UTFmax + 1,
|
||||||
@@ -28,6 +27,7 @@ enum
|
|||||||
Maxclients = 64,
|
Maxclients = 64,
|
||||||
Maxkouho = 32,
|
Maxkouho = 32,
|
||||||
Maxdisp = 9,
|
Maxdisp = 9,
|
||||||
|
Wlkeymax = 0x300,
|
||||||
Imgw = (Maxrunes + 3) * Fontsz,
|
Imgw = (Maxrunes + 3) * Fontsz,
|
||||||
Imgh = (Maxdisp + 1) * Fontsz,
|
Imgh = (Maxdisp + 1) * Fontsz,
|
||||||
|
|
||||||
@@ -43,6 +43,17 @@ struct Str
|
|||||||
int n;
|
int n;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct Wlstate Wlstate;
|
||||||
|
struct Wlstate
|
||||||
|
{
|
||||||
|
uchar down[Wlkeymax];
|
||||||
|
u32int repeatkey;
|
||||||
|
uvlong repeatat;
|
||||||
|
int rate;
|
||||||
|
int delay;
|
||||||
|
int repeating;
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct Emit Emit;
|
typedef struct Emit Emit;
|
||||||
struct Emit
|
struct Emit
|
||||||
{
|
{
|
||||||
@@ -110,23 +121,41 @@ struct Im
|
|||||||
{
|
{
|
||||||
Lang *l;
|
Lang *l;
|
||||||
Str pre;
|
Str pre;
|
||||||
Str raw; /* physical keys for the current Telex preedit */
|
/* Telex history, or the short pending romaji in a Japanese mode. */
|
||||||
|
Str raw;
|
||||||
Str kouho[Maxkouho];
|
Str kouho[Maxkouho];
|
||||||
int nkouho;
|
int nkouho;
|
||||||
int sel;
|
int sel;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Drawcmd Drawcmd;
|
typedef struct Drawcmd Drawcmd;
|
||||||
|
typedef struct Caret Caret;
|
||||||
|
struct Caret
|
||||||
|
{
|
||||||
|
int valid;
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
int h;
|
||||||
|
};
|
||||||
|
|
||||||
struct Drawcmd
|
struct Drawcmd
|
||||||
{
|
{
|
||||||
Str pre;
|
Str pre;
|
||||||
Str kouho[Maxdisp];
|
Str kouho[Maxdisp];
|
||||||
int nkouho;
|
int nkouho;
|
||||||
int sel;
|
int sel;
|
||||||
|
Caret caret;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Keyreq Keyreq;
|
typedef struct Keyreq Keyreq;
|
||||||
typedef struct Keyres Keyres;
|
typedef struct Keyres Keyres;
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Keypress,
|
||||||
|
Keyreset,
|
||||||
|
Keyrelease,
|
||||||
|
Keycaret,
|
||||||
|
};
|
||||||
struct Keyres
|
struct Keyres
|
||||||
{
|
{
|
||||||
int eaten;
|
int eaten;
|
||||||
@@ -136,8 +165,11 @@ struct Keyres
|
|||||||
|
|
||||||
struct Keyreq
|
struct Keyreq
|
||||||
{
|
{
|
||||||
|
uvlong owner;
|
||||||
|
int op;
|
||||||
u32int ks;
|
u32int ks;
|
||||||
u32int mod;
|
u32int mod;
|
||||||
|
Caret caret;
|
||||||
int want; /* nonzero: include preedit in reply */
|
int want; /* nonzero: include preedit in reply */
|
||||||
Channel *reply;
|
Channel *reply;
|
||||||
};
|
};
|
||||||
|
|||||||
35
dict.c
35
dict.c
@@ -33,7 +33,7 @@ dictlookup(Dictreq *req, Dictres *res)
|
|||||||
while(p < e && *p != ' ')
|
while(p < e && *p != ' ')
|
||||||
p++;
|
p++;
|
||||||
sinit(&tmp, sp, p - sp);
|
sinit(&tmp, sp, p - sp);
|
||||||
if(scmp(&tmp, &req->key) != 0)
|
if(req->lang == LangEMOJI || scmp(&tmp, &req->key) != 0)
|
||||||
res->kouho[res->nkouho++] = tmp;
|
res->kouho[res->nkouho++] = tmp;
|
||||||
if(p < e)
|
if(p < e)
|
||||||
p++;
|
p++;
|
||||||
@@ -44,7 +44,7 @@ void
|
|||||||
dictthread(void*)
|
dictthread(void*)
|
||||||
{
|
{
|
||||||
Dictreq req;
|
Dictreq req;
|
||||||
Dictres res;
|
static Dictres res;
|
||||||
|
|
||||||
threadsetname("dict");
|
threadsetname("dict");
|
||||||
for(;;){
|
for(;;){
|
||||||
@@ -63,14 +63,16 @@ dictopen(char *path)
|
|||||||
Hmap *h;
|
Hmap *h;
|
||||||
Biobuf *b;
|
Biobuf *b;
|
||||||
Str key;
|
Str key;
|
||||||
char *line, *tab;
|
char *line, *tab, *p, *e;
|
||||||
int len;
|
int len, lineno;
|
||||||
|
|
||||||
b = Bopen(path, OREAD);
|
b = Bopen(path, OREAD);
|
||||||
if(b == nil)
|
if(b == nil)
|
||||||
die("can't open: %s", path);
|
die("can't open: %s", path);
|
||||||
h = hmapalloc(4096);
|
h = hmapalloc(4096);
|
||||||
|
lineno = 0;
|
||||||
while((line = Brdstr(b, '\n', 1)) != nil){
|
while((line = Brdstr(b, '\n', 1)) != nil){
|
||||||
|
lineno++;
|
||||||
len = strlen(line);
|
len = strlen(line);
|
||||||
if(len > 0 && line[len-1] == '\r')
|
if(len > 0 && line[len-1] == '\r')
|
||||||
line[--len] = '\0';
|
line[--len] = '\0';
|
||||||
@@ -79,11 +81,21 @@ dictopen(char *path)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tab = strchr(line, '\t');
|
tab = strchr(line, '\t');
|
||||||
if(tab == nil || tab >= line + len - 1){
|
if(tab == nil || tab == line || tab >= line + len - 1 ||
|
||||||
free(line);
|
strchr(tab+1, '\t') != nil)
|
||||||
continue;
|
die("malformed dictionary: %s:%d", path, lineno);
|
||||||
}
|
|
||||||
*tab = '\0';
|
*tab = '\0';
|
||||||
|
if(utflen(line) > Maxrunes)
|
||||||
|
die("dictionary key too long: %s:%d", path, lineno);
|
||||||
|
for(p = tab+1; p < line+len; p = e+1){
|
||||||
|
e = memchr(p, ' ', line+len-p);
|
||||||
|
if(e == nil)
|
||||||
|
e = line+len;
|
||||||
|
if(utfnlen(p, e-p) > Maxrunes)
|
||||||
|
die("dictionary candidate too long: %s:%d", path, lineno);
|
||||||
|
if(e == line+len)
|
||||||
|
break;
|
||||||
|
}
|
||||||
sinit(&key, line, tab - line);
|
sinit(&key, line, tab - line);
|
||||||
hmapset(&h, &key, tab+1, len - (tab - line) - 1);
|
hmapset(&h, &key, tab+1, len - (tab - line) - 1);
|
||||||
free(line);
|
free(line);
|
||||||
@@ -95,13 +107,16 @@ dictopen(char *path)
|
|||||||
void
|
void
|
||||||
dictinit(char *dir)
|
dictinit(char *dir)
|
||||||
{
|
{
|
||||||
char path[1024];
|
char *path;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for(i = 0; i < nlang; i++){
|
for(i = 0; i < nlang; i++){
|
||||||
if(langs[i].dictname == nil)
|
if(langs[i].dictname == nil)
|
||||||
continue;
|
continue;
|
||||||
snprint(path, sizeof(path), "%s/%s.dict", dir, langs[i].dictname);
|
path = smprint("%s/%s.dict", dir, langs[i].dictname);
|
||||||
|
if(path == nil)
|
||||||
|
die("out of memory");
|
||||||
langs[i].dict = dictopen(path);
|
langs[i].dict = dictopen(path);
|
||||||
|
free(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
72
docs/PROVENANCE.md
Normal file
72
docs/PROVENANCE.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Data and third-party provenance
|
||||||
|
|
||||||
|
This file records what can be established from repository contents and
|
||||||
|
history. It does not assign a license to original strans source code.
|
||||||
|
|
||||||
|
## Input maps and dictionaries
|
||||||
|
|
||||||
|
`map/hira.map`, `map/kata.map`, and the original map data entered the history
|
||||||
|
in commit `cc157c0b3c33506d1cbf5bd251ddc6a9ca909081`, whose subject says the
|
||||||
|
maps came from 9front. That commit does not identify a 9front revision or
|
||||||
|
carry a separate data license. `map/telex.map` is generated by
|
||||||
|
`map/mktelex.py`; Vietnamese behavior is retained for compatibility.
|
||||||
|
|
||||||
|
`map/emoji.dict` is generated deterministically from the repository-maintained
|
||||||
|
`map/emoji.src` by `map/mkemoji`. No external dataset is required.
|
||||||
|
|
||||||
|
`map/kanji.dict` identifies itself as SKK Medium dictionary version 8.1,
|
||||||
|
May 24, 1995, rearranged for Plan 9 ktrans by Kenji Okamoto on February 17,
|
||||||
|
2000. Its header names its copyright holders and grants GPL version 2 or
|
||||||
|
later. The complete GPL version 2 text is in
|
||||||
|
`LICENSES/GPL-2.0-or-later.txt`; the dictionary header supplies the
|
||||||
|
"or later" option. `map/README` records the exact repository history,
|
||||||
|
repairs made to this copy, and the reproducible procedure for importing a
|
||||||
|
new SKK revision.
|
||||||
|
|
||||||
|
## Fonts
|
||||||
|
|
||||||
|
The popup bundles Noto Sans, Japanese-region Noto Sans Mono CJK, and Noto
|
||||||
|
Emoji. Exact checksums, versions or source revisions, download locations
|
||||||
|
where known, fallback order, and license-file names are recorded in
|
||||||
|
`font/PROVENANCE`. The corresponding OFL 1.1 and Apache 2.0 texts are kept
|
||||||
|
beside the fonts.
|
||||||
|
|
||||||
|
## stb_truetype
|
||||||
|
|
||||||
|
`stb_truetype.h` is Sean Barrett's stb_truetype version 1.26, dated
|
||||||
|
August 28, 2021 in its header. Its exact repository checksum is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ecd30b05e0dd4fea3a13c26810dd9e1992dc379049482c393d5a19e6b5090aab
|
||||||
|
```
|
||||||
|
|
||||||
|
The file was present in the repository's first commit,
|
||||||
|
`dcd1147638f908eca7e4b7616815e215022cc99d`. No upstream commit or download
|
||||||
|
URL was recorded. Its complete dual MIT/Public Domain terms are retained at
|
||||||
|
the end of the header.
|
||||||
|
|
||||||
|
## Generated Wayland protocol code
|
||||||
|
|
||||||
|
The four checked-in Wayland protocol C/header files were added in commit
|
||||||
|
`7629ec5fe8976812655c9d12b447a62abc8bb671`:
|
||||||
|
|
||||||
|
- `input-method-unstable-v2-client-protocol.h`
|
||||||
|
- `input-method-unstable-v2-protocol.c`
|
||||||
|
- `virtual-keyboard-unstable-v1-client-protocol.h`
|
||||||
|
- `virtual-keyboard-unstable-v1-protocol.c`
|
||||||
|
|
||||||
|
Their embedded notices identify the protocols as input-method-unstable-v2
|
||||||
|
and virtual-keyboard-unstable-v1 and include the applicable MIT-style license
|
||||||
|
terms. The input-method files report generation by wayland-scanner 1.21.0;
|
||||||
|
the virtual-keyboard files report wayland-scanner 1.23.1. The source XML
|
||||||
|
files and their upstream repository revisions were not committed or recorded
|
||||||
|
in history, so these generated files cannot honestly be regenerated
|
||||||
|
byte-for-byte from repository artifacts. Do not edit them manually. A
|
||||||
|
future regeneration should commit the source XML or record its immutable
|
||||||
|
upstream revision and the scanner command/version.
|
||||||
|
|
||||||
|
## Git submodules
|
||||||
|
|
||||||
|
`cutest` is pinned by the repository as the unit-test framework. XIM uses
|
||||||
|
the system xcb-imdkit and xcb-util libraries at build time; those system
|
||||||
|
libraries are not bundled by this repository.
|
||||||
13
fn.h
13
fn.h
@@ -28,6 +28,8 @@ void dictthread(void*);
|
|||||||
void dictlookup(Dictreq*, Dictres*);
|
void dictlookup(Dictreq*, Dictres*);
|
||||||
|
|
||||||
void drawthread(void*);
|
void drawthread(void*);
|
||||||
|
int popupcells(Rune*, int);
|
||||||
|
void popupposition(Caret*, int, int, int, int, int, int, int*, int*);
|
||||||
void imthread(void*);
|
void imthread(void*);
|
||||||
Emit transmap(Im*, Rune);
|
Emit transmap(Im*, Rune);
|
||||||
Emit transko(Im*, Rune);
|
Emit transko(Im*, Rune);
|
||||||
@@ -35,14 +37,23 @@ Emit transvi(Im*, Rune);
|
|||||||
void backko(Im*);
|
void backko(Im*);
|
||||||
void backvi(Im*);
|
void backvi(Im*);
|
||||||
void dictsend(Im*, Str*);
|
void dictsend(Im*, Str*);
|
||||||
|
uvlong ownernew(void);
|
||||||
|
|
||||||
int srvreadkey(int, Keyreq*);
|
int srvreadkey(int, Keyreq*);
|
||||||
void srvthread(void*);
|
void srvthread(void*);
|
||||||
void ibusthread(void*);
|
void ibusthread(void*);
|
||||||
void waylandthread(void*);
|
void waylandthread(void*);
|
||||||
|
void wlstateinit(Wlstate*);
|
||||||
|
int wlstatepress(Wlstate*, u32int, int, int, uvlong);
|
||||||
|
int wlstaterelease(Wlstate*, u32int);
|
||||||
|
void wlstaterepeatinfo(Wlstate*, int, int);
|
||||||
|
void wlstatecancelrepeat(Wlstate*);
|
||||||
|
void wlstateclear(Wlstate*);
|
||||||
|
int wlstatetimeout(Wlstate*, uvlong);
|
||||||
|
int wlstaterepeat(Wlstate*, uvlong, u32int*);
|
||||||
|
|
||||||
void* emalloc(ulong);
|
void* emalloc(ulong);
|
||||||
void* erealloc(void*, ulong);
|
void* erealloc(void*, ulong);
|
||||||
|
|
||||||
void fontinit(char*);
|
int fontinit(char*);
|
||||||
void putfont(u32int*, int, int, int, int, Rune);
|
void putfont(u32int*, int, int, int, int, Rune);
|
||||||
|
|||||||
132
font.c
132
font.c
@@ -4,95 +4,111 @@
|
|||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
typedef struct Glyph Glyph;
|
typedef struct Glyph Glyph;
|
||||||
|
typedef struct Font Font;
|
||||||
struct Glyph
|
struct Glyph
|
||||||
{
|
{
|
||||||
uchar *bmp;
|
uchar *bmp;
|
||||||
int w, h, ox, oy;
|
int w, h, ox, oy, base;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Font
|
||||||
|
{
|
||||||
|
stbtt_fontinfo info;
|
||||||
|
uchar *data;
|
||||||
|
float scale;
|
||||||
|
int base;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum { Maxfonts = 4 };
|
enum { Maxfonts = 4 };
|
||||||
|
|
||||||
static stbtt_fontinfo fonts[Maxfonts];
|
static Font fonts[Maxfonts];
|
||||||
static uchar *fontdata[Maxfonts];
|
|
||||||
static float scale[Maxfonts];
|
|
||||||
static int nfonts;
|
static int nfonts;
|
||||||
static Glyph cache[Nglyphs];
|
static Glyph cache[Nglyphs];
|
||||||
static u32int blendtab[2][256];
|
static u32int blendtab[2][256];
|
||||||
|
|
||||||
static u32int
|
static u32int
|
||||||
blend(u32int bg, int a)
|
blend(u32int bg, u32int fg, int a)
|
||||||
{
|
{
|
||||||
int r, g, b, inv;
|
int r, g, b, inv;
|
||||||
|
|
||||||
inv = 255 - a;
|
inv = 255 - a;
|
||||||
r = (bg >> 16 & 0xff) * inv / 255;
|
r = ((bg >> 16 & 0xff) * inv + (fg >> 16 & 0xff) * a) / 255;
|
||||||
g = (bg >> 8 & 0xff) * inv / 255;
|
g = ((bg >> 8 & 0xff) * inv + (fg >> 8 & 0xff) * a) / 255;
|
||||||
b = (bg & 0xff) * inv / 255;
|
b = ((bg & 0xff) * inv + (fg & 0xff) * a) / 255;
|
||||||
return (r << 16) | (g << 8) | b;
|
return (r << 16) | (g << 8) | b;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
loadfont(char *path)
|
loadfont(char *path)
|
||||||
{
|
{
|
||||||
int fd;
|
Font *f;
|
||||||
|
int ascent, descent, gap, fd;
|
||||||
long sz, n;
|
long sz, n;
|
||||||
|
|
||||||
if(nfonts >= Maxfonts)
|
|
||||||
die("too many fonts");
|
|
||||||
fd = open(path, OREAD);
|
fd = open(path, OREAD);
|
||||||
if(fd < 0)
|
if(fd < 0){
|
||||||
die("can't open font: %s", path);
|
fprint(2, "strans: popup: can't open font: %s\n", path);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
sz = seek(fd, 0, 2);
|
sz = seek(fd, 0, 2);
|
||||||
seek(fd, 0, 0);
|
seek(fd, 0, 0);
|
||||||
fontdata[nfonts] = emalloc(sz);
|
if(sz <= 0){
|
||||||
n = readn(fd, fontdata[nfonts], sz);
|
fprint(2, "strans: popup: empty font: %s\n", path);
|
||||||
close(fd);
|
close(fd);
|
||||||
if(n != sz)
|
|
||||||
die("can't read font: %s", path);
|
|
||||||
if(!stbtt_InitFont(&fonts[nfonts], fontdata[nfonts], stbtt_GetFontOffsetForIndex(fontdata[nfonts], 0)))
|
|
||||||
die("can't init font: %s", path);
|
|
||||||
scale[nfonts] = stbtt_ScaleForPixelHeight(&fonts[nfonts], Fontsz);
|
|
||||||
nfonts++;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
isfont(char *name)
|
|
||||||
{
|
|
||||||
char *p;
|
|
||||||
|
|
||||||
p = strrchr(name, '.');
|
|
||||||
if(p == nil)
|
|
||||||
return 0;
|
return 0;
|
||||||
return strcmp(p, ".ttf") == 0 || strcmp(p, ".otf") == 0;
|
}
|
||||||
|
f = &fonts[nfonts];
|
||||||
|
f->data = emalloc(sz);
|
||||||
|
n = readn(fd, f->data, sz);
|
||||||
|
close(fd);
|
||||||
|
if(n != sz){
|
||||||
|
fprint(2, "strans: popup: can't read font: %s\n", path);
|
||||||
|
free(f->data);
|
||||||
|
f->data = nil;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(!stbtt_InitFont(&f->info, f->data,
|
||||||
|
stbtt_GetFontOffsetForIndex(f->data, 0))){
|
||||||
|
fprint(2, "strans: popup: can't init font: %s\n", path);
|
||||||
|
free(f->data);
|
||||||
|
f->data = nil;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
f->scale = stbtt_ScaleForPixelHeight(&f->info, Fontsz);
|
||||||
|
stbtt_GetFontVMetrics(&f->info, &ascent, &descent, &gap);
|
||||||
|
(void)descent;
|
||||||
|
(void)gap;
|
||||||
|
f->base = (int)(ascent * f->scale);
|
||||||
|
nfonts++;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
int
|
||||||
fontinit(char *dir)
|
fontinit(char *dir)
|
||||||
{
|
{
|
||||||
int fd, n, i, a;
|
static char *names[] = {
|
||||||
Dir *d;
|
"NotoSans-Regular.ttf",
|
||||||
|
"NotoSansMonoCJKjp-Regular.otf",
|
||||||
|
"NotoEmoji-Regular.ttf",
|
||||||
|
};
|
||||||
|
int i, a;
|
||||||
char path[256];
|
char path[256];
|
||||||
|
|
||||||
fd = open(dir, OREAD);
|
nfonts = 0;
|
||||||
if(fd < 0)
|
memset(cache, 0, sizeof cache);
|
||||||
die("can't open font dir: %s", dir);
|
for(i = 0; i < nelem(names); i++){
|
||||||
n = dirreadall(fd, &d);
|
if(strlen(dir) + 1 + strlen(names[i]) + 1 > sizeof path){
|
||||||
close(fd);
|
fprint(2, "strans: popup: font path is too long\n");
|
||||||
if(n < 0)
|
continue;
|
||||||
die("can't read font dir: %s", dir);
|
|
||||||
for(i = 0; i < n; i++){
|
|
||||||
if(isfont(d[i].name)){
|
|
||||||
snprint(path, sizeof path, "%s/%s", dir, d[i].name);
|
|
||||||
loadfont(path);
|
|
||||||
}
|
}
|
||||||
|
snprint(path, sizeof path, "%s/%s", dir, names[i]);
|
||||||
|
loadfont(path);
|
||||||
}
|
}
|
||||||
free(d);
|
|
||||||
if(nfonts == 0)
|
|
||||||
die("no fonts in %s", dir);
|
|
||||||
for(a = 0; a < 256; a++){
|
for(a = 0; a < 256; a++){
|
||||||
blendtab[0][a] = blend(Colbg, a);
|
blendtab[0][a] = blend(Colbg, Colfg, a);
|
||||||
blendtab[1][a] = blend(Colsel, a);
|
blendtab[1][a] = blend(Colsel, Colfg, a);
|
||||||
}
|
}
|
||||||
|
return nfonts != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -108,17 +124,21 @@ putfont(u32int *buf, int w, int h, int px, int py, Rune r)
|
|||||||
g = &cache[r];
|
g = &cache[r];
|
||||||
if(g->bmp == nil){
|
if(g->bmp == nil){
|
||||||
for(f = 0; f < nfonts; f++){
|
for(f = 0; f < nfonts; f++){
|
||||||
if(stbtt_FindGlyphIndex(&fonts[f], r) == 0)
|
if(stbtt_FindGlyphIndex(&fonts[f].info, r) == 0)
|
||||||
continue;
|
continue;
|
||||||
g->bmp = stbtt_GetCodepointBitmap(&fonts[f], scale[f], scale[f], r, &g->w, &g->h, &g->ox, &g->oy);
|
g->bmp = stbtt_GetCodepointBitmap(&fonts[f].info,
|
||||||
if(g->bmp != nil)
|
fonts[f].scale, fonts[f].scale, r,
|
||||||
|
&g->w, &g->h, &g->ox, &g->oy);
|
||||||
|
if(g->bmp != nil){
|
||||||
|
g->base = fonts[f].base;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(g->bmp == nil)
|
if(g->bmp == nil)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
y0 = py + g->oy + Fontsz - Fontbase;
|
y0 = py + g->base + g->oy;
|
||||||
j0 = y0 < 0 ? -y0 : 0;
|
j0 = y0 < 0 ? -y0 : 0;
|
||||||
j1 = y0 + g->h > h ? h - y0 : g->h;
|
j1 = y0 + g->h > h ? h - y0 : g->h;
|
||||||
x0 = px + g->ox;
|
x0 = px + g->ox;
|
||||||
|
|||||||
201
font/Apache-2.0-NotoEmoji.txt
Normal file
201
font/Apache-2.0-NotoEmoji.txt
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
Binary file not shown.
94
font/OFL-NotoSans.txt
Normal file
94
font/OFL-NotoSans.txt
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
Copyright 2018 The Noto Project Authors (github.com/googlei18n/noto-fonts)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License,
|
||||||
|
Version 1.1.
|
||||||
|
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font
|
||||||
|
creation efforts of academic and linguistic communities, and to
|
||||||
|
provide a free and open framework in which fonts may be shared and
|
||||||
|
improved in partnership with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply to
|
||||||
|
any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software
|
||||||
|
components as distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to,
|
||||||
|
deleting, or substituting -- in part or in whole -- any of the
|
||||||
|
components of the Original Version, by changing formats or by porting
|
||||||
|
the Font Software to a new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||||
|
modify, redistribute, and sell modified and unmodified copies of the
|
||||||
|
Font Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components, in
|
||||||
|
Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the
|
||||||
|
corresponding Copyright Holder. This restriction only applies to the
|
||||||
|
primary font name as presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created using
|
||||||
|
the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
92
font/OFL-NotoSansCJK.txt
Normal file
92
font/OFL-NotoSansCJK.txt
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
This Font Software is licensed under the SIL Open Font License,
|
||||||
|
Version 1.1.
|
||||||
|
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font
|
||||||
|
creation efforts of academic and linguistic communities, and to
|
||||||
|
provide a free and open framework in which fonts may be shared and
|
||||||
|
improved in partnership with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply to
|
||||||
|
any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software
|
||||||
|
components as distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to,
|
||||||
|
deleting, or substituting -- in part or in whole -- any of the
|
||||||
|
components of the Original Version, by changing formats or by porting
|
||||||
|
the Font Software to a new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||||
|
modify, redistribute, and sell modified and unmodified copies of the
|
||||||
|
Font Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components, in
|
||||||
|
Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the
|
||||||
|
corresponding Copyright Holder. This restriction only applies to the
|
||||||
|
primary font name as presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created using
|
||||||
|
the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
37
font/PROVENANCE
Normal file
37
font/PROVENANCE
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
Bundled popup fonts
|
||||||
|
===================
|
||||||
|
|
||||||
|
The renderer tries these files in this fixed order:
|
||||||
|
|
||||||
|
1. NotoSans-Regular.ttf
|
||||||
|
2. NotoSansMonoCJKjp-Regular.otf
|
||||||
|
3. NotoEmoji-Regular.ttf
|
||||||
|
|
||||||
|
NotoSans-Regular.ttf
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
SHA-256: 9cb49a54e520423033f9727be2e53e4805a60656deb09c219740d8e5f3e033ac
|
||||||
|
Internal family/version: Noto Sans Regular, version 2.005
|
||||||
|
License: SIL Open Font License 1.1 (OFL-NotoSans.txt)
|
||||||
|
|
||||||
|
This file entered the strans repository in commit 874b941. That commit did
|
||||||
|
not record its download URL, and the current upstream build is not
|
||||||
|
byte-identical. The checksum above identifies the bundled file; no claim is
|
||||||
|
made that the current upstream URL regenerates it.
|
||||||
|
|
||||||
|
NotoSansMonoCJKjp-Regular.otf
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
SHA-256: 4d01725be822d144cf9a56ade981e6fb920cd7a610b8fc24cc601a920beea5b9
|
||||||
|
Release: Noto Sans CJK 2.004, tag Sans2.004
|
||||||
|
Source: https://raw.githubusercontent.com/notofonts/noto-cjk/Sans2.004/Sans/Mono/NotoSansMonoCJKjp-Regular.otf
|
||||||
|
License: SIL Open Font License 1.1 (OFL-NotoSansCJK.txt)
|
||||||
|
License source: https://raw.githubusercontent.com/notofonts/noto-cjk/Sans2.004/LICENSE
|
||||||
|
|
||||||
|
NotoEmoji-Regular.ttf
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
SHA-256: 415dc6290378574135b64c808dc640c1df7531973290c4970c51fdeb849cb0c5
|
||||||
|
Source: https://raw.githubusercontent.com/googlei18n/noto-emoji/2f1ffdd6fbbd05d6f382138a3d3adcd89c5ce800/fonts/NotoEmoji-Regular.ttf
|
||||||
|
License: Apache License 2.0 (Apache-2.0-NotoEmoji.txt)
|
||||||
|
License source: https://raw.githubusercontent.com/googlei18n/noto-emoji/2f1ffdd6fbbd05d6f382138a3d3adcd89c5ce800/LICENSE
|
||||||
18
gtk/Makefile
18
gtk/Makefile
@@ -1,8 +1,8 @@
|
|||||||
PROG = im-strans.so
|
PROG = im-strans.so
|
||||||
CFLAGS = -Wall -O2 -I.. $(shell pkg-config --cflags gtk+-3.0)
|
CFLAGS = -Wall -O2 -I.. $(shell pkg-config --cflags gtk+-3.0)
|
||||||
LDFLAGS = $(shell pkg-config --libs gtk+-3.0)
|
LDFLAGS = $(shell pkg-config --libs gtk+-3.0)
|
||||||
DSTDIR ?= $(shell find /usr/lib* -type d \
|
LIBDIR ?= /usr/lib
|
||||||
-path "*/gtk-3.0/*/immodules" 2>/dev/null | head -1)
|
GTK_MODULE_DIR = $(LIBDIR)/gtk-3.0/3.0.0/immodules
|
||||||
|
|
||||||
all: $(PROG)
|
all: $(PROG)
|
||||||
|
|
||||||
@@ -10,15 +10,15 @@ $(PROG): main.c ../ipc.c ../ipc.h
|
|||||||
$(CC) -shared -fPIC $(CFLAGS) -o $@ main.c ../ipc.c $(LDFLAGS)
|
$(CC) -shared -fPIC $(CFLAGS) -o $@ main.c ../ipc.c $(LDFLAGS)
|
||||||
|
|
||||||
install: $(PROG)
|
install: $(PROG)
|
||||||
test -n "$(DSTDIR)"
|
test -n "$(LIBDIR)"
|
||||||
mkdir -p "$(DSTDIR)"
|
mkdir -p "$(DESTDIR)$(GTK_MODULE_DIR)"
|
||||||
cp $(PROG) "$(DSTDIR)/"
|
cp $(PROG) "$(DESTDIR)$(GTK_MODULE_DIR)/"
|
||||||
gtk-query-immodules-3.0 --update-cache
|
if test -z "$(DESTDIR)"; then gtk-query-immodules-3.0 --update-cache; fi
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
test -n "$(DSTDIR)"
|
test -n "$(LIBDIR)"
|
||||||
rm -f "$(DSTDIR)/$(PROG)"
|
rm -f "$(DESTDIR)$(GTK_MODULE_DIR)/$(PROG)"
|
||||||
gtk-query-immodules-3.0 --update-cache
|
if test -z "$(DESTDIR)"; then gtk-query-immodules-3.0 --update-cache; fi
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(PROG)
|
rm -f $(PROG)
|
||||||
|
|||||||
24
gtk/main.c
24
gtk/main.c
@@ -2,8 +2,6 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/un.h>
|
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
#include "ipc.h"
|
#include "ipc.h"
|
||||||
|
|
||||||
@@ -27,20 +25,9 @@ static GType imtype;
|
|||||||
static void
|
static void
|
||||||
srvconnect(Im *im)
|
srvconnect(Im *im)
|
||||||
{
|
{
|
||||||
struct sockaddr_un addr;
|
|
||||||
|
|
||||||
if(im->fd >= 0)
|
if(im->fd >= 0)
|
||||||
return;
|
return;
|
||||||
im->fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
im->fd = ipcconnect();
|
||||||
if(im->fd < 0)
|
|
||||||
return;
|
|
||||||
memset(&addr, 0, sizeof(addr));
|
|
||||||
addr.sun_family = AF_UNIX;
|
|
||||||
snprintf(addr.sun_path, sizeof(addr.sun_path), IPCPATH, getuid());
|
|
||||||
if(connect(im->fd, (struct sockaddr*)&addr, sizeof(addr)) < 0){
|
|
||||||
close(im->fd);
|
|
||||||
im->fd = -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -120,7 +107,7 @@ sendreset(Im *im)
|
|||||||
srvclose(im);
|
srvclose(im);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ipcpackreq(buf, 1, 0, Kesc);
|
ipcpackreset(buf, 1);
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||||
readresp(im, resp, sizeof(resp)) < 0)
|
readresp(im, resp, sizeof(resp)) < 0)
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
@@ -216,7 +203,12 @@ reset(GtkIMContext *ctx)
|
|||||||
static void
|
static void
|
||||||
focusout(GtkIMContext *ctx)
|
focusout(GtkIMContext *ctx)
|
||||||
{
|
{
|
||||||
sendreset((Im*)ctx);
|
Im *im;
|
||||||
|
|
||||||
|
im = (Im*)ctx;
|
||||||
|
sendreset(im);
|
||||||
|
/* Closing the context connection releases engine ownership. */
|
||||||
|
srvclose(im);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|||||||
14
hash.c
14
hash.c
@@ -25,7 +25,7 @@ hmapalloc(int nbuckets)
|
|||||||
int nsz;
|
int nsz;
|
||||||
|
|
||||||
if(nbuckets < 1)
|
if(nbuckets < 1)
|
||||||
die("hmapalloc: no buckets");
|
return nil;
|
||||||
nsz = Tagsize;
|
nsz = Tagsize;
|
||||||
store = emalloc(sizeof(*h) + nbuckets * nsz);
|
store = emalloc(sizeof(*h) + nbuckets * nsz);
|
||||||
h = store;
|
h = store;
|
||||||
@@ -54,6 +54,8 @@ hmapget(Hmap *h, Str *key)
|
|||||||
Hnode *n;
|
Hnode *n;
|
||||||
uchar *v;
|
uchar *v;
|
||||||
|
|
||||||
|
if(h == nil || key == nil || key->n < 0 || key->n > Maxrunes)
|
||||||
|
return nil;
|
||||||
v = h->nodes + (hash(key) % h->nbs) * h->nsz;
|
v = h->nodes + (hash(key) % h->nbs) * h->nsz;
|
||||||
for(;;){
|
for(;;){
|
||||||
n = (Hnode*)v;
|
n = (Hnode*)v;
|
||||||
@@ -67,7 +69,7 @@ hmapget(Hmap *h, Str *key)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static char*
|
static char*
|
||||||
sdup(Str *s)
|
sdup(Str *s, int *len)
|
||||||
{
|
{
|
||||||
char buf[Maxutf];
|
char buf[Maxutf];
|
||||||
char *p;
|
char *p;
|
||||||
@@ -77,6 +79,7 @@ sdup(Str *s)
|
|||||||
p = emalloc(n + 1);
|
p = emalloc(n + 1);
|
||||||
memmove(p, buf, n);
|
memmove(p, buf, n);
|
||||||
p[n] = '\0';
|
p[n] = '\0';
|
||||||
|
*len = n;
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +124,10 @@ hmapset(Hmap **store, Str *key, const char *val, int vlen)
|
|||||||
int next;
|
int next;
|
||||||
vlong diff;
|
vlong diff;
|
||||||
|
|
||||||
|
if(store == nil || *store == nil || key == nil ||
|
||||||
|
key->n < 0 || key->n > Maxrunes || vlen < 0 ||
|
||||||
|
(vlen > 0 && val == nil))
|
||||||
|
return;
|
||||||
newval = memdup(val, vlen);
|
newval = memdup(val, vlen);
|
||||||
h = *store;
|
h = *store;
|
||||||
v = h->nodes + (hash(key) % h->nbs) * h->nsz;
|
v = h->nodes + (hash(key) % h->nbs) * h->nsz;
|
||||||
@@ -151,8 +158,7 @@ hmapset(Hmap **store, Str *key, const char *val, int vlen)
|
|||||||
n = (Hnode*)v;
|
n = (Hnode*)v;
|
||||||
replace:
|
replace:
|
||||||
if(n->filled == 0){
|
if(n->filled == 0){
|
||||||
n->key = sdup(key);
|
n->key = sdup(key, &n->klen);
|
||||||
n->klen = strlen(n->key);
|
|
||||||
n->filled = 1;
|
n->filled = 1;
|
||||||
}
|
}
|
||||||
n->next = next;
|
n->next = next;
|
||||||
|
|||||||
300
ibus.c
300
ibus.c
@@ -14,7 +14,9 @@ enum
|
|||||||
{
|
{
|
||||||
Maxwatches = 32,
|
Maxwatches = 32,
|
||||||
Maxconns = 16,
|
Maxconns = 16,
|
||||||
|
Maxcontexts = 64,
|
||||||
Relmask = 1<<30,
|
Relmask = 1<<30,
|
||||||
|
Ibussupermask = 1<<26,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Watch Watch;
|
typedef struct Watch Watch;
|
||||||
@@ -23,12 +25,26 @@ struct Watch
|
|||||||
DBusWatch *w;
|
DBusWatch *w;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct Ictx Ictx;
|
||||||
|
struct Ictx
|
||||||
|
{
|
||||||
|
DBusConnection *conn;
|
||||||
|
char path[64];
|
||||||
|
uvlong owner;
|
||||||
|
int focused;
|
||||||
|
Caret caret;
|
||||||
|
};
|
||||||
|
|
||||||
static Watch watches[Maxwatches];
|
static Watch watches[Maxwatches];
|
||||||
static int nwatches;
|
static int nwatches;
|
||||||
static DBusConnection *conns[Maxconns];
|
static DBusConnection *conns[Maxconns];
|
||||||
static int nconns;
|
static int nconns;
|
||||||
static DBusServer *srv;
|
static DBusServer *srv;
|
||||||
static char addrfile[256];
|
static Ictx contexts[Maxcontexts];
|
||||||
|
static char addrfile[512];
|
||||||
|
static dev_t addrdev;
|
||||||
|
static ino_t addrino;
|
||||||
|
static int addrowned;
|
||||||
static int icctr;
|
static int icctr;
|
||||||
static int busctr;
|
static int busctr;
|
||||||
static Channel *replyc;
|
static Channel *replyc;
|
||||||
@@ -38,8 +54,12 @@ static DBusHandlerResult onmsg(DBusConnection*, DBusMessage*, void*);
|
|||||||
static void
|
static void
|
||||||
unlinkaddr(void)
|
unlinkaddr(void)
|
||||||
{
|
{
|
||||||
if(addrfile[0] != '\0')
|
struct stat st;
|
||||||
|
|
||||||
|
if(addrowned && lstat(addrfile, &st) == 0 &&
|
||||||
|
st.st_dev == addrdev && st.st_ino == addrino)
|
||||||
unlink(addrfile);
|
unlink(addrfile);
|
||||||
|
addrowned = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -97,7 +117,8 @@ buildaddrpath(char *buf, int sz)
|
|||||||
{
|
{
|
||||||
char mid[64], host[64], num[8], *cfg, *home;
|
char mid[64], host[64], num[8], *cfg, *home;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
char dir[256];
|
char dir[512];
|
||||||
|
int n;
|
||||||
|
|
||||||
machineid(mid, sizeof(mid));
|
machineid(mid, sizeof(mid));
|
||||||
if(mid[0] == '\0')
|
if(mid[0] == '\0')
|
||||||
@@ -105,15 +126,17 @@ buildaddrpath(char *buf, int sz)
|
|||||||
xdisplay(host, sizeof(host), num, sizeof(num));
|
xdisplay(host, sizeof(host), num, sizeof(num));
|
||||||
cfg = getenv("XDG_CONFIG_HOME");
|
cfg = getenv("XDG_CONFIG_HOME");
|
||||||
if(cfg != nil && cfg[0] != '\0')
|
if(cfg != nil && cfg[0] != '\0')
|
||||||
snprintf(dir, sizeof(dir), "%s/ibus/bus", cfg);
|
n = snprintf(dir, sizeof(dir), "%s/ibus/bus", cfg);
|
||||||
else{
|
else{
|
||||||
home = getenv("HOME");
|
home = getenv("HOME");
|
||||||
if(home == nil)
|
if(home == nil)
|
||||||
return -1;
|
return -1;
|
||||||
snprintf(dir, sizeof(dir), "%s/.config/ibus/bus", home);
|
n = snprintf(dir, sizeof(dir), "%s/.config/ibus/bus", home);
|
||||||
}
|
}
|
||||||
|
if(n < 0 || n >= (int)sizeof dir)
|
||||||
|
return -1;
|
||||||
if(stat(dir, &st) < 0){
|
if(stat(dir, &st) < 0){
|
||||||
char tmp[256];
|
char tmp[512];
|
||||||
char *p;
|
char *p;
|
||||||
strncpy(tmp, dir, sizeof(tmp));
|
strncpy(tmp, dir, sizeof(tmp));
|
||||||
tmp[sizeof(tmp)-1] = '\0';
|
tmp[sizeof(tmp)-1] = '\0';
|
||||||
@@ -125,23 +148,55 @@ buildaddrpath(char *buf, int sz)
|
|||||||
}
|
}
|
||||||
mkdir(tmp, 0700);
|
mkdir(tmp, 0700);
|
||||||
}
|
}
|
||||||
snprintf(buf, sz, "%s/%s-%s-%s", dir, mid, host, num);
|
n = snprintf(buf, sz, "%s/%s-%s-%s", dir, mid, host, num);
|
||||||
return 0;
|
return n < 0 || n >= sz ? -1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
writeaddr(char *path, char *addr)
|
writeaddr(char *path, char *addr)
|
||||||
{
|
{
|
||||||
|
char tmp[576];
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
|
struct stat st;
|
||||||
|
int fd, n, ok;
|
||||||
|
|
||||||
fp = fopen(path, "w");
|
n = snprintf(tmp, sizeof tmp, "%s.tmp.XXXXXX", path);
|
||||||
if(fp == nil)
|
if(n < 0 || n >= (int)sizeof tmp)
|
||||||
return -1;
|
return -1;
|
||||||
fprintf(fp, "IBUS_ADDRESS=%s\n", addr);
|
fd = mkstemp(tmp);
|
||||||
fprintf(fp, "IBUS_DAEMON_PID=%d\n", (int)getpid());
|
if(fd < 0)
|
||||||
fclose(fp);
|
return -1;
|
||||||
chmod(path, 0600);
|
ok = 0;
|
||||||
return 0;
|
fp = nil;
|
||||||
|
if(fchmod(fd, 0600) < 0)
|
||||||
|
goto out;
|
||||||
|
fp = fdopen(fd, "w");
|
||||||
|
if(fp == nil)
|
||||||
|
goto out;
|
||||||
|
fd = -1;
|
||||||
|
if(fprintf(fp, "IBUS_ADDRESS=%s\n", addr) < 0 ||
|
||||||
|
fprintf(fp, "IBUS_DAEMON_PID=%d\n", (int)getpid()) < 0 ||
|
||||||
|
fflush(fp) < 0 || fsync(fileno(fp)) < 0)
|
||||||
|
goto out;
|
||||||
|
if(fclose(fp) < 0){
|
||||||
|
fp = nil;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
fp = nil;
|
||||||
|
if(rename(tmp, path) < 0 || lstat(path, &st) < 0)
|
||||||
|
goto out;
|
||||||
|
addrdev = st.st_dev;
|
||||||
|
addrino = st.st_ino;
|
||||||
|
addrowned = 1;
|
||||||
|
ok = 1;
|
||||||
|
out:
|
||||||
|
if(fp != nil)
|
||||||
|
fclose(fp);
|
||||||
|
if(fd >= 0)
|
||||||
|
close(fd);
|
||||||
|
if(!ok)
|
||||||
|
unlink(tmp);
|
||||||
|
return ok ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static dbus_bool_t
|
static dbus_bool_t
|
||||||
@@ -203,29 +258,72 @@ mget(u32int state)
|
|||||||
if(state & (1<<0)) m |= Mshift;
|
if(state & (1<<0)) m |= Mshift;
|
||||||
if(state & (1<<2)) m |= Mctrl;
|
if(state & (1<<2)) m |= Mctrl;
|
||||||
if(state & (1<<3)) m |= Malt;
|
if(state & (1<<3)) m |= Malt;
|
||||||
if(state & (1<<6)) m |= Msuper;
|
if(state & ((1<<6)|Ibussupermask)) m |= Msuper;
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Ictx*
|
||||||
|
findcontext(DBusConnection *conn, const char *path)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < nelem(contexts); i++)
|
||||||
|
if(contexts[i].conn == conn && strcmp(contexts[i].path, path) == 0)
|
||||||
|
return &contexts[i];
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Ictx*
|
||||||
|
newcontext(DBusConnection *conn, const char *path)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < nelem(contexts); i++)
|
||||||
|
if(contexts[i].conn == nil){
|
||||||
|
memset(&contexts[i], 0, sizeof contexts[i]);
|
||||||
|
contexts[i].conn = conn;
|
||||||
|
contexts[i].owner = ownernew();
|
||||||
|
strncpy(contexts[i].path, path, sizeof contexts[i].path);
|
||||||
|
contexts[i].path[sizeof contexts[i].path-1] = '\0';
|
||||||
|
return &contexts[i];
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sendkey(u32int ks, u32int mod, Keyres *res)
|
sendrequest(Ictx *ctx, int op, u32int ks, u32int mod, Keyres *res)
|
||||||
{
|
{
|
||||||
Keyreq kr;
|
Keyreq kr;
|
||||||
|
|
||||||
|
memset(&kr, 0, sizeof kr);
|
||||||
|
kr.owner = ctx->owner;
|
||||||
|
kr.op = op;
|
||||||
kr.ks = ks;
|
kr.ks = ks;
|
||||||
kr.mod = mod;
|
kr.mod = mod;
|
||||||
kr.want = 1;
|
kr.caret = ctx->caret;
|
||||||
|
kr.want = op == Keypress;
|
||||||
kr.reply = replyc;
|
kr.reply = replyc;
|
||||||
chansend(keyc, &kr);
|
chansend(keyc, &kr);
|
||||||
chanrecv(replyc, res);
|
chanrecv(replyc, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sendreset(void)
|
releasecontext(Ictx *ctx)
|
||||||
{
|
{
|
||||||
Keyres res;
|
Keyres res;
|
||||||
|
|
||||||
sendkey(Kesc, 0, &res);
|
if(replyc != nil)
|
||||||
|
sendrequest(ctx, Keyrelease, 0, 0, &res);
|
||||||
|
ctx->focused = 0;
|
||||||
|
memset(&ctx->caret, 0, sizeof ctx->caret);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dropcontext(Ictx *ctx)
|
||||||
|
{
|
||||||
|
releasecontext(ctx);
|
||||||
|
memset(ctx, 0, sizeof *ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -308,15 +406,53 @@ handlehello(DBusConnection *c, DBusMessage *m)
|
|||||||
return DBUS_HANDLER_RESULT_HANDLED;
|
return DBUS_HANDLER_RESULT_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static DBusHandlerResult
|
||||||
|
handleerror(DBusConnection *c, DBusMessage *m, const char *name,
|
||||||
|
const char *text)
|
||||||
|
{
|
||||||
|
DBusMessage *r;
|
||||||
|
|
||||||
|
r = dbus_message_new_error(m, name, text);
|
||||||
|
if(r != nil){
|
||||||
|
dbus_connection_send(c, r, nil);
|
||||||
|
dbus_message_unref(r);
|
||||||
|
}
|
||||||
|
return DBUS_HANDLER_RESULT_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DBusHandlerResult
|
||||||
|
handlebool(DBusConnection *c, DBusMessage *m, int value)
|
||||||
|
{
|
||||||
|
DBusMessage *r;
|
||||||
|
dbus_bool_t b;
|
||||||
|
|
||||||
|
r = dbus_message_new_method_return(m);
|
||||||
|
b = value ? TRUE : FALSE;
|
||||||
|
dbus_message_append_args(r, DBUS_TYPE_BOOLEAN, &b, DBUS_TYPE_INVALID);
|
||||||
|
dbus_connection_send(c, r, nil);
|
||||||
|
dbus_message_unref(r);
|
||||||
|
return DBUS_HANDLER_RESULT_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
static DBusHandlerResult
|
static DBusHandlerResult
|
||||||
handlecreate(DBusConnection *c, DBusMessage *m)
|
handlecreate(DBusConnection *c, DBusMessage *m)
|
||||||
{
|
{
|
||||||
DBusMessage *r;
|
DBusMessage *r;
|
||||||
char path[64];
|
char path[64];
|
||||||
const char *pp;
|
const char *pp;
|
||||||
|
Ictx *ctx;
|
||||||
|
int n;
|
||||||
|
|
||||||
icctr++;
|
icctr++;
|
||||||
snprintf(path, sizeof(path), "/org/freedesktop/IBus/InputContext_%d", icctr);
|
n = snprintf(path, sizeof(path),
|
||||||
|
"/org/freedesktop/IBus/InputContext_%d", icctr);
|
||||||
|
if(n < 0 || n >= (int)sizeof path)
|
||||||
|
return handleerror(c, m, DBUS_ERROR_LIMITS_EXCEEDED,
|
||||||
|
"input context path exhausted");
|
||||||
|
ctx = newcontext(c, path);
|
||||||
|
if(ctx == nil)
|
||||||
|
return handleerror(c, m, DBUS_ERROR_LIMITS_EXCEEDED,
|
||||||
|
"too many input contexts");
|
||||||
pp = path;
|
pp = path;
|
||||||
r = dbus_message_new_method_return(m);
|
r = dbus_message_new_method_return(m);
|
||||||
dbus_message_append_args(r, DBUS_TYPE_OBJECT_PATH, &pp, DBUS_TYPE_INVALID);
|
dbus_message_append_args(r, DBUS_TYPE_OBJECT_PATH, &pp, DBUS_TYPE_INVALID);
|
||||||
@@ -326,14 +462,12 @@ handlecreate(DBusConnection *c, DBusMessage *m)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static DBusHandlerResult
|
static DBusHandlerResult
|
||||||
handlekey(DBusConnection *c, DBusMessage *m)
|
handlekey(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
||||||
{
|
{
|
||||||
DBusMessage *r;
|
|
||||||
DBusError err;
|
DBusError err;
|
||||||
dbus_uint32_t sym, code, state;
|
dbus_uint32_t sym, code, state;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
char commit[Maxutf], preedit[Maxutf];
|
char commit[Maxutf], preedit[Maxutf];
|
||||||
dbus_bool_t b;
|
|
||||||
u32int ks, mod;
|
u32int ks, mod;
|
||||||
|
|
||||||
dbus_error_init(&err);
|
dbus_error_init(&err);
|
||||||
@@ -346,28 +480,17 @@ handlekey(DBusConnection *c, DBusMessage *m)
|
|||||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||||
}
|
}
|
||||||
USED(code);
|
USED(code);
|
||||||
if(state & Relmask){
|
if(state & Relmask || !ctx->focused)
|
||||||
r = dbus_message_new_method_return(m);
|
return handlebool(c, m, 0);
|
||||||
b = FALSE;
|
|
||||||
dbus_message_append_args(r, DBUS_TYPE_BOOLEAN, &b, DBUS_TYPE_INVALID);
|
|
||||||
dbus_connection_send(c, r, nil);
|
|
||||||
dbus_message_unref(r);
|
|
||||||
return DBUS_HANDLER_RESULT_HANDLED;
|
|
||||||
}
|
|
||||||
ks = kget(sym);
|
ks = kget(sym);
|
||||||
mod = mget(state);
|
mod = mget(state);
|
||||||
sendkey(ks, mod, &res);
|
sendrequest(ctx, Keypress, ks, mod, &res);
|
||||||
stoutf(&res.commit, commit, sizeof commit);
|
stoutf(&res.commit, commit, sizeof commit);
|
||||||
stoutf(&res.preedit, preedit, sizeof preedit);
|
stoutf(&res.preedit, preedit, sizeof preedit);
|
||||||
if(commit[0] != '\0')
|
if(commit[0] != '\0')
|
||||||
emitcommit(c, dbus_message_get_path(m), commit);
|
emitcommit(c, dbus_message_get_path(m), commit);
|
||||||
emitpreedit(c, dbus_message_get_path(m), preedit);
|
emitpreedit(c, dbus_message_get_path(m), preedit);
|
||||||
r = dbus_message_new_method_return(m);
|
return handlebool(c, m, res.eaten);
|
||||||
b = res.eaten ? TRUE : FALSE;
|
|
||||||
dbus_message_append_args(r, DBUS_TYPE_BOOLEAN, &b, DBUS_TYPE_INVALID);
|
|
||||||
dbus_connection_send(c, r, nil);
|
|
||||||
dbus_message_unref(r);
|
|
||||||
return DBUS_HANDLER_RESULT_HANDLED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static DBusHandlerResult
|
static DBusHandlerResult
|
||||||
@@ -382,13 +505,65 @@ handlenoop(DBusConnection *c, DBusMessage *m)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static DBusHandlerResult
|
static DBusHandlerResult
|
||||||
handlereset(DBusConnection *c, DBusMessage *m)
|
handlereset(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
||||||
{
|
{
|
||||||
sendreset();
|
Keyres res;
|
||||||
|
|
||||||
|
sendrequest(ctx, Keyreset, 0, 0, &res);
|
||||||
emitpreedit(c, dbus_message_get_path(m), "");
|
emitpreedit(c, dbus_message_get_path(m), "");
|
||||||
return handlenoop(c, m);
|
return handlenoop(c, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static DBusHandlerResult
|
||||||
|
handlefocusin(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
||||||
|
{
|
||||||
|
ctx->focused = 1;
|
||||||
|
return handlenoop(c, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DBusHandlerResult
|
||||||
|
handlefocusout(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
||||||
|
{
|
||||||
|
releasecontext(ctx);
|
||||||
|
emitpreedit(c, dbus_message_get_path(m), "");
|
||||||
|
return handlenoop(c, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DBusHandlerResult
|
||||||
|
handledestroy(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
||||||
|
{
|
||||||
|
dropcontext(ctx);
|
||||||
|
return handlenoop(c, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DBusHandlerResult
|
||||||
|
handlecursor(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
||||||
|
{
|
||||||
|
DBusError err;
|
||||||
|
dbus_int32_t x, y, w, h;
|
||||||
|
Keyres res;
|
||||||
|
|
||||||
|
dbus_error_init(&err);
|
||||||
|
if(!dbus_message_get_args(m, &err,
|
||||||
|
DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y,
|
||||||
|
DBUS_TYPE_INT32, &w, DBUS_TYPE_INT32, &h,
|
||||||
|
DBUS_TYPE_INVALID)){
|
||||||
|
dbus_error_free(&err);
|
||||||
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
||||||
|
"SetCursorLocation expects four integers");
|
||||||
|
}
|
||||||
|
if(w < 0 || h < 0)
|
||||||
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
||||||
|
"cursor dimensions must not be negative");
|
||||||
|
ctx->caret.valid = 1;
|
||||||
|
ctx->caret.x = x;
|
||||||
|
ctx->caret.y = y;
|
||||||
|
ctx->caret.h = h;
|
||||||
|
if(ctx->focused)
|
||||||
|
sendrequest(ctx, Keycaret, 0, 0, &res);
|
||||||
|
return handlenoop(c, m);
|
||||||
|
}
|
||||||
|
|
||||||
static const char introspectxml[] =
|
static const char introspectxml[] =
|
||||||
"<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
|
"<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
|
||||||
" \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
|
" \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
|
||||||
@@ -438,6 +613,7 @@ static DBusHandlerResult
|
|||||||
onmsg(DBusConnection *c, DBusMessage *m, void *_)
|
onmsg(DBusConnection *c, DBusMessage *m, void *_)
|
||||||
{
|
{
|
||||||
const char *iface, *member, *path;
|
const char *iface, *member, *path;
|
||||||
|
Ictx *ctx;
|
||||||
|
|
||||||
USED(_);
|
USED(_);
|
||||||
if(dbus_message_get_type(m) != DBUS_MESSAGE_TYPE_METHOD_CALL)
|
if(dbus_message_get_type(m) != DBUS_MESSAGE_TYPE_METHOD_CALL)
|
||||||
@@ -462,15 +638,23 @@ onmsg(DBusConnection *c, DBusMessage *m, void *_)
|
|||||||
return handlecreate(c, m);
|
return handlecreate(c, m);
|
||||||
}
|
}
|
||||||
if(strcmp(iface, "org.freedesktop.IBus.InputContext") == 0){
|
if(strcmp(iface, "org.freedesktop.IBus.InputContext") == 0){
|
||||||
|
ctx = findcontext(c, path);
|
||||||
|
if(ctx == nil)
|
||||||
|
return handleerror(c, m, DBUS_ERROR_UNKNOWN_OBJECT,
|
||||||
|
"unknown input context");
|
||||||
if(strcmp(member, "ProcessKeyEvent") == 0)
|
if(strcmp(member, "ProcessKeyEvent") == 0)
|
||||||
return handlekey(c, m);
|
return handlekey(c, m, ctx);
|
||||||
if(strcmp(member, "FocusOut") == 0
|
if(strcmp(member, "FocusIn") == 0)
|
||||||
|| strcmp(member, "Reset") == 0)
|
return handlefocusin(c, m, ctx);
|
||||||
return handlereset(c, m);
|
if(strcmp(member, "FocusOut") == 0)
|
||||||
if(strcmp(member, "FocusIn") == 0
|
return handlefocusout(c, m, ctx);
|
||||||
|| strcmp(member, "Destroy") == 0
|
if(strcmp(member, "Reset") == 0)
|
||||||
|| strcmp(member, "SetCapabilities") == 0
|
return handlereset(c, m, ctx);
|
||||||
|| strcmp(member, "SetCursorLocation") == 0
|
if(strcmp(member, "Destroy") == 0)
|
||||||
|
return handledestroy(c, m, ctx);
|
||||||
|
if(strcmp(member, "SetCursorLocation") == 0)
|
||||||
|
return handlecursor(c, m, ctx);
|
||||||
|
if(strcmp(member, "SetCapabilities") == 0
|
||||||
|| strcmp(member, "SetEngine") == 0)
|
|| strcmp(member, "SetEngine") == 0)
|
||||||
return handlenoop(c, m);
|
return handlenoop(c, m);
|
||||||
}
|
}
|
||||||
@@ -497,7 +681,7 @@ newconn(DBusServer *s, DBusConnection *c, void *_)
|
|||||||
static void
|
static void
|
||||||
pruneconns(void)
|
pruneconns(void)
|
||||||
{
|
{
|
||||||
int i, j;
|
int i, j, k;
|
||||||
|
|
||||||
j = 0;
|
j = 0;
|
||||||
for(i = 0; i < nconns; i++){
|
for(i = 0; i < nconns; i++){
|
||||||
@@ -505,6 +689,9 @@ pruneconns(void)
|
|||||||
conns[j++] = conns[i];
|
conns[j++] = conns[i];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
for(k = 0; k < nelem(contexts); k++)
|
||||||
|
if(contexts[k].conn == conns[i])
|
||||||
|
dropcontext(&contexts[k]);
|
||||||
dbus_connection_unref(conns[i]);
|
dbus_connection_unref(conns[i]);
|
||||||
}
|
}
|
||||||
nconns = j;
|
nconns = j;
|
||||||
@@ -521,8 +708,11 @@ ibusinit(void)
|
|||||||
fprintf(stderr, "strans: ibus: cannot build address path\n");
|
fprintf(stderr, "strans: ibus: cannot build address path\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
unlink(addrfile);
|
if(snprintf(addr, sizeof(addr), "unix:abstract=strans-%d",
|
||||||
snprintf(addr, sizeof(addr), "unix:abstract=strans-%d", (int)getpid());
|
(int)getpid()) >= (int)sizeof addr){
|
||||||
|
fprintf(stderr, "strans: ibus: address is too long\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
dbus_error_init(&err);
|
dbus_error_init(&err);
|
||||||
srv = dbus_server_listen(addr, &err);
|
srv = dbus_server_listen(addr, &err);
|
||||||
if(srv == nil){
|
if(srv == nil){
|
||||||
@@ -552,6 +742,7 @@ void
|
|||||||
ibusthread(void *_)
|
ibusthread(void *_)
|
||||||
{
|
{
|
||||||
struct pollfd pfds[Maxwatches];
|
struct pollfd pfds[Maxwatches];
|
||||||
|
DBusWatch *polled[Maxwatches];
|
||||||
int wi[Maxwatches];
|
int wi[Maxwatches];
|
||||||
int i, n, rv;
|
int i, n, rv;
|
||||||
unsigned int f;
|
unsigned int f;
|
||||||
@@ -574,6 +765,7 @@ ibusthread(void *_)
|
|||||||
if(f & DBUS_WATCH_READABLE) pfds[n].events |= POLLIN;
|
if(f & DBUS_WATCH_READABLE) pfds[n].events |= POLLIN;
|
||||||
if(f & DBUS_WATCH_WRITABLE) pfds[n].events |= POLLOUT;
|
if(f & DBUS_WATCH_WRITABLE) pfds[n].events |= POLLOUT;
|
||||||
wi[n] = i;
|
wi[n] = i;
|
||||||
|
polled[n] = watches[i].w;
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
rv = poll(pfds, n, 200);
|
rv = poll(pfds, n, 200);
|
||||||
@@ -582,14 +774,14 @@ ibusthread(void *_)
|
|||||||
for(i = 0; i < n; i++){
|
for(i = 0; i < n; i++){
|
||||||
if(pfds[i].revents == 0)
|
if(pfds[i].revents == 0)
|
||||||
continue;
|
continue;
|
||||||
if(watches[wi[i]].w == nil)
|
if(watches[wi[i]].w != polled[i])
|
||||||
continue;
|
continue;
|
||||||
f = 0;
|
f = 0;
|
||||||
if(pfds[i].revents & POLLIN) f |= DBUS_WATCH_READABLE;
|
if(pfds[i].revents & POLLIN) f |= DBUS_WATCH_READABLE;
|
||||||
if(pfds[i].revents & POLLOUT) f |= DBUS_WATCH_WRITABLE;
|
if(pfds[i].revents & POLLOUT) f |= DBUS_WATCH_WRITABLE;
|
||||||
if(pfds[i].revents & POLLHUP) f |= DBUS_WATCH_HANGUP;
|
if(pfds[i].revents & POLLHUP) f |= DBUS_WATCH_HANGUP;
|
||||||
if(pfds[i].revents & POLLERR) f |= DBUS_WATCH_ERROR;
|
if(pfds[i].revents & POLLERR) f |= DBUS_WATCH_ERROR;
|
||||||
dbus_watch_handle(watches[wi[i]].w, f);
|
dbus_watch_handle(polled[i], f);
|
||||||
}
|
}
|
||||||
for(i = 0; i < nconns; i++)
|
for(i = 0; i < nconns; i++)
|
||||||
while(dbus_connection_dispatch(conns[i]) == DBUS_DISPATCH_DATA_REMAINS)
|
while(dbus_connection_dispatch(conns[i]) == DBUS_DISPATCH_DATA_REMAINS)
|
||||||
|
|||||||
104
ipc.c
104
ipc.c
@@ -1,13 +1,12 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
#include "ipc.h"
|
#include "ipc.h"
|
||||||
|
|
||||||
#if !defined(MSG_NOSIGNAL) && !defined(SO_NOSIGPIPE)
|
|
||||||
#error "ipcsend requires MSG_NOSIGNAL or SO_NOSIGPIPE"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
putlen(unsigned char p[Ipclensz], size_t n)
|
putlen(unsigned char p[Ipclensz], size_t n)
|
||||||
{
|
{
|
||||||
@@ -21,11 +20,54 @@ getlen(const unsigned char p[Ipclensz])
|
|||||||
return p[0] | (p[1] << 8);
|
return p[0] | (p[1] << 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
int
|
||||||
ipcpackreq(unsigned char req[Ipcreqsz], int want, unsigned int mod,
|
ipcpath(char *dst, size_t cap)
|
||||||
unsigned int key)
|
|
||||||
{
|
{
|
||||||
req[0] = want != 0;
|
const char *dir, *sep;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if(dst == NULL || cap == 0)
|
||||||
|
return -1;
|
||||||
|
dir = getenv("XDG_RUNTIME_DIR");
|
||||||
|
if(dir != NULL && dir[0] == '/'){
|
||||||
|
sep = dir[strlen(dir)-1] == '/' ? "" : "/";
|
||||||
|
n = snprintf(dst, cap, "%s%sstrans.sock", dir, sep);
|
||||||
|
}else
|
||||||
|
n = snprintf(dst, cap, "/tmp/strans.%lu",
|
||||||
|
(unsigned long)getuid());
|
||||||
|
if(n < 0 || (size_t)n >= cap)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ipcconnect(void)
|
||||||
|
{
|
||||||
|
struct sockaddr_un addr;
|
||||||
|
int e, fd;
|
||||||
|
|
||||||
|
memset(&addr, 0, sizeof addr);
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
if(ipcpath(addr.sun_path, sizeof addr.sun_path) < 0){
|
||||||
|
errno = ENAMETOOLONG;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if(fd < 0)
|
||||||
|
return -1;
|
||||||
|
if(connect(fd, (struct sockaddr*)&addr, sizeof addr) == 0)
|
||||||
|
return fd;
|
||||||
|
e = errno;
|
||||||
|
close(fd);
|
||||||
|
errno = e;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ipcpackreq(unsigned char req[Ipcreqsz], int want, uint32_t mod,
|
||||||
|
uint32_t key)
|
||||||
|
{
|
||||||
|
req[0] = want ? Ipcreqwant : 0;
|
||||||
req[1] = mod;
|
req[1] = mod;
|
||||||
req[2] = key;
|
req[2] = key;
|
||||||
req[3] = key >> 8;
|
req[3] = key >> 8;
|
||||||
@@ -34,15 +76,28 @@ ipcpackreq(unsigned char req[Ipcreqsz], int want, unsigned int mod,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, unsigned int *mod,
|
ipcpackreset(unsigned char req[Ipcreqsz], int want)
|
||||||
unsigned int *key)
|
|
||||||
{
|
{
|
||||||
*want = req[0] != 0;
|
memset(req, 0, Ipcreqsz);
|
||||||
|
req[0] = Ipcreqreset | (want ? Ipcreqwant : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
||||||
|
uint32_t *key)
|
||||||
|
{
|
||||||
|
*want = (req[0] & Ipcreqwant) != 0;
|
||||||
*mod = req[1] & Mmask;
|
*mod = req[1] & Mmask;
|
||||||
*key = (unsigned int)req[2] |
|
*key = (uint32_t)req[2] |
|
||||||
((unsigned int)req[3] << 8) |
|
((uint32_t)req[3] << 8) |
|
||||||
((unsigned int)req[4] << 16) |
|
((uint32_t)req[4] << 16) |
|
||||||
((unsigned int)req[5] << 24);
|
((uint32_t)req[5] << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ipcreqreset(const unsigned char req[Ipcreqsz])
|
||||||
|
{
|
||||||
|
return (req[0] & Ipcreqreset) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
@@ -97,27 +152,10 @@ ipcsend(int fd, const void *buf, size_t n)
|
|||||||
{
|
{
|
||||||
const unsigned char *p;
|
const unsigned char *p;
|
||||||
ssize_t r;
|
ssize_t r;
|
||||||
int flags;
|
|
||||||
#if !defined(MSG_NOSIGNAL) && defined(SO_NOSIGPIPE)
|
|
||||||
int one, sr;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
p = buf;
|
p = buf;
|
||||||
#ifdef MSG_NOSIGNAL
|
|
||||||
flags = MSG_NOSIGNAL;
|
|
||||||
#else
|
|
||||||
flags = 0;
|
|
||||||
#ifdef SO_NOSIGPIPE
|
|
||||||
one = 1;
|
|
||||||
do
|
|
||||||
sr = setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof one);
|
|
||||||
while(sr < 0 && errno == EINTR);
|
|
||||||
if(sr < 0)
|
|
||||||
return -1;
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
while(n > 0){
|
while(n > 0){
|
||||||
r = send(fd, p, n, flags);
|
r = send(fd, p, n, MSG_NOSIGNAL);
|
||||||
if(r < 0 && errno == EINTR)
|
if(r < 0 && errno == EINTR)
|
||||||
continue;
|
continue;
|
||||||
if(r <= 0)
|
if(r <= 0)
|
||||||
|
|||||||
24
ipc.h
24
ipc.h
@@ -1,12 +1,11 @@
|
|||||||
#ifndef STRANS_IPC_H
|
|
||||||
#define STRANS_IPC_H
|
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
#define IPCPATH "/tmp/strans.%d"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Request: [want-preedit, modifiers, key byte 0, ..., key byte 3].
|
* Request: [flags, modifiers, key byte 0, ..., key byte 3].
|
||||||
|
* Flags request preedit and distinguish lifecycle reset from physical Escape.
|
||||||
|
* The server assigns the connection's owner generation; it is not sent on
|
||||||
|
* the wire.
|
||||||
* Only Mmask modifier bits are retained.
|
* Only Mmask modifier bits are retained.
|
||||||
* Response: [eaten, commit-length-low, commit-length-high, commit...],
|
* Response: [eaten, commit-length-low, commit-length-high, commit...],
|
||||||
* followed, when requested, by
|
* followed, when requested, by
|
||||||
@@ -16,6 +15,9 @@
|
|||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
|
Ipcreqwant = 1<<0,
|
||||||
|
Ipcreqreset = 1<<1,
|
||||||
|
|
||||||
Kspec = 0x110000,
|
Kspec = 0x110000,
|
||||||
Kback = Kspec|0x08,
|
Kback = Kspec|0x08,
|
||||||
Ktab = Kspec|0x09,
|
Ktab = Kspec|0x09,
|
||||||
@@ -47,11 +49,13 @@ struct Ipcresp
|
|||||||
size_t npreedit;
|
size_t npreedit;
|
||||||
};
|
};
|
||||||
|
|
||||||
void ipcpackreq(unsigned char[Ipcreqsz], int, unsigned int, unsigned int);
|
void ipcpackreq(unsigned char[Ipcreqsz], int, uint32_t, uint32_t);
|
||||||
void ipcunpackreq(const unsigned char[Ipcreqsz], int*, unsigned int*, unsigned int*);
|
void ipcpackreset(unsigned char[Ipcreqsz], int);
|
||||||
|
void ipcunpackreq(const unsigned char[Ipcreqsz], int*, uint32_t*, uint32_t*);
|
||||||
|
int ipcreqreset(const unsigned char[Ipcreqsz]);
|
||||||
int ipcpackresp(unsigned char*, size_t, int, const char*, size_t, const char*, size_t, int);
|
int ipcpackresp(unsigned char*, size_t, int, const char*, size_t, const char*, size_t, int);
|
||||||
int ipcreadn(int, void*, size_t);
|
int ipcreadn(int, void*, size_t);
|
||||||
int ipcsend(int, const void*, size_t);
|
int ipcsend(int, const void*, size_t);
|
||||||
int ipcreadresp(int, int, char*, size_t, char*, size_t, Ipcresp*);
|
int ipcreadresp(int, int, char*, size_t, char*, size_t, Ipcresp*);
|
||||||
|
int ipcpath(char*, size_t);
|
||||||
#endif
|
int ipcconnect(void);
|
||||||
|
|||||||
28
main.c
28
main.c
@@ -7,12 +7,6 @@ Channel *dictreqc;
|
|||||||
Channel *dictresc;
|
Channel *dictresc;
|
||||||
char *fontdir;
|
char *fontdir;
|
||||||
|
|
||||||
int
|
|
||||||
threadmaybackground(void)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
usage(void)
|
usage(void)
|
||||||
{
|
{
|
||||||
@@ -38,10 +32,9 @@ emalloc(ulong n)
|
|||||||
{
|
{
|
||||||
void *p;
|
void *p;
|
||||||
|
|
||||||
p = malloc(n);
|
p = mallocz(n, 1);
|
||||||
if(p == nil)
|
if(p == nil)
|
||||||
die("out of memory");
|
die("out of memory");
|
||||||
memset(p, 0, n);
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,12 +60,15 @@ threadmain(int argc, char **argv)
|
|||||||
dictresc = chancreate(sizeof(Dictres), 0);
|
dictresc = chancreate(sizeof(Dictres), 0);
|
||||||
mapinit(argv[1]);
|
mapinit(argv[1]);
|
||||||
dictinit(argv[1]);
|
dictinit(argv[1]);
|
||||||
proccreate(drawthread, nil, 16384);
|
if(proccreate(drawthread, nil, 16384) < 0)
|
||||||
proccreate(srvthread, nil, 16384);
|
die("can't create draw worker");
|
||||||
proccreate(ibusthread, nil, 32768);
|
if(proccreate(srvthread, nil, 16384) < 0)
|
||||||
proccreate(waylandthread, nil, 32768);
|
die("can't create server worker");
|
||||||
threadcreate(dictthread, nil, 16384);
|
if(proccreate(ibusthread, nil, 32768) < 0)
|
||||||
threadcreate(imthread, nil, 16384);
|
die("can't create IBus worker");
|
||||||
|
if(proccreate(waylandthread, nil, 32768) < 0)
|
||||||
threadexits(nil);
|
die("can't create Wayland worker");
|
||||||
|
if(threadcreate(dictthread, nil, 16384) < 0)
|
||||||
|
die("can't create dictionary worker");
|
||||||
|
imthread(nil);
|
||||||
}
|
}
|
||||||
|
|||||||
51
map/README
51
map/README
@@ -1,12 +1,41 @@
|
|||||||
#
|
# Japanese dictionary data
|
||||||
# The following are a set of tools to obtain and process dictionaries from the SKK project in order to use them with ktrans(1).
|
|
||||||
#
|
|
||||||
# grabskkdicts pulls the skk kana-kanji conversion dictionaries from the skk-dev/dict repo.
|
|
||||||
# skk2ktrans takes an skk dictionary and converts it into a kanji jisho suitable to be used with ktrans(1).
|
|
||||||
#
|
|
||||||
# You can fetch and convert all the dictionaries by running this file.
|
|
||||||
#
|
|
||||||
|
|
||||||
grabskkdicts
|
`kanji.dict` is the historical dictionary bundled with strans. Its own header
|
||||||
for(d in skkdicts/SKK-JISYO.*)
|
identifies it as the SKK Medium dictionary, version 8.1 of May 24, 1995,
|
||||||
<$d skk2ktrans >$d.jisho
|
rearranged for Plan 9 ktrans by Kenji Okamoto on February 17, 2000. It was
|
||||||
|
already present when this repository was created in commit
|
||||||
|
`dcd1147638f908eca7e4b7616815e215022cc99d` on December 23, 2025. No original
|
||||||
|
SKK input file or upstream revision was committed, so the current file cannot
|
||||||
|
honestly be regenerated byte-for-byte from repository artifacts. The license
|
||||||
|
notice in its header permits redistribution and modification under GPL version
|
||||||
|
2 or later.
|
||||||
|
|
||||||
|
The repository copy keeps that historical candidate order. Duplicate keys
|
||||||
|
were merged at their first occurrence, later unseen candidates were appended
|
||||||
|
in source order, duplicate candidates were removed, and the empty
|
||||||
|
`ようたつ` row was removed.
|
||||||
|
|
||||||
|
## Importing a current SKK dictionary
|
||||||
|
|
||||||
|
From the repository root, fetch a known upstream revision and convert it with:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/skk-dev/dict.git map/skkdicts
|
||||||
|
git -C map/skkdicts checkout --detach REVISION
|
||||||
|
map/skk2ktrans map/skkdicts/SKK-JISYO.M >map/kanji.dict.new
|
||||||
|
python3 map/verifymap.py map/kanji.dict.new
|
||||||
|
```
|
||||||
|
|
||||||
|
Use a full skk-dev/dict commit ID for `REVISION` and record it when replacing
|
||||||
|
the bundled data. Git is needed only to fetch upstream data; it is not part of
|
||||||
|
the normal build image.
|
||||||
|
|
||||||
|
`skk2ktrans` accepts one or more EUC-JP SKK files (or standard input), writes
|
||||||
|
UTF-8 tab-separated rows, and merges input in command-line and source order.
|
||||||
|
It strips annotations, deduplicates candidates, and omits candidates containing
|
||||||
|
whitespace. Rows containing expressions or escaped candidate delimiters are
|
||||||
|
rejected; rewrite or omit those rows before import.
|
||||||
|
|
||||||
|
`verifymap.py` checks UTF-8, row structure, unique keys, 64-rune keys and
|
||||||
|
values, canonical candidate spacing, and duplicate dictionary candidates.
|
||||||
|
Pass it the exact `.map` and `.dict` files installed by the build.
|
||||||
|
|||||||
@@ -75,9 +75,14 @@ bl 😊
|
|||||||
blu 😊
|
blu 😊
|
||||||
blus 😊
|
blus 😊
|
||||||
blush 😊
|
blush 😊
|
||||||
c χ
|
c χ ☕️
|
||||||
ch χ
|
ch χ
|
||||||
chi χ
|
chi χ
|
||||||
|
co ☕️
|
||||||
|
cof ☕️
|
||||||
|
coff ☕️
|
||||||
|
coffe ☕️
|
||||||
|
coffee ☕️
|
||||||
d ° δ Δ ↓
|
d ° δ Δ ↓
|
||||||
de Δ ° δ
|
de Δ ° δ
|
||||||
deg °
|
deg °
|
||||||
@@ -198,6 +203,65 @@ zet ζ
|
|||||||
zeta ζ
|
zeta ζ
|
||||||
~ ≈
|
~ ≈
|
||||||
~= ≈
|
~= ≈
|
||||||
|
あ ❤️
|
||||||
|
あい ❤️
|
||||||
|
い 👍
|
||||||
|
いい 👍
|
||||||
|
いいね 👍
|
||||||
|
え 😀 😄 😊
|
||||||
|
えが 😀 😄 😊
|
||||||
|
えがお 😀 😄 😊
|
||||||
|
こ ☕️
|
||||||
|
こー ☕️
|
||||||
|
こーひ ☕️
|
||||||
|
こーひー ☕️
|
||||||
|
さ 👍
|
||||||
|
さん 👍
|
||||||
|
さんせ 👍
|
||||||
|
さんせい 👍
|
||||||
|
ば 😂
|
||||||
|
ばく 😂
|
||||||
|
ばくし 😂
|
||||||
|
ばくしょ 😂
|
||||||
|
ばくしょう 😂
|
||||||
|
ほ 😄 😊
|
||||||
|
ほほ 😄 😊
|
||||||
|
ほほえ 😄 😊
|
||||||
|
ほほえみ 😄 😊
|
||||||
|
わ 😀 😂
|
||||||
|
わら 😀 😂
|
||||||
|
わらい 😀 😂
|
||||||
|
ア ❤️
|
||||||
|
アイ ❤️
|
||||||
|
イ 👍
|
||||||
|
イイ 👍
|
||||||
|
イイネ 👍
|
||||||
|
エ 😀 😄 😊
|
||||||
|
エガ 😀 😄 😊
|
||||||
|
エガオ 😀 😄 😊
|
||||||
|
コ ☕️
|
||||||
|
コー ☕️
|
||||||
|
コーヒ ☕️
|
||||||
|
コーヒー ☕️
|
||||||
|
サ 👍
|
||||||
|
サン 👍
|
||||||
|
サンセ 👍
|
||||||
|
サンセイ 👍
|
||||||
|
ハ ❤️
|
||||||
|
ハー ❤️
|
||||||
|
ハート ❤️
|
||||||
|
バ 😂
|
||||||
|
バク 😂
|
||||||
|
バクシ 😂
|
||||||
|
バクショ 😂
|
||||||
|
バクショウ 😂
|
||||||
|
ホ 😄 😊
|
||||||
|
ホホ 😄 😊
|
||||||
|
ホホエ 😄 😊
|
||||||
|
ホホエミ 😄 😊
|
||||||
|
ワ 😀 😂
|
||||||
|
ワラ 😀 😂
|
||||||
|
ワライ 😀 😂
|
||||||
따 👍
|
따 👍
|
||||||
따봉 👍
|
따봉 👍
|
||||||
미 😄 😊
|
미 😄 😊
|
||||||
@@ -220,6 +284,8 @@ zeta ζ
|
|||||||
좋아요 👍
|
좋아요 👍
|
||||||
찬 👍
|
찬 👍
|
||||||
찬성 👍
|
찬성 👍
|
||||||
|
커 ☕️
|
||||||
|
커피 ☕️
|
||||||
폭 😂
|
폭 😂
|
||||||
폭소 😂
|
폭소 😂
|
||||||
하 ❤️
|
하 ❤️
|
||||||
|
|||||||
@@ -103,9 +103,10 @@
|
|||||||
ζ zeta
|
ζ zeta
|
||||||
|
|
||||||
# Small multilingual emoji seed. Shared aliases intentionally produce choices.
|
# Small multilingual emoji seed. Shared aliases intentionally produce choices.
|
||||||
😀 웃음 웃다 스마일 smile grin
|
😀 웃음 웃다 스마일 えがお エガオ わらい ワライ smile grin
|
||||||
😄 웃음 미소 스마일 smile happy
|
😄 웃음 미소 스마일 えがお エガオ ほほえみ ホホエミ smile happy
|
||||||
😂 웃음 폭소 웃겨 joy lol
|
😂 웃음 폭소 웃겨 ばくしょう バクショウ わらい ワライ joy lol
|
||||||
😊 미소 방긋 smile blush happy
|
😊 미소 방긋 ほほえみ ホホエミ えがお エガオ smile blush happy
|
||||||
❤️ 사랑 하트 heart love
|
❤️ 사랑 하트 あい アイ ハート heart love
|
||||||
👍 좋아요 찬성 따봉 엄지 thumbsup like
|
👍 좋아요 찬성 따봉 엄지 いいね イイネ さんせい サンセイ thumbsup like
|
||||||
|
☕️ 커피 こーひー コーヒー coffee
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/rc
|
|
||||||
git/clone https://github.com/skk-dev/dict skkdicts
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
|
|
||||||
, ,
|
|
||||||
. 。
|
|
||||||
< 《
|
|
||||||
> 》
|
|
||||||
/ /
|
|
||||||
? ?
|
|
||||||
; ;
|
|
||||||
: :
|
|
||||||
\ 、
|
|
||||||
| ・
|
|
||||||
` `
|
|
||||||
~ 〜
|
|
||||||
! !
|
|
||||||
@ @
|
|
||||||
# #
|
|
||||||
$ ¥
|
|
||||||
& &
|
|
||||||
* *
|
|
||||||
( (
|
|
||||||
) )
|
|
||||||
- -
|
|
||||||
+ +
|
|
||||||
= =
|
|
||||||
[ 「
|
|
||||||
] 」
|
|
||||||
{ 『
|
|
||||||
} 』
|
|
||||||
@@ -60,7 +60,6 @@
|
|||||||
あたらs 新
|
あたらs 新
|
||||||
あつi 熱 暑 厚
|
あつi 熱 暑 厚
|
||||||
あつk 厚 熱 暑
|
あつk 厚 熱 暑
|
||||||
あつk 厚 熱 暑
|
|
||||||
あつs 暑
|
あつs 暑
|
||||||
あつm 集 厚
|
あつm 集 厚
|
||||||
あつかe 扱
|
あつかe 扱
|
||||||
@@ -350,7 +349,6 @@
|
|||||||
かわいr 可愛
|
かわいr 可愛
|
||||||
かんj 感
|
かんj 感
|
||||||
かんs 関
|
かんs 関
|
||||||
かんj 感
|
|
||||||
かんz 感
|
かんz 感
|
||||||
かんがe 考
|
かんがe 考
|
||||||
かんしゃs 感謝
|
かんしゃs 感謝
|
||||||
@@ -455,7 +453,7 @@
|
|||||||
こu 乞 請
|
こu 乞 請
|
||||||
こy 来
|
こy 来
|
||||||
こうj 高
|
こうj 高
|
||||||
こうしょう 鉱床 高尚
|
こうしょう 鉱床 高尚 交渉
|
||||||
こうぶつ 鉱物 好物
|
こうぶつ 鉱物 好物
|
||||||
こうりょs 考慮
|
こうりょs 考慮
|
||||||
こえt 超
|
こえt 超
|
||||||
@@ -665,8 +663,6 @@
|
|||||||
ただc 直
|
ただc 直
|
||||||
ただt 直
|
ただt 直
|
||||||
たとe 例
|
たとe 例
|
||||||
たおr 倒
|
|
||||||
たおr 倒
|
|
||||||
たのm 頼
|
たのm 頼
|
||||||
たのn 頼
|
たのn 頼
|
||||||
たのs 楽
|
たのs 楽
|
||||||
@@ -742,7 +738,6 @@
|
|||||||
つみあげr 積み上げ
|
つみあげr 積み上げ
|
||||||
つみあげt 積み上げ
|
つみあげt 積み上げ
|
||||||
つめt 冷
|
つめt 冷
|
||||||
つづr 綴
|
|
||||||
つよi 強
|
つよi 強
|
||||||
つよk 強
|
つよk 強
|
||||||
つらi 辛
|
つらi 辛
|
||||||
@@ -793,7 +788,6 @@
|
|||||||
とt 取 撮 採
|
とt 取 撮 採
|
||||||
とu 問
|
とu 問
|
||||||
とw 問
|
とw 問
|
||||||
とj 閉
|
|
||||||
といあw 問い合
|
といあw 問い合
|
||||||
とうj 投
|
とうj 投
|
||||||
とおi 遠
|
とおi 遠
|
||||||
@@ -913,13 +907,12 @@
|
|||||||
ねらt 狙
|
ねらt 狙
|
||||||
ねらu 狙
|
ねらu 狙
|
||||||
のb 述 伸 延
|
のb 述 伸 延
|
||||||
のk 退
|
のk 退 乗
|
||||||
のm 飲 呑
|
のm 飲 呑
|
||||||
のn 飲
|
のn 飲
|
||||||
のr 乗 載
|
のr 乗 載
|
||||||
のs 載 乗
|
のs 載 乗
|
||||||
のt 乗 載
|
のt 乗 載
|
||||||
のk 乗
|
|
||||||
のこr 残
|
のこr 残
|
||||||
のこs 残
|
のこs 残
|
||||||
のこt 残
|
のこt 残
|
||||||
@@ -1059,12 +1052,11 @@
|
|||||||
ぼうえいs 防衛
|
ぼうえいs 防衛
|
||||||
まc 待
|
まc 待
|
||||||
まi 舞
|
まi 舞
|
||||||
まj 交
|
まj 交 混
|
||||||
まk 負 巻
|
まk 負 巻
|
||||||
まs 増
|
まs 増
|
||||||
まt 待
|
まt 待
|
||||||
まu 舞
|
まu 舞
|
||||||
まj 混
|
|
||||||
まz 混 先
|
まz 混 先
|
||||||
まいr 参 詣
|
まいr 参 詣
|
||||||
まいt 参 詣
|
まいt 参 詣
|
||||||
@@ -1241,8 +1233,6 @@
|
|||||||
よわm 弱
|
よわm 弱
|
||||||
よわs 弱
|
よわs 弱
|
||||||
よわt 弱
|
よわt 弱
|
||||||
よろこb 喜 慶
|
|
||||||
よろこb 喜 慶
|
|
||||||
ろんj 論
|
ろんj 論
|
||||||
ろんz 論
|
ろんz 論
|
||||||
わk 分 湧
|
わk 分 湧
|
||||||
@@ -1938,7 +1928,7 @@
|
|||||||
かいもの 買物
|
かいもの 買物
|
||||||
かいよう 海洋 潰瘍
|
かいよう 海洋 潰瘍
|
||||||
かいらん 回覧
|
かいらん 回覧
|
||||||
かいり 解離 乖離 海里 浬
|
かいり 解離 乖離 海里 浬
|
||||||
かいりょう 改良
|
かいりょう 改良
|
||||||
かいれい 海嶺
|
かいれい 海嶺
|
||||||
かいろ 回路
|
かいろ 回路
|
||||||
@@ -2267,7 +2257,6 @@
|
|||||||
がくわり 学割
|
がくわり 学割
|
||||||
がけ 崖
|
がけ 崖
|
||||||
がし 樫
|
がし 樫
|
||||||
かせい 火星
|
|
||||||
がそ 画素
|
がそ 画素
|
||||||
がそかん 画素間
|
がそかん 画素間
|
||||||
がそごと 画素毎
|
がそごと 画素毎
|
||||||
@@ -2993,7 +2982,6 @@
|
|||||||
こうしゃ 後者 公社 校舎
|
こうしゃ 後者 公社 校舎
|
||||||
こうしゅう 講習 公衆
|
こうしゅう 講習 公衆
|
||||||
こうしゅうかい 講習会
|
こうしゅうかい 講習会
|
||||||
こうしょう 交渉 鉱床
|
|
||||||
こうしん 更新 交信 後身
|
こうしん 更新 交信 後身
|
||||||
こうじ 麹 公示 高次 工事 孝二
|
こうじ 麹 公示 高次 工事 孝二
|
||||||
こうじつ 口実
|
こうじつ 口実
|
||||||
@@ -3509,7 +3497,7 @@
|
|||||||
しこく 四国
|
しこく 四国
|
||||||
しご 死後
|
しご 死後
|
||||||
しごと 仕事
|
しごと 仕事
|
||||||
しごとば 仕事場 堺市学園町1ー1
|
しごとば 仕事場 堺市学園町1ー1
|
||||||
しさ 示唆 視差
|
しさ 示唆 視差
|
||||||
しさい 仔細 子細
|
しさい 仔細 子細
|
||||||
しさく 試作
|
しさく 試作
|
||||||
@@ -4802,7 +4790,7 @@
|
|||||||
たいきゅうりょく 耐久力
|
たいきゅうりょく 耐久力
|
||||||
たいきょく 対極
|
たいきょく 対極
|
||||||
たいきん 大金
|
たいきん 大金
|
||||||
たいく 体躯
|
たいく 体躯 体育
|
||||||
たいくつ 退屈
|
たいくつ 退屈
|
||||||
たいけい 体系 体型
|
たいけい 体系 体型
|
||||||
たいけん 体験
|
たいけん 体験
|
||||||
@@ -4949,8 +4937,6 @@
|
|||||||
たんらく 短絡
|
たんらく 短絡
|
||||||
だ 騨 駄 陀 楕 舵 柁 打 惰 妥 堕 唾 田 朶
|
だ 騨 駄 陀 楕 舵 柁 打 惰 妥 堕 唾 田 朶
|
||||||
だい 大 第 内 代 台 題 醍
|
だい 大 第 内 代 台 題 醍
|
||||||
たいく 体育
|
|
||||||
たいいく 体育
|
|
||||||
だいいち 第一
|
だいいち 第一
|
||||||
だいがく 大学
|
だいがく 大学
|
||||||
だいがくいん 大学院
|
だいがくいん 大学院
|
||||||
@@ -5673,7 +5659,7 @@
|
|||||||
どんき 鈍器
|
どんき 鈍器
|
||||||
どんてん 曇天
|
どんてん 曇天
|
||||||
どんよく 貪欲
|
どんよく 貪欲
|
||||||
な 名 七 那 奈 魚 菜 字 肴 納 無 儺 己 汝 何
|
な 名 七 那 奈 魚 菜 字 肴 納 無 儺 己 汝 何
|
||||||
ない 内 無
|
ない 内 無
|
||||||
ないがい 内外
|
ないがい 内外
|
||||||
ないき 内記 内規
|
ないき 内記 内規
|
||||||
@@ -6816,7 +6802,7 @@
|
|||||||
ぼうし 防止
|
ぼうし 防止
|
||||||
ぼうそう 暴走
|
ぼうそう 暴走
|
||||||
ぼうだい 膨大
|
ぼうだい 膨大
|
||||||
ぼうちょう 膨張 防潮 傍聴
|
ぼうちょう 膨張 防潮 傍聴
|
||||||
ぼうとう 冒頭
|
ぼうとう 冒頭
|
||||||
ぼうどう 暴動
|
ぼうどう 暴動
|
||||||
ぼうねんかい 忘年会
|
ぼうねんかい 忘年会
|
||||||
@@ -7314,7 +7300,6 @@
|
|||||||
よこう 予行 予稿
|
よこう 予行 予稿
|
||||||
よこうち 横内
|
よこうち 横内
|
||||||
よこく 予告
|
よこく 予告
|
||||||
ようたつ
|
|
||||||
よこて 横手
|
よこて 横手
|
||||||
よこはま 横浜
|
よこはま 横浜
|
||||||
よこもじ 横文字
|
よこもじ 横文字
|
||||||
@@ -7426,7 +7411,7 @@
|
|||||||
りゅうど 粒度
|
りゅうど 粒度
|
||||||
りゆう 理由
|
りゆう 理由
|
||||||
りょ 虜 旅 慮 侶
|
りょ 虜 旅 慮 侶
|
||||||
りょう 量 寮 両 領 陵 遼 諒 良 糧 稜 瞭 療 猟 涼 梁 料 凌 僚 亮 了 漁 糧 粮 霊
|
りょう 量 寮 両 領 陵 遼 諒 良 糧 稜 瞭 療 猟 涼 梁 料 凌 僚 亮 了 漁 粮 霊
|
||||||
りょうあし 両足
|
りょうあし 両足
|
||||||
りょういき 領域
|
りょういき 領域
|
||||||
りょうか 量化
|
りょうか 量化
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ sho ショ
|
|||||||
syo ショ
|
syo ショ
|
||||||
ja ジャ
|
ja ジャ
|
||||||
ju ジュ
|
ju ジュ
|
||||||
je ジェ
|
|
||||||
jo ジョ
|
jo ジョ
|
||||||
su ス
|
su ス
|
||||||
zu ズ
|
zu ズ
|
||||||
@@ -119,6 +118,7 @@ xn ン
|
|||||||
v ヴ
|
v ヴ
|
||||||
xa ァ
|
xa ァ
|
||||||
xi ィ
|
xi ィ
|
||||||
|
xu ゥ
|
||||||
xe ェ
|
xe ェ
|
||||||
xo ォ
|
xo ォ
|
||||||
cc ッ
|
cc ッ
|
||||||
@@ -128,7 +128,6 @@ pp ッ
|
|||||||
tt ッ
|
tt ッ
|
||||||
tch ッ
|
tch ッ
|
||||||
ss ッ
|
ss ッ
|
||||||
xn ン
|
|
||||||
di ディ
|
di ディ
|
||||||
fa ファ
|
fa ファ
|
||||||
fi フィ
|
fi フィ
|
||||||
@@ -136,6 +135,7 @@ fe フェ
|
|||||||
fo フォ
|
fo フォ
|
||||||
va ヴァ
|
va ヴァ
|
||||||
vi ヴィ
|
vi ヴィ
|
||||||
|
vu ヴ
|
||||||
ve ヴェ
|
ve ヴェ
|
||||||
vo ヴォ
|
vo ヴォ
|
||||||
. 。
|
. 。
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def read(path):
|
|||||||
for field in fields[1:]:
|
for field in fields[1:]:
|
||||||
alias = fold(field)
|
alias = fold(field)
|
||||||
if (not alias or len(alias) > MAXRUNES or hascontrol(alias)
|
if (not alias or len(alias) > MAXRUNES or hascontrol(alias)
|
||||||
or alias != alias.strip()):
|
or alias != alias.strip() or alias.startswith(";")):
|
||||||
raise ValueError(f"{path}:{lineno}: bad alias")
|
raise ValueError(f"{path}:{lineno}: bad alias")
|
||||||
entries.append((result, alias))
|
entries.append((result, alias))
|
||||||
return entries
|
return entries
|
||||||
|
|||||||
28294
map/pinyin.dict
28294
map/pinyin.dict
File diff suppressed because it is too large
Load Diff
@@ -1,2 +1,63 @@
|
|||||||
#!/bin/rc
|
#!/bin/bash
|
||||||
tcs -sf ujis | awk '$1 !~ /;;/ {gsub("(^\/|\/$)", "", $2); gsub(" ", " "); gsub("\/", " ", $2);} {print}'
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
transcode()
|
||||||
|
{
|
||||||
|
local file
|
||||||
|
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
iconv -f EUC-JP -t UTF-8
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
for file in "$@"; do
|
||||||
|
iconv -f EUC-JP -t UTF-8 "$file"
|
||||||
|
printf '\n'
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
transcode "$@" | awk '
|
||||||
|
function fail(s) {
|
||||||
|
print "skk2ktrans: " FILENAME ":" FNR ": " s >"/dev/stderr"
|
||||||
|
bad = 1
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
function add(k, v, id) {
|
||||||
|
sub(/;.*/, "", v)
|
||||||
|
if(v == "" || v ~ /^[([#]/ || v ~ /[[:space:]]/)
|
||||||
|
return
|
||||||
|
id = k SUBSEP v
|
||||||
|
if(seen[id])
|
||||||
|
return
|
||||||
|
seen[id] = 1
|
||||||
|
if(!(k in row))
|
||||||
|
order[++nkey] = k
|
||||||
|
else
|
||||||
|
row[k] = row[k] " "
|
||||||
|
row[k] = row[k] v
|
||||||
|
}
|
||||||
|
/^;;/ || /^[[:space:]]*$/ {
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if(!match($0, /[[:space:]]+/))
|
||||||
|
fail("missing candidate list")
|
||||||
|
key = substr($0, 1, RSTART-1)
|
||||||
|
field = substr($0, RSTART+RLENGTH)
|
||||||
|
if(key == "" || key ~ /^;/ || field !~ /^\/.*\/$/)
|
||||||
|
fail("invalid row")
|
||||||
|
if(field ~ /\\/)
|
||||||
|
fail("escaped candidates are unsupported")
|
||||||
|
if(field ~ /\/[([#]/)
|
||||||
|
fail("expression candidates are unsupported")
|
||||||
|
n = split(substr(field, 2, length(field)-2), value, "/")
|
||||||
|
for(i = 1; i <= n; i++)
|
||||||
|
add(key, value[i])
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
if(bad)
|
||||||
|
exit 1
|
||||||
|
for(i = 1; i <= nkey; i++)
|
||||||
|
print order[i] "\t" row[order[i]]
|
||||||
|
}
|
||||||
|
'
|
||||||
|
|||||||
73
map/verifymap.py
Executable file
73
map/verifymap.py
Executable file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Verify the text maps and dictionaries consumed by strans."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def verify(path):
|
||||||
|
errors = []
|
||||||
|
try:
|
||||||
|
data = path.read_bytes()
|
||||||
|
except OSError as error:
|
||||||
|
return [f"{path}: {error.strerror}"]
|
||||||
|
try:
|
||||||
|
text = data.decode("utf-8")
|
||||||
|
except UnicodeDecodeError as error:
|
||||||
|
return [f"{path}: invalid UTF-8: {error}"]
|
||||||
|
|
||||||
|
keys = {}
|
||||||
|
for lineno, line in enumerate(text.split("\n"), 1):
|
||||||
|
where = f"{path}:{lineno}"
|
||||||
|
if "\r" in line:
|
||||||
|
errors.append(f"{where}: carriage return is not canonical")
|
||||||
|
line = line.replace("\r", "")
|
||||||
|
if not line or line.startswith(";"):
|
||||||
|
continue
|
||||||
|
if "\0" in line:
|
||||||
|
errors.append(f"{where}: embedded NUL")
|
||||||
|
if line.count("\t") != 1:
|
||||||
|
errors.append(f"{where}: expected exactly one tab")
|
||||||
|
continue
|
||||||
|
key, value = line.split("\t")
|
||||||
|
if not key:
|
||||||
|
errors.append(f"{where}: empty key")
|
||||||
|
if len(key) > 64:
|
||||||
|
errors.append(f"{where}: key has {len(key)} runes; maximum is 64")
|
||||||
|
if key in keys:
|
||||||
|
errors.append(f"{where}: duplicate key; first defined on line {keys[key]}")
|
||||||
|
else:
|
||||||
|
keys[key] = lineno
|
||||||
|
if not value:
|
||||||
|
errors.append(f"{where}: empty value")
|
||||||
|
continue
|
||||||
|
if value != " ".join(value.split(" ")):
|
||||||
|
errors.append(f"{where}: noncanonical candidate spacing")
|
||||||
|
values = value.split(" ")
|
||||||
|
if path.name.endswith(".map"):
|
||||||
|
if len(value) > 64:
|
||||||
|
errors.append(f"{where}: value exceeds 64 runes")
|
||||||
|
else:
|
||||||
|
for candidate in values:
|
||||||
|
if len(candidate) > 64:
|
||||||
|
errors.append(f"{where}: candidate exceeds 64 runes")
|
||||||
|
if len(values) != len(set(values)):
|
||||||
|
errors.append(f"{where}: duplicate candidate")
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("file", nargs="+", type=Path)
|
||||||
|
args = parser.parse_args()
|
||||||
|
errors = []
|
||||||
|
for path in args.file:
|
||||||
|
errors.extend(verify(path))
|
||||||
|
for error in errors:
|
||||||
|
print(error, file=sys.stderr)
|
||||||
|
return bool(errors)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
83100
map/wubi.dict
83100
map/wubi.dict
File diff suppressed because it is too large
Load Diff
33
popup_layout.c
Normal file
33
popup_layout.c
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
|
|
||||||
|
int
|
||||||
|
popupcells(Rune *r, int n)
|
||||||
|
{
|
||||||
|
int i, w;
|
||||||
|
|
||||||
|
w = 0;
|
||||||
|
for(i = 0; i < n; i++)
|
||||||
|
if(r[i] != 0xfe0e && r[i] != 0xfe0f)
|
||||||
|
w++;
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
popupposition(Caret *caret, int pointerx, int pointery, int screenw,
|
||||||
|
int screenh, int w, int h, int *x, int *y)
|
||||||
|
{
|
||||||
|
vlong px, py, xmax, ymax;
|
||||||
|
|
||||||
|
if(caret->valid){
|
||||||
|
px = caret->x;
|
||||||
|
py = (vlong)caret->y + max(caret->h, 0);
|
||||||
|
}else{
|
||||||
|
px = (vlong)pointerx + 10;
|
||||||
|
py = (vlong)pointery + 10;
|
||||||
|
}
|
||||||
|
xmax = max((vlong)screenw - w, 0);
|
||||||
|
ymax = max((vlong)screenh - h, 0);
|
||||||
|
*x = max(0, min(px, xmax));
|
||||||
|
*y = max(0, min(py, ymax));
|
||||||
|
}
|
||||||
45
srv.c
45
srv.c
@@ -1,9 +1,19 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
static char adir[40];
|
#include <sys/stat.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
|
static char adir[256];
|
||||||
static Channel *clientc;
|
static Channel *clientc;
|
||||||
|
|
||||||
|
typedef struct Client Client;
|
||||||
|
struct Client
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
uvlong owner;
|
||||||
|
};
|
||||||
|
|
||||||
int
|
int
|
||||||
srvreadkey(int fd, Keyreq *kr)
|
srvreadkey(int fd, Keyreq *kr)
|
||||||
{
|
{
|
||||||
@@ -14,15 +24,18 @@ srvreadkey(int fd, Keyreq *kr)
|
|||||||
if(ipcreadn(fd, req, sizeof req) < 0)
|
if(ipcreadn(fd, req, sizeof req) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
ipcunpackreq(req, &want, &mod, &ks);
|
ipcunpackreq(req, &want, &mod, &ks);
|
||||||
|
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
|
||||||
kr->ks = ks;
|
kr->ks = ks;
|
||||||
kr->mod = mod;
|
kr->mod = mod;
|
||||||
kr->want = want;
|
kr->want = want;
|
||||||
|
memset(&kr->caret, 0, sizeof kr->caret);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
clientthread(void *arg)
|
clientthread(void *arg)
|
||||||
{
|
{
|
||||||
|
Client *client;
|
||||||
Channel *reply;
|
Channel *reply;
|
||||||
int fd;
|
int fd;
|
||||||
Keyreq kr;
|
Keyreq kr;
|
||||||
@@ -32,10 +45,12 @@ clientthread(void *arg)
|
|||||||
int n, ncommit, npreedit;
|
int n, ncommit, npreedit;
|
||||||
uchar token;
|
uchar token;
|
||||||
|
|
||||||
fd = (int)(uintptr)arg;
|
client = arg;
|
||||||
|
fd = client->fd;
|
||||||
threadsetname("client %d", fd);
|
threadsetname("client %d", fd);
|
||||||
reply = chancreate(sizeof(Keyres), 0);
|
reply = chancreate(sizeof(Keyres), 0);
|
||||||
kr.reply = reply;
|
kr.reply = reply;
|
||||||
|
kr.owner = client->owner;
|
||||||
while(srvreadkey(fd, &kr) == 0){
|
while(srvreadkey(fd, &kr) == 0){
|
||||||
chansend(keyc, &kr);
|
chansend(keyc, &kr);
|
||||||
chanrecv(reply, &res);
|
chanrecv(reply, &res);
|
||||||
@@ -46,25 +61,37 @@ clientthread(void *arg)
|
|||||||
if(n < 0 || ipcsend(fd, out, n) < 0)
|
if(n < 0 || ipcsend(fd, out, n) < 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
kr.op = Keyrelease;
|
||||||
|
kr.want = 0;
|
||||||
|
chansend(keyc, &kr);
|
||||||
|
chanrecv(reply, &res);
|
||||||
chanfree(reply);
|
chanfree(reply);
|
||||||
close(fd);
|
close(fd);
|
||||||
|
free(client);
|
||||||
chanrecv(clientc, &token);
|
chanrecv(clientc, &token);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
srvinit(void)
|
srvinit(void)
|
||||||
{
|
{
|
||||||
char addr[64];
|
char *addr, path[sizeof(((struct sockaddr_un*)0)->sun_path)];
|
||||||
|
|
||||||
snprint(addr, sizeof(addr), "unix!" IPCPATH, getuid());
|
if(ipcpath(path, sizeof path) < 0)
|
||||||
remove(addr + 5);
|
die("IPC path is too long");
|
||||||
|
addr = smprint("unix!%s", path);
|
||||||
|
if(addr == nil)
|
||||||
|
die("out of memory");
|
||||||
if(announce(addr, adir) < 0)
|
if(announce(addr, adir) < 0)
|
||||||
die("announce: %r");
|
die("IPC endpoint is already in use: %r");
|
||||||
|
free(addr);
|
||||||
|
if(chmod(path, 0600) < 0)
|
||||||
|
die("can't protect IPC endpoint: %s", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
srvthread(void*)
|
srvthread(void*)
|
||||||
{
|
{
|
||||||
|
Client *client;
|
||||||
char ldir[40];
|
char ldir[40];
|
||||||
int fd;
|
int fd;
|
||||||
uchar token;
|
uchar token;
|
||||||
@@ -81,9 +108,13 @@ srvthread(void*)
|
|||||||
close(fd);
|
close(fd);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(proccreate(clientthread, (void*)(uintptr)fd, 8192) < 0){
|
client = emalloc(sizeof *client);
|
||||||
|
client->fd = fd;
|
||||||
|
client->owner = ownernew();
|
||||||
|
if(proccreate(clientthread, client, 8192) < 0){
|
||||||
chanrecv(clientc, &token);
|
chanrecv(clientc, &token);
|
||||||
close(fd);
|
close(fd);
|
||||||
|
free(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
str.c
5
str.c
@@ -7,6 +7,8 @@ sinit(Str *s, char *src, int n)
|
|||||||
int len;
|
int len;
|
||||||
|
|
||||||
s->n = 0;
|
s->n = 0;
|
||||||
|
if(n < 0 || (n > 0 && src == nil))
|
||||||
|
return;
|
||||||
while(n > 0 && s->n < Maxrunes){
|
while(n > 0 && s->n < Maxrunes){
|
||||||
if(!fullrune(src, n))
|
if(!fullrune(src, n))
|
||||||
break;
|
break;
|
||||||
@@ -28,8 +30,9 @@ sclear(Str *s)
|
|||||||
void
|
void
|
||||||
sputr(Str *s, Rune r)
|
sputr(Str *s, Rune r)
|
||||||
{
|
{
|
||||||
|
/* Str is a capped value; appends at capacity leave it unchanged. */
|
||||||
if(s->n >= Maxrunes)
|
if(s->n >= Maxrunes)
|
||||||
die("sputr overflow");
|
return;
|
||||||
s->r[s->n++] = r;
|
s->r[s->n++] = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
301
strans.c
301
strans.c
@@ -4,7 +4,14 @@
|
|||||||
static Im im;
|
static Im im;
|
||||||
static int popup = 0;
|
static int popup = 0;
|
||||||
static int visible = 0;
|
static int visible = 0;
|
||||||
|
static Lock ownerlock;
|
||||||
|
static uvlong ownerctr;
|
||||||
|
static uvlong activeowner;
|
||||||
|
static Caret caret;
|
||||||
static void dictqmap(Im*);
|
static void dictqmap(Im*);
|
||||||
|
static void dictqjp(Im*);
|
||||||
|
static void backjp(Im*);
|
||||||
|
static int maplookup(Trie*, Str*, Str*);
|
||||||
|
|
||||||
typedef struct Search Search;
|
typedef struct Search Search;
|
||||||
struct Search
|
struct Search
|
||||||
@@ -15,17 +22,24 @@ struct Search
|
|||||||
};
|
};
|
||||||
static Search search;
|
static Search search;
|
||||||
|
|
||||||
static void
|
uvlong
|
||||||
backmap(Im *im)
|
ownernew(void)
|
||||||
{
|
{
|
||||||
spopr(&im->pre);
|
uvlong n;
|
||||||
|
|
||||||
|
lock(&ownerlock);
|
||||||
|
n = ++ownerctr;
|
||||||
|
if(n == 0)
|
||||||
|
n = ++ownerctr;
|
||||||
|
unlock(&ownerlock);
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
Lang langs[] = {
|
Lang langs[] = {
|
||||||
{LangEN, "english", nil, transmap, backmap, dictqmap, nil, nil},
|
{LangEN, nil, nil, nil, nil, nil, nil, nil},
|
||||||
{LangJP, "hira", "kanji", transmap, backmap, dictqmap, nil, nil},
|
{LangJP, "hira", "kanji", transmap, backjp, dictqjp, nil, nil},
|
||||||
{LangJPK, "kata", "kanji", transmap, backmap, dictqmap, nil, nil},
|
{LangJPK, "kata", nil, transmap, backjp, dictqjp, nil, nil},
|
||||||
{LangKO, "hangul", nil, transko, backko, dictqmap, nil, nil},
|
{LangKO, nil, nil, transko, backko, nil, nil, nil},
|
||||||
{LangEMOJI, nil, "emoji", nil, nil, nil, nil, nil},
|
{LangEMOJI, nil, "emoji", nil, nil, nil, nil, nil},
|
||||||
{LangVI, "telex", nil, transvi, backvi, dictqmap, nil, nil},
|
{LangVI, "telex", nil, transvi, backvi, dictqmap, nil, nil},
|
||||||
};
|
};
|
||||||
@@ -38,6 +52,48 @@ clearkouho(void)
|
|||||||
im.sel = -1;
|
im.sel = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
isjp(Im *p)
|
||||||
|
{
|
||||||
|
return p->l->lang == LangJP || p->l->lang == LangJPK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Japanese keeps completed kana in pre and an ambiguous romaji syllable in
|
||||||
|
* raw. A mapped raw suffix is part of the reading even while it remains
|
||||||
|
* pending (notably n, which may still grow into nya).
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
jpreading(Im *p, Str *s)
|
||||||
|
{
|
||||||
|
Str mapped;
|
||||||
|
|
||||||
|
*s = p->pre;
|
||||||
|
if(p->raw.n == 0)
|
||||||
|
return 1;
|
||||||
|
if(mapget(p->l->map, &p->raw, &mapped)){
|
||||||
|
sappend(s, &mapped);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
sappend(s, &p->raw);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
haspre(Im *p)
|
||||||
|
{
|
||||||
|
return p->pre.n != 0 || (isjp(p) && p->raw.n != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
impre(Str *s)
|
||||||
|
{
|
||||||
|
if(isjp(&im))
|
||||||
|
jpreading(&im, s);
|
||||||
|
else
|
||||||
|
*s = im.pre;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setkouho(Dictres *res)
|
setkouho(Dictres *res)
|
||||||
{
|
{
|
||||||
@@ -55,20 +111,25 @@ show(void)
|
|||||||
{
|
{
|
||||||
static Drawcmd old;
|
static Drawcmd old;
|
||||||
Drawcmd dc;
|
Drawcmd dc;
|
||||||
Str *pre;
|
Str pre;
|
||||||
int i, first, n;
|
int i, first, n;
|
||||||
|
|
||||||
sclear(&dc.pre);
|
sclear(&dc.pre);
|
||||||
pre = search.on ? &search.text : &im.pre;
|
if(search.on)
|
||||||
|
pre = search.text;
|
||||||
|
else
|
||||||
|
impre(&pre);
|
||||||
if(popup){
|
if(popup){
|
||||||
if(search.on || im.l->map == nil || !mapget(im.l->map, pre, &dc.pre))
|
if(search.on || isjp(&im) || im.l->map == nil ||
|
||||||
dc.pre = *pre;
|
!mapget(im.l->map, &pre, &dc.pre))
|
||||||
|
dc.pre = pre;
|
||||||
}
|
}
|
||||||
first = im.sel >= Maxdisp ? im.sel - Maxdisp + 1 : 0;
|
first = im.sel >= Maxdisp ? im.sel - Maxdisp + 1 : 0;
|
||||||
n = im.nkouho - first;
|
n = im.nkouho - first;
|
||||||
if(n > Maxdisp) n = Maxdisp;
|
if(n > Maxdisp) n = Maxdisp;
|
||||||
dc.nkouho = n;
|
dc.nkouho = n;
|
||||||
dc.sel = im.sel >= 0 ? im.sel - first : -1;
|
dc.sel = im.sel >= 0 ? im.sel - first : -1;
|
||||||
|
dc.caret = caret;
|
||||||
for(i = 0; i < n; i++)
|
for(i = 0; i < n; i++)
|
||||||
dc.kouho[i] = im.kouho[first + i];
|
dc.kouho[i] = im.kouho[first + i];
|
||||||
if(dc.pre.n == 0 && n == 0 && !visible)
|
if(dc.pre.n == 0 && n == 0 && !visible)
|
||||||
@@ -85,6 +146,10 @@ reset(void)
|
|||||||
{
|
{
|
||||||
sclear(&im.pre);
|
sclear(&im.pre);
|
||||||
sclear(&im.raw);
|
sclear(&im.raw);
|
||||||
|
search.on = 0;
|
||||||
|
sclear(&search.raw);
|
||||||
|
sclear(&search.text);
|
||||||
|
memset(&caret, 0, sizeof caret);
|
||||||
clearkouho();
|
clearkouho();
|
||||||
show();
|
show();
|
||||||
}
|
}
|
||||||
@@ -92,11 +157,17 @@ reset(void)
|
|||||||
void
|
void
|
||||||
dictsend(Im *im, Str *key)
|
dictsend(Im *im, Str *key)
|
||||||
{
|
{
|
||||||
Dictreq req;
|
Dictreq old, req;
|
||||||
|
|
||||||
req.key = *key;
|
req.key = *key;
|
||||||
req.lang = im->l->lang;
|
req.lang = im->l->lang;
|
||||||
req.pre = im->pre;
|
if(isjp(im))
|
||||||
|
jpreading(im, &req.pre);
|
||||||
|
else
|
||||||
|
req.pre = im->pre;
|
||||||
|
/* imthread is the sole producer; discard every obsolete queued lookup. */
|
||||||
|
while(channbrecv(dictreqc, &old) > 0)
|
||||||
|
;
|
||||||
channbsend(dictreqc, &req);
|
channbsend(dictreqc, &req);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,11 +178,25 @@ dictqmap(Im *im)
|
|||||||
|
|
||||||
clearkouho();
|
clearkouho();
|
||||||
show();
|
show();
|
||||||
|
if(im->l->map == nil)
|
||||||
|
return;
|
||||||
if(!mapget(im->l->map, &im->pre, &dict))
|
if(!mapget(im->l->map, &im->pre, &dict))
|
||||||
return;
|
return;
|
||||||
dictsend(im, &dict);
|
dictsend(im, &dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dictqjp(Im *im)
|
||||||
|
{
|
||||||
|
Str reading;
|
||||||
|
|
||||||
|
clearkouho();
|
||||||
|
show();
|
||||||
|
if(im->l->dict == nil || !jpreading(im, &reading) || reading.n == 0)
|
||||||
|
return;
|
||||||
|
dictsend(im, &reading);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
setlang(int c)
|
setlang(int c)
|
||||||
{
|
{
|
||||||
@@ -129,7 +214,10 @@ commit(Str *com)
|
|||||||
{
|
{
|
||||||
Str val;
|
Str val;
|
||||||
|
|
||||||
if(im.l->map != nil && mapget(im.l->map, &im.pre, &val))
|
if(isjp(&im)){
|
||||||
|
jpreading(&im, &val);
|
||||||
|
sappend(com, &val);
|
||||||
|
}else if(im.l->map != nil && mapget(im.l->map, &im.pre, &val))
|
||||||
sappend(com, &val);
|
sappend(com, &val);
|
||||||
else
|
else
|
||||||
sappend(com, &im.pre);
|
sappend(com, &im.pre);
|
||||||
@@ -137,13 +225,81 @@ commit(Str *com)
|
|||||||
sclear(&im.raw);
|
sclear(&im.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
backjp(Im *im)
|
||||||
|
{
|
||||||
|
if(im->raw.n != 0)
|
||||||
|
spopr(&im->raw);
|
||||||
|
else
|
||||||
|
spopr(&im->pre);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
issmalltsu(Str *s)
|
||||||
|
{
|
||||||
|
return s->n == 1 && (s->r[0] == L'っ' || s->r[0] == L'ッ');
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
keeptail(Str *s)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 1; i < s->n; i++)
|
||||||
|
s->r[i-1] = s->r[i];
|
||||||
|
if(s->n > 0)
|
||||||
|
s->n--;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
dotransjp(Rune c, Str *com)
|
||||||
|
{
|
||||||
|
Str key, mapped;
|
||||||
|
|
||||||
|
if(c == '\'' && im.raw.n == 1 && im.raw.r[0] == 'n'){
|
||||||
|
if(mapget(im.l->map, &im.raw, &mapped))
|
||||||
|
sappend(&im.pre, &mapped);
|
||||||
|
sclear(&im.raw);
|
||||||
|
dictqjp(&im);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
key = im.raw;
|
||||||
|
if(key.n < Maxrunes)
|
||||||
|
sputr(&key, c);
|
||||||
|
sclear(&mapped);
|
||||||
|
if(maplookup(im.l->map, &key, &mapped)){
|
||||||
|
im.raw = key;
|
||||||
|
if(issmalltsu(&mapped)){
|
||||||
|
sappend(&im.pre, &mapped);
|
||||||
|
keeptail(&im.raw);
|
||||||
|
}
|
||||||
|
dictqjp(&im);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if(im.raw.n != 0 && mapget(im.l->map, &im.raw, &mapped)){
|
||||||
|
sappend(&im.pre, &mapped);
|
||||||
|
sclear(&im.raw);
|
||||||
|
sclear(&key);
|
||||||
|
sputr(&key, c);
|
||||||
|
if(maplookup(im.l->map, &key, &mapped)){
|
||||||
|
im.raw = key;
|
||||||
|
dictqjp(&im);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commit(com);
|
||||||
|
clearkouho();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
dotrans(Rune c, Str *com)
|
dotrans(Rune c, Str *com)
|
||||||
{
|
{
|
||||||
Emit e;
|
Emit e;
|
||||||
Dictreq req;
|
|
||||||
Str mapped;
|
Str mapped;
|
||||||
|
|
||||||
|
if(isjp(&im))
|
||||||
|
return dotransjp(c, com);
|
||||||
e = im.l->trans(&im, c);
|
e = im.l->trans(&im, c);
|
||||||
if(im.l->lang == LangVI){
|
if(im.l->lang == LangVI){
|
||||||
if(e.s.n > 0 || !e.eat)
|
if(e.s.n > 0 || !e.eat)
|
||||||
@@ -165,10 +321,7 @@ dotrans(Rune c, Str *com)
|
|||||||
sclear(&im.pre);
|
sclear(&im.pre);
|
||||||
sappend(&im.pre, &e.next);
|
sappend(&im.pre, &e.next);
|
||||||
if(e.eat && e.dict.n > 0){
|
if(e.eat && e.dict.n > 0){
|
||||||
req.key = e.dict;
|
dictsend(&im, &e.dict);
|
||||||
req.lang = im.l->lang;
|
|
||||||
req.pre = im.pre;
|
|
||||||
channbsend(dictreqc, &req);
|
|
||||||
}
|
}
|
||||||
return e.eat;
|
return e.eat;
|
||||||
}
|
}
|
||||||
@@ -238,6 +391,16 @@ ctrlkey(Rune c)
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Rune
|
||||||
|
kokey(Rune c, u32int mod)
|
||||||
|
{
|
||||||
|
if(c >= 'A' && c <= 'Z')
|
||||||
|
c += 'a' - 'A';
|
||||||
|
if((mod & Mshift) && c >= 'a' && c <= 'z')
|
||||||
|
c -= 'a' - 'A';
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
ismodkey(u32int ks)
|
ismodkey(u32int ks)
|
||||||
{
|
{
|
||||||
@@ -347,7 +510,7 @@ endsearch(void)
|
|||||||
static void
|
static void
|
||||||
startsearch(Str *com)
|
startsearch(Str *com)
|
||||||
{
|
{
|
||||||
if(im.pre.n > 0)
|
if(haspre(&im))
|
||||||
commit(com);
|
commit(com);
|
||||||
sclear(&im.pre);
|
sclear(&im.pre);
|
||||||
sclear(&im.raw);
|
sclear(&im.raw);
|
||||||
@@ -484,7 +647,7 @@ keystroke(u32int ks, u32int mod, Str *com)
|
|||||||
off = 0;
|
off = 0;
|
||||||
if(im.sel >= Maxdisp)
|
if(im.sel >= Maxdisp)
|
||||||
off = im.sel - Maxdisp + 1;
|
off = im.sel - Maxdisp + 1;
|
||||||
if(n >= 0 && n < Maxdisp && off + n < im.nkouho){
|
if(n >= 0 && n < Maxdisp && off + n < im.nkouho && mod == 0){
|
||||||
sappend(com, &im.kouho[off + n]);
|
sappend(com, &im.kouho[off + n]);
|
||||||
reset();
|
reset();
|
||||||
return 1;
|
return 1;
|
||||||
@@ -495,7 +658,7 @@ keystroke(u32int ks, u32int mod, Str *com)
|
|||||||
reset();
|
reset();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(im.pre.n > 0){
|
if(haspre(&im)){
|
||||||
commit(com);
|
commit(com);
|
||||||
reset();
|
reset();
|
||||||
return 1;
|
return 1;
|
||||||
@@ -503,18 +666,19 @@ keystroke(u32int ks, u32int mod, Str *com)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if(ks == Kback){
|
if(ks == Kback){
|
||||||
if(im.pre.n == 0)
|
if(!haspre(&im))
|
||||||
return 0;
|
return 0;
|
||||||
im.l->back(&im);
|
im.l->back(&im);
|
||||||
if(im.pre.n == 0){
|
if(!haspre(&im)){
|
||||||
reset();
|
reset();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
im.l->dictq(&im);
|
if(im.l->dictq != nil)
|
||||||
|
im.l->dictq(&im);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(ks == Kesc){
|
if(ks == Kesc){
|
||||||
if(im.pre.n == 0)
|
if(!haspre(&im))
|
||||||
return 0;
|
return 0;
|
||||||
reset();
|
reset();
|
||||||
return 1;
|
return 1;
|
||||||
@@ -535,9 +699,14 @@ keystroke(u32int ks, u32int mod, Str *com)
|
|||||||
show();
|
show();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
reset();
|
if(getlang(c) != nil){
|
||||||
if(setlang(c))
|
if(isjp(&im) && haspre(&im))
|
||||||
|
commit(com);
|
||||||
|
reset();
|
||||||
|
setlang(c);
|
||||||
return 1;
|
return 1;
|
||||||
|
}
|
||||||
|
reset();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if(ks == '0' && im.nkouho > 0){
|
if(ks == '0' && im.nkouho > 0){
|
||||||
@@ -546,13 +715,25 @@ keystroke(u32int ks, u32int mod, Str *com)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(ks > 0x7f || ks == ' '){
|
if(ks > 0x7f || ks == ' '){
|
||||||
if(im.pre.n == 0)
|
if(!haspre(&im))
|
||||||
return 0;
|
return 0;
|
||||||
commit(com);
|
commit(com);
|
||||||
sputr(com, ks);
|
|
||||||
reset();
|
reset();
|
||||||
|
if(com->n >= Maxrunes)
|
||||||
|
return 0;
|
||||||
|
sputr(com, ks);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if(im.l->trans == nil)
|
||||||
|
return 0;
|
||||||
|
if(im.pre.n >= Maxrunes ||
|
||||||
|
(isjp(&im) && im.pre.n + im.raw.n >= Maxrunes)){
|
||||||
|
commit(com);
|
||||||
|
reset();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(im.l->lang == LangKO)
|
||||||
|
ks = kokey(ks, mod);
|
||||||
n = dotrans(ks, com);
|
n = dotrans(ks, com);
|
||||||
show();
|
show();
|
||||||
return n;
|
return n;
|
||||||
@@ -564,9 +745,17 @@ init(void)
|
|||||||
memset(&im, 0, sizeof(im));
|
memset(&im, 0, sizeof(im));
|
||||||
im.l = getlang(LangEN);
|
im.l = getlang(LangEN);
|
||||||
memset(&search, 0, sizeof search);
|
memset(&search, 0, sizeof search);
|
||||||
|
memset(&caret, 0, sizeof caret);
|
||||||
|
activeowner = 0;
|
||||||
visible = 0;
|
visible = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
meaningful(Keyreq *kr)
|
||||||
|
{
|
||||||
|
return kr->ks != 0 && !ismodkey(kr->ks);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
imhandlekey(Keyreq *kr)
|
imhandlekey(Keyreq *kr)
|
||||||
{
|
{
|
||||||
@@ -574,17 +763,54 @@ imhandlekey(Keyreq *kr)
|
|||||||
|
|
||||||
sclear(&res.commit);
|
sclear(&res.commit);
|
||||||
sclear(&res.preedit);
|
sclear(&res.preedit);
|
||||||
res.eaten = keystroke(kr->ks, kr->mod, &res.commit);
|
res.eaten = 1;
|
||||||
if(kr->want)
|
switch(kr->op){
|
||||||
res.preedit = search.on ? search.text : im.pre;
|
case Keyrelease:
|
||||||
|
if(kr->owner == activeowner){
|
||||||
|
reset();
|
||||||
|
activeowner = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Keyreset:
|
||||||
|
if(kr->owner == activeowner)
|
||||||
|
reset();
|
||||||
|
break;
|
||||||
|
case Keycaret:
|
||||||
|
if(kr->owner == activeowner){
|
||||||
|
caret = kr->caret;
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
res.eaten = 0;
|
||||||
|
if(meaningful(kr)){
|
||||||
|
if(kr->owner != activeowner){
|
||||||
|
reset();
|
||||||
|
activeowner = kr->owner;
|
||||||
|
caret = kr->caret;
|
||||||
|
}else if(kr->caret.valid)
|
||||||
|
caret = kr->caret;
|
||||||
|
res.eaten = keystroke(kr->ks, kr->mod, &res.commit);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(kr->want && kr->owner == activeowner){
|
||||||
|
if(search.on)
|
||||||
|
res.preedit = search.text;
|
||||||
|
else
|
||||||
|
impre(&res.preedit);
|
||||||
|
}
|
||||||
chansend(kr->reply, &res);
|
chansend(kr->reply, &res);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dictresult(Dictres *res)
|
dictresult(Dictres *res)
|
||||||
{
|
{
|
||||||
|
Str pre;
|
||||||
|
|
||||||
|
impre(&pre);
|
||||||
if(search.on || res->lang != im.l->lang ||
|
if(search.on || res->lang != im.l->lang ||
|
||||||
scmp(&res->key, &im.pre) != 0)
|
scmp(&res->key, &pre) != 0)
|
||||||
return;
|
return;
|
||||||
setkouho(res);
|
setkouho(res);
|
||||||
show();
|
show();
|
||||||
@@ -634,13 +860,16 @@ mapget(Trie *t, Str *key, Str *out)
|
|||||||
void
|
void
|
||||||
mapinit(char *dir)
|
mapinit(char *dir)
|
||||||
{
|
{
|
||||||
char path[1024];
|
char *path;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for(i = 0; i < nelem(langs); i++){
|
for(i = 0; i < nelem(langs); i++){
|
||||||
if(langs[i].mapname == nil)
|
if(langs[i].mapname == nil)
|
||||||
continue;
|
continue;
|
||||||
snprint(path, sizeof(path), "%s/%s.map", dir, langs[i].mapname);
|
path = smprint("%s/%s.map", dir, langs[i].mapname);
|
||||||
|
if(path == nil)
|
||||||
|
die("out of memory");
|
||||||
langs[i].map = trieopen(path);
|
langs[i].map = trieopen(path);
|
||||||
|
free(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ LIBS = -lthread -lbio
|
|||||||
|
|
||||||
PROG = unit_test
|
PROG = unit_test
|
||||||
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
||||||
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c
|
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c wayland_test.c \
|
||||||
|
popup_test.c
|
||||||
TESTOBJ = $(TESTSRC:.c=.o)
|
TESTOBJ = $(TESTSRC:.c=.o)
|
||||||
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c
|
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c wayland_state.c \
|
||||||
|
popup_layout.c
|
||||||
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
||||||
OBJS = $(TESTOBJ) $(PARENTOBJ)
|
OBJS = $(TESTOBJ) $(PARENTOBJ)
|
||||||
|
|
||||||
|
|||||||
3
tests/data/skk/expected.tsv
Normal file
3
tests/data/skk/expected.tsv
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
かんじ 漢字 幹事 感じ
|
||||||
|
えがお 笑顔
|
||||||
|
きごう 記号 普通
|
||||||
|
6
tests/data/skk/source.skk.utf8
Normal file
6
tests/data/skk/source.skk.utf8
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
;; converter fixture; the test transcodes this file to EUC-JP
|
||||||
|
かんじ /漢字;common/幹事/
|
||||||
|
えがお /笑顔;face/
|
||||||
|
かんじ /感じ/漢字;duplicate/
|
||||||
|
きごう /記号;symbol/普通/
|
||||||
|
むこう /候補 with space/
|
||||||
@@ -3,4 +3,3 @@ ab beta
|
|||||||
한 값
|
한 값
|
||||||
duplicate first
|
duplicate first
|
||||||
duplicate second
|
duplicate second
|
||||||
tabs one two
|
|
||||||
|
|||||||
@@ -89,3 +89,28 @@ dictionary_misses_clear_result(struct ct *t)
|
|||||||
hmapfree(lang->dict);
|
hmapfree(lang->dict);
|
||||||
lang->dict = saved;
|
lang->dict = saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
dictionary_emoji_identity(struct ct *t)
|
||||||
|
{
|
||||||
|
Dictreq req;
|
||||||
|
Dictres res;
|
||||||
|
Hmap *saved;
|
||||||
|
Lang *lang;
|
||||||
|
Str key;
|
||||||
|
|
||||||
|
lang = getlang(LangEMOJI);
|
||||||
|
saved = lang->dict;
|
||||||
|
lang->dict = hmapalloc(2);
|
||||||
|
key = mkstr("é");
|
||||||
|
hmapset(&lang->dict, &key, "é", strlen("é"));
|
||||||
|
memset(&req, 0, sizeof req);
|
||||||
|
req.key = key;
|
||||||
|
req.pre = key;
|
||||||
|
req.lang = LangEMOJI;
|
||||||
|
dictlookup(&req, &res);
|
||||||
|
if(CT_EQ_INT(t, 1, res.nkouho))
|
||||||
|
checkstr(t, "emoji identity candidate", "é", &res.kouho[0]);
|
||||||
|
hmapfree(lang->dict);
|
||||||
|
lang->dict = saved;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
#define STRANS_TEST_ENGINE
|
|
||||||
#include "../strans.c"
|
#include "../strans.c"
|
||||||
#include "test.h"
|
#include "../cutest/cutest.h"
|
||||||
|
|
||||||
|
extern Lang testvi;
|
||||||
|
|
||||||
|
Str mkstr(char*);
|
||||||
|
int checkstr(struct ct*, char*, char*, Str*);
|
||||||
|
Str shownpre(Im*);
|
||||||
|
|
||||||
|
static int typekeys(struct ct*, char*, Str*);
|
||||||
|
|
||||||
void
|
void
|
||||||
vietnamese_state_lifetime(struct ct *t)
|
vietnamese_state_lifetime(struct ct *t)
|
||||||
@@ -73,7 +80,7 @@ engine_backspace_clears_candidates(struct ct *t)
|
|||||||
im.sel = 0;
|
im.sel = 0;
|
||||||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
checkstr(t, "backspace commit", "", &com);
|
checkstr(t, "backspace commit", "", &com);
|
||||||
checkstr(t, "raw preedit after backspace", "n", &im.pre);
|
checkstr(t, "pending romaji after backspace", "n", &im.raw);
|
||||||
shown = shownpre(&im);
|
shown = shownpre(&im);
|
||||||
checkstr(t, "shown preedit after backspace", "ん", &shown);
|
checkstr(t, "shown preedit after backspace", "ん", &shown);
|
||||||
CT_EQ_INT(t, 0, im.nkouho);
|
CT_EQ_INT(t, 0, im.nkouho);
|
||||||
@@ -106,6 +113,159 @@ engine_selects_visible_candidate(struct ct *t)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_candidate_shortcut_modifiers(struct ct *t)
|
||||||
|
{
|
||||||
|
static u32int mods[] = { Mctrl, Malt, Msuper };
|
||||||
|
Str com;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < nelem(mods); i++){
|
||||||
|
init();
|
||||||
|
im.kouho[0] = mkstr("must-not-select");
|
||||||
|
im.nkouho = 1;
|
||||||
|
im.sel = -1;
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, !keystroke('1', mods[i], &com));
|
||||||
|
CT_CHECK(t, scmp(&com, &im.kouho[0]) != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_dictionary_queue_latest_wins(struct ct *t)
|
||||||
|
{
|
||||||
|
Channel *saved;
|
||||||
|
Dictreq old, got;
|
||||||
|
Str key;
|
||||||
|
|
||||||
|
saved = dictreqc;
|
||||||
|
dictreqc = chancreate(sizeof(Dictreq), 1);
|
||||||
|
memset(&old, 0, sizeof old);
|
||||||
|
old.key = mkstr("old-key");
|
||||||
|
old.pre = mkstr("old-preedit");
|
||||||
|
old.lang = LangJP;
|
||||||
|
CT_CHECK(t, channbsend(dictreqc, &old) > 0);
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJP);
|
||||||
|
im.pre = mkstr("new-preedit");
|
||||||
|
key = mkstr("new-key");
|
||||||
|
dictsend(&im, &key);
|
||||||
|
if(CT_CHECK(t, channbrecv(dictreqc, &got) > 0)){
|
||||||
|
checkstr(t, "newest dictionary key", "new-key", &got.key);
|
||||||
|
checkstr(t, "newest dictionary preedit", "new-preedit", &got.pre);
|
||||||
|
CT_EQ_INT(t, LangJP, got.lang);
|
||||||
|
}
|
||||||
|
CT_CHECK(t, channbrecv(dictreqc, &got) <= 0);
|
||||||
|
chanfree(dictreqc);
|
||||||
|
dictreqc = saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Keyres
|
||||||
|
ownerrequestat(uvlong owner, int op, Rune key, u32int mod, Caret *caret)
|
||||||
|
{
|
||||||
|
Channel *reply;
|
||||||
|
Keyreq req;
|
||||||
|
Keyres res;
|
||||||
|
|
||||||
|
reply = chancreate(sizeof(Keyres), 1);
|
||||||
|
memset(&req, 0, sizeof req);
|
||||||
|
req.owner = owner;
|
||||||
|
req.op = op;
|
||||||
|
req.ks = key;
|
||||||
|
req.mod = mod;
|
||||||
|
if(caret != nil)
|
||||||
|
req.caret = *caret;
|
||||||
|
req.want = 1;
|
||||||
|
req.reply = reply;
|
||||||
|
imhandlekey(&req);
|
||||||
|
chanrecv(reply, &res);
|
||||||
|
chanfree(reply);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Keyres
|
||||||
|
ownerrequest(uvlong owner, int op, Rune key, u32int mod)
|
||||||
|
{
|
||||||
|
return ownerrequestat(owner, op, key, mod, nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_active_owner_lifecycle(struct ct *t)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
Str shown;
|
||||||
|
uvlong a, b, c;
|
||||||
|
|
||||||
|
a = ownernew();
|
||||||
|
b = ownernew();
|
||||||
|
c = ownernew();
|
||||||
|
CT_CHECK(t, a != 0 && b != 0 && c != 0);
|
||||||
|
CT_CHECK(t, a != b && b != c && a != c);
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJP);
|
||||||
|
res = ownerrequest(a, Keypress, 'k', 0);
|
||||||
|
CT_CHECK(t, res.eaten);
|
||||||
|
res = ownerrequest(a, Keypress, 'a', 0);
|
||||||
|
checkstr(t, "first owner preedit", "か", &res.preedit);
|
||||||
|
CT_EQ_UINT(t, a, activeowner);
|
||||||
|
|
||||||
|
res = ownerrequest(b, Keypress, 'n', 0);
|
||||||
|
checkstr(t, "takeover resets old preedit", "ん", &res.preedit);
|
||||||
|
CT_EQ_UINT(t, b, activeowner);
|
||||||
|
res = ownerrequest(a, Keyreset, 0, 0);
|
||||||
|
CT_EQ_INT(t, 0, res.preedit.n);
|
||||||
|
shown = shownpre(&im);
|
||||||
|
checkstr(t, "stale reset preserves owner", "ん", &shown);
|
||||||
|
CT_EQ_UINT(t, b, activeowner);
|
||||||
|
|
||||||
|
res = ownerrequest(b, Keypress, 'y', 0);
|
||||||
|
res = ownerrequest(b, Keypress, 'a', 0);
|
||||||
|
checkstr(t, "current owner continues", "にゃ", &res.preedit);
|
||||||
|
ownerrequest(a, Keyrelease, 0, 0);
|
||||||
|
shown = shownpre(&im);
|
||||||
|
checkstr(t, "stale release preserves owner", "にゃ", &shown);
|
||||||
|
CT_EQ_UINT(t, b, activeowner);
|
||||||
|
|
||||||
|
ownerrequest(b, Keyrelease, 0, 0);
|
||||||
|
CT_EQ_UINT(t, 0, activeowner);
|
||||||
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
|
CT_EQ_INT(t, 0, im.raw.n);
|
||||||
|
ownerrequest(c, Keypress, Kmodfirst, 0);
|
||||||
|
CT_EQ_UINT(t, 0, activeowner);
|
||||||
|
res = ownerrequest(c, Keypress, 'k', 0);
|
||||||
|
CT_EQ_UINT(t, c, activeowner);
|
||||||
|
checkstr(t, "next owner first key", "k", &res.preedit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_active_owner_caret(struct ct *t)
|
||||||
|
{
|
||||||
|
Caret a, moved;
|
||||||
|
uvlong one, two;
|
||||||
|
|
||||||
|
one = ownernew();
|
||||||
|
two = ownernew();
|
||||||
|
memset(&a, 0, sizeof a);
|
||||||
|
a.valid = 1;
|
||||||
|
a.x = 10;
|
||||||
|
a.y = 20;
|
||||||
|
a.h = 14;
|
||||||
|
moved = a;
|
||||||
|
moved.x = 80;
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJP);
|
||||||
|
ownerrequestat(one, Keypress, 'k', 0, &a);
|
||||||
|
CT_CHECK(t, caret.valid);
|
||||||
|
CT_EQ_INT(t, 10, caret.x);
|
||||||
|
ownerrequestat(two, Keycaret, 0, 0, &moved);
|
||||||
|
CT_EQ_INT(t, 10, caret.x);
|
||||||
|
ownerrequestat(one, Keycaret, 0, 0, &moved);
|
||||||
|
CT_EQ_INT(t, 80, caret.x);
|
||||||
|
ownerrequest(two, Keypress, 'n', 0);
|
||||||
|
CT_EQ_UINT(t, two, activeowner);
|
||||||
|
CT_CHECK(t, !caret.valid);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
engine_commit_contract(struct ct *t)
|
engine_commit_contract(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -113,10 +273,11 @@ engine_commit_contract(struct ct *t)
|
|||||||
|
|
||||||
init();
|
init();
|
||||||
im.l = getlang(LangJP);
|
im.l = getlang(LangJP);
|
||||||
im.pre = mkstr("ka");
|
|
||||||
im.nkouho = 1;
|
im.nkouho = 1;
|
||||||
im.sel = -1;
|
im.sel = -1;
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
|
if(!typekeys(t, "ka", &com))
|
||||||
|
return;
|
||||||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
checkstr(t, "return commit", "か", &com);
|
checkstr(t, "return commit", "か", &com);
|
||||||
CT_EQ_INT(t, 0, im.pre.n);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
@@ -126,12 +287,13 @@ engine_commit_contract(struct ct *t)
|
|||||||
|
|
||||||
init();
|
init();
|
||||||
im.l = getlang(LangJP);
|
im.l = getlang(LangJP);
|
||||||
im.pre = mkstr("ka");
|
sclear(&com);
|
||||||
|
if(!typekeys(t, "ka", &com))
|
||||||
|
return;
|
||||||
im.kouho[0] = mkstr("c1");
|
im.kouho[0] = mkstr("c1");
|
||||||
im.kouho[1] = mkstr("c2");
|
im.kouho[1] = mkstr("c2");
|
||||||
im.nkouho = 2;
|
im.nkouho = 2;
|
||||||
im.sel = 1;
|
im.sel = 1;
|
||||||
sclear(&com);
|
|
||||||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
checkstr(t, "selected candidate", "c2", &com);
|
checkstr(t, "selected candidate", "c2", &com);
|
||||||
CT_EQ_INT(t, 0, im.pre.n);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
@@ -140,10 +302,11 @@ engine_commit_contract(struct ct *t)
|
|||||||
|
|
||||||
init();
|
init();
|
||||||
im.l = getlang(LangJP);
|
im.l = getlang(LangJP);
|
||||||
im.pre = mkstr("ka");
|
sclear(&com);
|
||||||
|
if(!typekeys(t, "ka", &com))
|
||||||
|
return;
|
||||||
im.nkouho = 1;
|
im.nkouho = 1;
|
||||||
im.sel = -1;
|
im.sel = -1;
|
||||||
sclear(&com);
|
|
||||||
CT_CHECK(t, !keystroke('q', 0, &com));
|
CT_CHECK(t, !keystroke('q', 0, &com));
|
||||||
checkstr(t, "uneaten-key commit", "か", &com);
|
checkstr(t, "uneaten-key commit", "か", &com);
|
||||||
CT_EQ_INT(t, 0, im.pre.n);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
@@ -153,8 +316,9 @@ engine_commit_contract(struct ct *t)
|
|||||||
|
|
||||||
init();
|
init();
|
||||||
im.l = getlang(LangJP);
|
im.l = getlang(LangJP);
|
||||||
im.pre = mkstr("ka");
|
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
|
if(!typekeys(t, "ka", &com))
|
||||||
|
return;
|
||||||
CT_CHECK(t, keystroke(0xf008, 0, &com));
|
CT_CHECK(t, keystroke(0xf008, 0, &com));
|
||||||
CT_EQ_INT(t, 2, com.n);
|
CT_EQ_INT(t, 2, com.n);
|
||||||
CT_EQ_UINT(t, 0x304b, com.r[0]);
|
CT_EQ_UINT(t, 0x304b, com.r[0]);
|
||||||
@@ -183,6 +347,95 @@ engine_telex_history_bound(struct ct *t)
|
|||||||
CT_EQ_INT(t, 0, im.raw.n);
|
CT_EQ_INT(t, 0, im.raw.n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_korean_modifiers_and_backspace(struct ct *t)
|
||||||
|
{
|
||||||
|
static const struct {
|
||||||
|
Rune key;
|
||||||
|
char *plain;
|
||||||
|
char *shifted;
|
||||||
|
} cases[] = {
|
||||||
|
{ 'r', "ㄱ", "ㄲ" }, { 'e', "ㄷ", "ㄸ" },
|
||||||
|
{ 'q', "ㅂ", "ㅃ" }, { 't', "ㅅ", "ㅆ" },
|
||||||
|
{ 'w', "ㅈ", "ㅉ" }, { 'o', "ㅐ", "ㅒ" },
|
||||||
|
{ 'p', "ㅔ", "ㅖ" },
|
||||||
|
};
|
||||||
|
Str com;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < nelem(cases); i++){
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangKO);
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke(cases[i].key, 0, &com));
|
||||||
|
checkstr(t, "plain Korean key", cases[i].plain, &im.pre);
|
||||||
|
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangKO);
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke(cases[i].key, Mshift, &com));
|
||||||
|
checkstr(t, "shifted Korean key", cases[i].shifted, &im.pre);
|
||||||
|
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangKO);
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke(cases[i].key - 'a' + 'A', 0, &com));
|
||||||
|
checkstr(t, "Caps Lock Korean key", cases[i].plain, &im.pre);
|
||||||
|
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangKO);
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke(cases[i].key, Mshift, &com));
|
||||||
|
checkstr(t, "Shift+Caps Korean key", cases[i].shifted, &im.pre);
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangKO);
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "rkr", &com)){
|
||||||
|
checkstr(t, "Korean engine preedit", "각", &im.pre);
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
checkstr(t, "Korean final backspace", "가", &im.pre);
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
checkstr(t, "Korean vowel backspace", "ㄱ", &im.pre);
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
|
CT_CHECK(t, !keystroke(Kback, 0, &com));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_direct_language_modes(struct ct *t)
|
||||||
|
{
|
||||||
|
Str com;
|
||||||
|
|
||||||
|
init();
|
||||||
|
sclear(&com);
|
||||||
|
CT_EQ_INT(t, LangEN, im.l->lang);
|
||||||
|
CT_EQ_PTR(t, nil, im.l->mapname);
|
||||||
|
CT_EQ_PTR(t, nil, im.l->map);
|
||||||
|
CT_EQ_PTR(t, nil, im.l->trans);
|
||||||
|
CT_CHECK(t, !keystroke('a', 0, &com));
|
||||||
|
CT_CHECK(t, !keystroke('Z', Mshift, &com));
|
||||||
|
checkstr(t, "English direct commit", "", &com);
|
||||||
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
|
CT_EQ_INT(t, 0, im.raw.n);
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
CT_CHECK(t, search.on);
|
||||||
|
CT_EQ_INT(t, LangEN, im.l->lang);
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
CT_CHECK(t, !search.on);
|
||||||
|
CT_CHECK(t, keystroke('s', Mctrl, &com));
|
||||||
|
CT_EQ_INT(t, LangKO, im.l->lang);
|
||||||
|
CT_EQ_PTR(t, nil, im.l->mapname);
|
||||||
|
CT_EQ_PTR(t, nil, im.l->map);
|
||||||
|
CT_EQ_PTR(t, nil, im.l->dictq);
|
||||||
|
CT_CHECK(t, keystroke('r', 0, &com));
|
||||||
|
checkstr(t, "Korean without map", "ㄱ", &im.pre);
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
engine_language_switch_state(struct ct *t)
|
engine_language_switch_state(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -309,6 +562,10 @@ emojibegin(Emojifix *f, int showpre)
|
|||||||
setemoji(&l->dict, "smil", "😀 😄");
|
setemoji(&l->dict, "smil", "😀 😄");
|
||||||
setemoji(&l->dict, "smile", "😀 😄");
|
setemoji(&l->dict, "smile", "😀 😄");
|
||||||
setemoji(&l->dict, "웃음", "😀 😄 😂");
|
setemoji(&l->dict, "웃음", "😀 😄 😂");
|
||||||
|
setemoji(&l->dict, "えがお", "😀 😄 😊");
|
||||||
|
setemoji(&l->dict, "エガオ", "😀 😄 😊");
|
||||||
|
setemoji(&l->dict, "heart", "❤️");
|
||||||
|
setemoji(&l->dict, "coffee", "☕️");
|
||||||
setemoji(&l->dict, "^", "¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁽");
|
setemoji(&l->dict, "^", "¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁽");
|
||||||
setemoji(&l->dict, "^0", "⁰");
|
setemoji(&l->dict, "^0", "⁰");
|
||||||
setemoji(&l->dict, "_", "₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₍");
|
setemoji(&l->dict, "_", "₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₍");
|
||||||
@@ -346,6 +603,170 @@ typekeys(struct ct *t, char *keys, Str *com)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_japanese_readings(struct ct *t)
|
||||||
|
{
|
||||||
|
static const struct {
|
||||||
|
char *keys;
|
||||||
|
char *want;
|
||||||
|
} cases[] = {
|
||||||
|
{ "nya", "にゃ" },
|
||||||
|
{ "n'ya", "んや" },
|
||||||
|
{ "nnya", "んにゃ" },
|
||||||
|
{ "kin'youbi", "きんようび" },
|
||||||
|
};
|
||||||
|
Str com, shown;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < nelem(cases); i++){
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJP);
|
||||||
|
sclear(&com);
|
||||||
|
if(!typekeys(t, cases[i].keys, &com))
|
||||||
|
continue;
|
||||||
|
checkstr(t, cases[i].keys, "", &com);
|
||||||
|
shown = shownpre(&im);
|
||||||
|
checkstr(t, cases[i].keys, cases[i].want, &shown);
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "committed reading", cases[i].want, &com);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
newestrequest(Dictreq *req)
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
|
||||||
|
n = 0;
|
||||||
|
while(channbrecv(dictreqc, req) > 0)
|
||||||
|
n++;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_japanese_candidates(struct ct *t)
|
||||||
|
{
|
||||||
|
Dictreq req;
|
||||||
|
Dictres res;
|
||||||
|
Hmap *saved;
|
||||||
|
Lang *jp;
|
||||||
|
Str com, key, shown;
|
||||||
|
|
||||||
|
jp = getlang(LangJP);
|
||||||
|
saved = jp->dict;
|
||||||
|
jp->dict = hmapalloc(8);
|
||||||
|
key = mkstr("かんじ");
|
||||||
|
hmapset(&jp->dict, &key, "漢字 幹事", strlen("漢字 幹事"));
|
||||||
|
init();
|
||||||
|
im.l = jp;
|
||||||
|
sclear(&com);
|
||||||
|
if(!typekeys(t, "kanji", &com))
|
||||||
|
goto cleanup;
|
||||||
|
shown = shownpre(&im);
|
||||||
|
checkstr(t, "complete Japanese reading", "かんじ", &shown);
|
||||||
|
if(!CT_CHECK(t, newestrequest(&req) > 0))
|
||||||
|
goto cleanup;
|
||||||
|
checkstr(t, "dictionary lookup key", "かんじ", &req.key);
|
||||||
|
checkstr(t, "dictionary request identity", "かんじ", &req.pre);
|
||||||
|
dictlookup(&req, &res);
|
||||||
|
dictresult(&res);
|
||||||
|
if(!CT_EQ_INT(t, 2, im.nkouho))
|
||||||
|
goto cleanup;
|
||||||
|
checkstr(t, "candidate one", "漢字", &im.kouho[0]);
|
||||||
|
checkstr(t, "candidate two", "幹事", &im.kouho[1]);
|
||||||
|
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||||||
|
CT_EQ_INT(t, 0, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||||||
|
CT_EQ_INT(t, 1, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "selected Kanji", "幹事", &com);
|
||||||
|
cleanup:
|
||||||
|
newestrequest(&req);
|
||||||
|
hmapfree(jp->dict);
|
||||||
|
jp->dict = saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_japanese_backspace_and_boundaries(struct ct *t)
|
||||||
|
{
|
||||||
|
Str com, shown;
|
||||||
|
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJP);
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "kanji", &com)){
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
shown = shownpre(&im);
|
||||||
|
checkstr(t, "backspace pending i", "かんj", &shown);
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
shown = shownpre(&im);
|
||||||
|
checkstr(t, "backspace pending j", "かん", &shown);
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
shown = shownpre(&im);
|
||||||
|
checkstr(t, "backspace accumulated kana", "か", &shown);
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJP);
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "kanji", &com)){
|
||||||
|
CT_CHECK(t, keystroke('k', Mctrl, &com));
|
||||||
|
checkstr(t, "Hiragana mode boundary", "かんじ", &com);
|
||||||
|
CT_EQ_INT(t, LangJPK, im.l->lang);
|
||||||
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
|
CT_EQ_INT(t, 0, im.raw.n);
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "ka", &com)){
|
||||||
|
CT_CHECK(t, keystroke('n', Mctrl, &com));
|
||||||
|
checkstr(t, "Katakana mode boundary", "カ", &com);
|
||||||
|
CT_EQ_INT(t, LangJP, im.l->lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJP);
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "kanji", &com)){
|
||||||
|
CT_CHECK(t, keystroke(' ', 0, &com));
|
||||||
|
checkstr(t, "real input boundary", "かんじ ", &com);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_katakana_sequences(struct ct *t)
|
||||||
|
{
|
||||||
|
static const struct {
|
||||||
|
char *keys;
|
||||||
|
char *want;
|
||||||
|
} cases[] = {
|
||||||
|
{ "kitte", "キッテ" },
|
||||||
|
{ "katta", "カッタ" },
|
||||||
|
{ "gakkou", "ガッコウ" },
|
||||||
|
{ "matcha", "マッチャ" },
|
||||||
|
{ "xu", "ゥ" },
|
||||||
|
{ "vu", "ヴ" },
|
||||||
|
};
|
||||||
|
Dictreq req;
|
||||||
|
Str com, shown;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
newestrequest(&req);
|
||||||
|
for(i = 0; i < nelem(cases); i++){
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJPK);
|
||||||
|
sclear(&com);
|
||||||
|
if(!typekeys(t, cases[i].keys, &com))
|
||||||
|
continue;
|
||||||
|
checkstr(t, cases[i].keys, "", &com);
|
||||||
|
shown = shownpre(&im);
|
||||||
|
checkstr(t, cases[i].keys, cases[i].want, &shown);
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "committed Katakana", cases[i].want, &com);
|
||||||
|
}
|
||||||
|
CT_EQ_INT(t, 0, newestrequest(&req));
|
||||||
|
CT_EQ_PTR(t, nil, getlang(LangJPK)->dictname);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
engine_emoji_single_candidate(struct ct *t)
|
engine_emoji_single_candidate(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -439,6 +860,53 @@ engine_emoji_queries(struct ct *t)
|
|||||||
emojiend(&f);
|
emojiend(&f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_emoji_japanese_and_multirune(struct ct *t)
|
||||||
|
{
|
||||||
|
Emojifix f;
|
||||||
|
Str com;
|
||||||
|
|
||||||
|
emojibegin(&f, 0);
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJP);
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
if(typekeys(t, "egao", &com)){
|
||||||
|
checkstr(t, "localized Hiragana query", "えがお", &search.text);
|
||||||
|
CT_EQ_INT(t, 3, im.nkouho);
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJPK);
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
if(typekeys(t, "egao", &com)){
|
||||||
|
checkstr(t, "localized Katakana query", "エガオ", &search.text);
|
||||||
|
CT_EQ_INT(t, 3, im.nkouho);
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangEN);
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
if(typekeys(t, "heart", &com)){
|
||||||
|
CT_EQ_INT(t, 1, im.nkouho);
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "variation-selector emoji", "❤️", &com);
|
||||||
|
}
|
||||||
|
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
|
if(typekeys(t, "coffee", &com)){
|
||||||
|
CT_EQ_INT(t, 1, im.nkouho);
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "multi-codepoint emoji", "☕️", &com);
|
||||||
|
}
|
||||||
|
emojiend(&f);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
engine_emoji_digit_aliases(struct ct *t)
|
engine_emoji_digit_aliases(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -556,7 +1024,8 @@ engine_emoji_preedit_languages(struct ct *t)
|
|||||||
sclear(&com);
|
sclear(&com);
|
||||||
if(typekeys(t, "ka", &com)){
|
if(typekeys(t, "ka", &com)){
|
||||||
CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com));
|
CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com));
|
||||||
checkstr(t, "modifier preserves Japanese", "ka", &im.pre);
|
shown = shownpre(&im);
|
||||||
|
checkstr(t, "modifier preserves Japanese", "か", &shown);
|
||||||
CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com));
|
CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com));
|
||||||
checkstr(t, "committed Japanese preedit", "か", &com);
|
checkstr(t, "committed Japanese preedit", "か", &com);
|
||||||
CT_CHECK(t, search.on);
|
CT_CHECK(t, search.on);
|
||||||
@@ -701,3 +1170,146 @@ engine_emoji_dictionary_identity(struct ct *t)
|
|||||||
checkstr(t, "matching response accepted", "right", &im.kouho[0]);
|
checkstr(t, "matching response accepted", "right", &im.kouho[0]);
|
||||||
emojiend(&f);
|
emojiend(&f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static u32int
|
||||||
|
stressrand(u32int *state)
|
||||||
|
{
|
||||||
|
*state = *state * 1664525U + 1013904223U;
|
||||||
|
return *state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
stressstr(Str *s)
|
||||||
|
{
|
||||||
|
return s->n >= 0 && s->n <= Maxrunes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_randomized_stress(struct ct *t)
|
||||||
|
{
|
||||||
|
static Rune text[] = {
|
||||||
|
'a', 'e', 'g', 'i', 'j', 'k', 'm', 'n', 'o', 'r', 's', 't',
|
||||||
|
'u', 'w', 'y', '1', '2', '9', '\'', ' ', L'日',
|
||||||
|
};
|
||||||
|
static Rune modes[] = { 't', 'n', 'k', 's', 'v' };
|
||||||
|
Emojifix f;
|
||||||
|
Lang *vi;
|
||||||
|
Trie *vimap;
|
||||||
|
Keyres res;
|
||||||
|
Caret pos;
|
||||||
|
u32int rnd, mod;
|
||||||
|
uvlong owners[3], owner;
|
||||||
|
int i, j, op;
|
||||||
|
|
||||||
|
vi = getlang(LangVI);
|
||||||
|
if(!CT_CHECK(t, vi != nil))
|
||||||
|
return;
|
||||||
|
vimap = vi->map;
|
||||||
|
vi->map = testvi.map;
|
||||||
|
emojibegin(&f, 0);
|
||||||
|
owners[0] = ownernew();
|
||||||
|
owners[1] = ownernew();
|
||||||
|
owners[2] = ownernew();
|
||||||
|
rnd = 0x5eed1234U;
|
||||||
|
for(i = 0; i < 16000; i++){
|
||||||
|
rnd = stressrand(&rnd);
|
||||||
|
owner = owners[(rnd >> 8) % nelem(owners)];
|
||||||
|
op = rnd % 28;
|
||||||
|
mod = (rnd >> 16) & Mmask;
|
||||||
|
memset(&pos, 0, sizeof pos);
|
||||||
|
pos.valid = (rnd >> 25) & 1;
|
||||||
|
pos.x = (rnd >> 3) & 0x3ff;
|
||||||
|
pos.y = (rnd >> 13) & 0x3ff;
|
||||||
|
pos.h = 1 + ((rnd >> 18) & 0x1f);
|
||||||
|
if(op < 13)
|
||||||
|
res = ownerrequestat(owner, Keypress,
|
||||||
|
text[(rnd >> 5) % nelem(text)], mod, &pos);
|
||||||
|
else if(op == 13)
|
||||||
|
res = ownerrequestat(owner, Keypress, Kback, 0, &pos);
|
||||||
|
else if(op == 14)
|
||||||
|
res = ownerrequestat(owner, Keypress, Kret, 0, &pos);
|
||||||
|
else if(op == 15)
|
||||||
|
res = ownerrequestat(owner, Keypress, Kesc, 0, &pos);
|
||||||
|
else if(op == 16)
|
||||||
|
res = ownerrequestat(owner, Keypress, Kdown, 0, &pos);
|
||||||
|
else if(op == 17)
|
||||||
|
res = ownerrequestat(owner, Keypress, Kup, 0, &pos);
|
||||||
|
else if(op == 18)
|
||||||
|
res = ownerrequestat(owner, Keypress, Ktab,
|
||||||
|
(rnd & 0x1000) ? Mshift : 0, &pos);
|
||||||
|
else if(op == 19)
|
||||||
|
res = ownerrequestat(owner, Keypress,
|
||||||
|
modes[(rnd >> 12) % nelem(modes)], Mctrl, &pos);
|
||||||
|
else if(op == 20)
|
||||||
|
res = ownerrequestat(owner, Keypress, 'e', Mctrl, &pos);
|
||||||
|
else if(op == 21)
|
||||||
|
res = ownerrequest(owner, Keyreset, 0, 0);
|
||||||
|
else if(op == 22)
|
||||||
|
res = ownerrequest(owner, Keyrelease, 0, 0);
|
||||||
|
else if(op == 23)
|
||||||
|
res = ownerrequestat(owner, Keycaret, 0, 0, &pos);
|
||||||
|
else if(op == 24)
|
||||||
|
res = ownerrequest(owner, Keypress,
|
||||||
|
Kmodfirst + ((rnd >> 20) & 7), 0);
|
||||||
|
else if(op == 25)
|
||||||
|
res = ownerrequestat(owner, Keypress, 'p', Mctrl, &pos);
|
||||||
|
else if(op == 26)
|
||||||
|
res = ownerrequestat(owner, Keypress, 0xf008, 0, &pos);
|
||||||
|
else
|
||||||
|
res = ownerrequestat(owner, Keypress,
|
||||||
|
'1' + ((rnd >> 11) % 9), 0, &pos);
|
||||||
|
|
||||||
|
if(!stressstr(&res.commit) || !stressstr(&res.preedit) ||
|
||||||
|
!stressstr(&im.pre) || !stressstr(&im.raw) ||
|
||||||
|
!stressstr(&search.raw) || !stressstr(&search.text) ||
|
||||||
|
im.l == nil || im.nkouho < 0 || im.nkouho > Maxkouho ||
|
||||||
|
im.sel < -1 || im.sel >= Maxkouho ||
|
||||||
|
(im.nkouho > 0 && im.sel >= im.nkouho)){
|
||||||
|
CT_ERRORF(t, "invalid engine state after stress operation %d", i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for(j = 0; j < im.nkouho; j++)
|
||||||
|
if(!stressstr(&im.kouho[j])){
|
||||||
|
CT_ERRORF(t, "invalid candidate after stress operation %d", i);
|
||||||
|
i = 16000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(activeowner != 0 && activeowner != owners[0] &&
|
||||||
|
activeowner != owners[1] && activeowner != owners[2]){
|
||||||
|
CT_ERRORF(t, "unknown active owner after stress operation %d", i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emojiend(&f);
|
||||||
|
vi->map = vimap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_full_boundary_passthrough(struct ct *t)
|
||||||
|
{
|
||||||
|
Str com;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJP);
|
||||||
|
for(i = 0; i < Maxrunes; i++)
|
||||||
|
im.pre.r[i] = L'あ';
|
||||||
|
im.pre.n = Maxrunes;
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, !keystroke(' ', 0, &com));
|
||||||
|
CT_EQ_INT(t, Maxrunes, com.n);
|
||||||
|
for(i = 0; i < Maxrunes; i++)
|
||||||
|
CT_EQ_UINT(t, L'あ', com.r[i]);
|
||||||
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
|
CT_EQ_INT(t, 0, im.raw.n);
|
||||||
|
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJP);
|
||||||
|
for(i = 0; i < Maxrunes-1; i++)
|
||||||
|
im.pre.r[i] = L'あ';
|
||||||
|
im.pre.n = Maxrunes-1;
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke(' ', 0, &com));
|
||||||
|
CT_EQ_INT(t, Maxrunes, com.n);
|
||||||
|
CT_EQ_UINT(t, ' ', com.r[Maxrunes-1]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -76,3 +76,37 @@ hmap_long_utf8_keys(struct ct *t)
|
|||||||
cleanup:
|
cleanup:
|
||||||
hmapfree(h);
|
hmapfree(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hmap_binary_keys_and_invalid_lengths(struct ct *t)
|
||||||
|
{
|
||||||
|
Hmap *h;
|
||||||
|
Hnode *n;
|
||||||
|
Str key, other;
|
||||||
|
|
||||||
|
h = hmapalloc(1);
|
||||||
|
CT_CHECK(t, h != nil);
|
||||||
|
key.n = 3;
|
||||||
|
key.r[0] = 'a';
|
||||||
|
key.r[1] = 0;
|
||||||
|
key.r[2] = 'b';
|
||||||
|
other = key;
|
||||||
|
other.r[2] = 'c';
|
||||||
|
hmapset(&h, &key, "one", 3);
|
||||||
|
hmapset(&h, &other, "two", 3);
|
||||||
|
n = hmapget(h, &key);
|
||||||
|
if(CT_CHECK(t, n != nil)){
|
||||||
|
CT_EQ_INT(t, 3, n->klen);
|
||||||
|
CT_EQ_MEM(t, "one", n->val, n->vlen);
|
||||||
|
}
|
||||||
|
n = hmapget(h, &other);
|
||||||
|
if(CT_CHECK(t, n != nil))
|
||||||
|
CT_EQ_MEM(t, "two", n->val, n->vlen);
|
||||||
|
hmapset(&h, &key, "bad", -1);
|
||||||
|
hmapset(&h, &key, nil, 1);
|
||||||
|
n = hmapget(h, &key);
|
||||||
|
if(CT_CHECK(t, n != nil))
|
||||||
|
CT_EQ_MEM(t, "one", n->val, n->vlen);
|
||||||
|
CT_EQ_PTR(t, nil, hmapalloc(0));
|
||||||
|
hmapfree(h);
|
||||||
|
}
|
||||||
|
|||||||
202
tests/ipc_test.c
202
tests/ipc_test.c
@@ -1,3 +1,8 @@
|
|||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -12,4 +17,201 @@ ipc_masks_modifiers(struct ct *t)
|
|||||||
CT_EQ_INT(t, 1, want);
|
CT_EQ_INT(t, 1, want);
|
||||||
CT_EQ_UINT(t, Mctrl|Malt, mod);
|
CT_EQ_UINT(t, Mctrl|Malt, mod);
|
||||||
CT_EQ_UINT(t, 0x1f642, key);
|
CT_EQ_UINT(t, 0x1f642, key);
|
||||||
|
CT_CHECK(t, !ipcreqreset(buf));
|
||||||
|
|
||||||
|
ipcpackreset(buf, 1);
|
||||||
|
ipcunpackreq(buf, &want, &mod, &key);
|
||||||
|
CT_EQ_INT(t, 1, want);
|
||||||
|
CT_EQ_UINT(t, 0, mod);
|
||||||
|
CT_EQ_UINT(t, 0, key);
|
||||||
|
CT_CHECK(t, ipcreqreset(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ipc_runtime_path(struct ct *t)
|
||||||
|
{
|
||||||
|
char buf[128], fallback[128], *old, *saved;
|
||||||
|
|
||||||
|
old = getenv("XDG_RUNTIME_DIR");
|
||||||
|
saved = old == nil ? nil : strdup(old);
|
||||||
|
setenv("XDG_RUNTIME_DIR", "/tmp/strans-runtime-test", 1);
|
||||||
|
CT_EQ_INT(t, 0, ipcpath(buf, sizeof buf));
|
||||||
|
CT_EQ_STR(t, "/tmp/strans-runtime-test/strans.sock", buf);
|
||||||
|
setenv("XDG_RUNTIME_DIR", "/tmp/strans-runtime-test/", 1);
|
||||||
|
CT_EQ_INT(t, 0, ipcpath(buf, sizeof buf));
|
||||||
|
CT_EQ_STR(t, "/tmp/strans-runtime-test/strans.sock", buf);
|
||||||
|
setenv("XDG_RUNTIME_DIR", "relative", 1);
|
||||||
|
CT_EQ_INT(t, 0, ipcpath(buf, sizeof buf));
|
||||||
|
snprint(fallback, sizeof fallback, "/tmp/strans.%d", getuid());
|
||||||
|
CT_EQ_STR(t, fallback, buf);
|
||||||
|
CT_EQ_INT(t, -1, ipcpath(buf, 4));
|
||||||
|
CT_EQ_INT(t, -1, ipcpath(nil, sizeof buf));
|
||||||
|
if(saved != nil){
|
||||||
|
setenv("XDG_RUNTIME_DIR", saved, 1);
|
||||||
|
free(saved);
|
||||||
|
}else
|
||||||
|
unsetenv("XDG_RUNTIME_DIR");
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ipc_response_pack_boundaries(struct ct *t)
|
||||||
|
{
|
||||||
|
static const uchar withpre[] = { 1, 1, 0, 'A', 2, 0, 'x', 'y' };
|
||||||
|
static const uchar withoutpre[] = { 1, 1, 0, 'A' };
|
||||||
|
uchar out[Ipcmaxresp+1], field[Ipcfieldmax];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
n = ipcpackresp(out, sizeof out, 7, "A", 1, "xy", 2, 1);
|
||||||
|
CT_EQ_INT(t, sizeof withpre, n);
|
||||||
|
CT_EQ_MEM(t, withpre, out, sizeof withpre);
|
||||||
|
n = ipcpackresp(out, sizeof out, 1, "A", 1, "xy", 2, 0);
|
||||||
|
CT_EQ_INT(t, sizeof withoutpre, n);
|
||||||
|
CT_EQ_MEM(t, withoutpre, out, sizeof withoutpre);
|
||||||
|
n = ipcpackresp(out, sizeof out, 0, nil, 0, nil, 0, 1);
|
||||||
|
CT_EQ_INT(t, Ipcresphdrsz + Ipclensz, n);
|
||||||
|
|
||||||
|
memset(field, 'x', sizeof field);
|
||||||
|
memset(out, 0xa5, sizeof out);
|
||||||
|
n = ipcpackresp(out, Ipcmaxresp, 1,
|
||||||
|
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
||||||
|
CT_EQ_INT(t, Ipcmaxresp, n);
|
||||||
|
CT_EQ_INT(t, 0, out[1]);
|
||||||
|
CT_EQ_INT(t, 1, out[2]);
|
||||||
|
CT_EQ_INT(t, 0, out[3+Ipcfieldmax]);
|
||||||
|
CT_EQ_INT(t, 1, out[4+Ipcfieldmax]);
|
||||||
|
CT_EQ_INT(t, 0xa5, out[Ipcmaxresp]);
|
||||||
|
CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1,
|
||||||
|
(char*)field, sizeof field, (char*)field, sizeof field, 1));
|
||||||
|
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
||||||
|
nil, 1, nil, 0, 0));
|
||||||
|
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
||||||
|
nil, 0, nil, 1, 1));
|
||||||
|
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
||||||
|
(char*)field, Ipcfieldmax+1, nil, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ipc_response_empty_and_preedit(struct ct *t)
|
||||||
|
{
|
||||||
|
uchar first[Ipcmaxresp], second[Ipcmaxresp];
|
||||||
|
char commit[16], preedit[16];
|
||||||
|
Ipcresp resp;
|
||||||
|
int fd[2], nfirst, nsecond;
|
||||||
|
|
||||||
|
nfirst = ipcpackresp(first, sizeof first, 0, nil, 0, nil, 0, 0);
|
||||||
|
nsecond = ipcpackresp(second, sizeof second, 1,
|
||||||
|
"go", 2, "kana", 4, 1);
|
||||||
|
if(!CT_CHECK(t, nfirst > 0 && nsecond > 0))
|
||||||
|
return;
|
||||||
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
||||||
|
CT_ERRORF(t, "socketpair failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(CT_EQ_INT(t, 0, ipcsend(fd[0], first, nfirst)) &&
|
||||||
|
CT_EQ_INT(t, 0, ipcsend(fd[0], second, nsecond)) &&
|
||||||
|
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, sizeof commit,
|
||||||
|
nil, 0, &resp))){
|
||||||
|
CT_EQ_INT(t, 0, resp.eaten);
|
||||||
|
CT_EQ_SIZE(t, 0, resp.ncommit);
|
||||||
|
CT_EQ_SIZE(t, 0, resp.npreedit);
|
||||||
|
CT_EQ_STR(t, "", commit);
|
||||||
|
}
|
||||||
|
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, sizeof commit,
|
||||||
|
preedit, sizeof preedit, &resp))){
|
||||||
|
CT_EQ_INT(t, 1, resp.eaten);
|
||||||
|
CT_EQ_SIZE(t, 2, resp.ncommit);
|
||||||
|
CT_EQ_SIZE(t, 4, resp.npreedit);
|
||||||
|
CT_EQ_STR(t, "go", commit);
|
||||||
|
CT_EQ_STR(t, "kana", preedit);
|
||||||
|
}
|
||||||
|
close(fd[0]);
|
||||||
|
close(fd[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ipc_response_max_and_drain(struct ct *t)
|
||||||
|
{
|
||||||
|
uchar first[Ipcmaxresp], second[Ipcmaxresp], field[Ipcfieldmax];
|
||||||
|
char commit[8], preedit[8];
|
||||||
|
Ipcresp resp;
|
||||||
|
int fd[2], nfirst, nsecond;
|
||||||
|
|
||||||
|
memset(field, 'x', sizeof field);
|
||||||
|
nfirst = ipcpackresp(first, sizeof first, 1,
|
||||||
|
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
||||||
|
nsecond = ipcpackresp(second, sizeof second, 0, "ok", 2, nil, 0, 0);
|
||||||
|
if(!CT_CHECK(t, nfirst == Ipcmaxresp && nsecond > 0))
|
||||||
|
return;
|
||||||
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
||||||
|
CT_ERRORF(t, "socketpair failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(CT_EQ_INT(t, 0, ipcsend(fd[0], first, nfirst)) &&
|
||||||
|
CT_EQ_INT(t, 0, ipcsend(fd[0], second, nsecond)) &&
|
||||||
|
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, sizeof commit,
|
||||||
|
preedit, sizeof preedit, &resp))){
|
||||||
|
CT_EQ_SIZE(t, Ipcfieldmax, resp.ncommit);
|
||||||
|
CT_EQ_SIZE(t, Ipcfieldmax, resp.npreedit);
|
||||||
|
CT_EQ_STR(t, "xxxxxxx", commit);
|
||||||
|
CT_EQ_STR(t, "xxxxxxx", preedit);
|
||||||
|
}
|
||||||
|
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, sizeof commit,
|
||||||
|
nil, 0, &resp)))
|
||||||
|
CT_EQ_STR(t, "ok", commit);
|
||||||
|
close(fd[0]);
|
||||||
|
close(fd[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ipc_response_fragmented_and_truncated(struct ct *t)
|
||||||
|
{
|
||||||
|
uchar frame[Ipcmaxresp];
|
||||||
|
char commit[8], preedit[8];
|
||||||
|
Ipcresp resp;
|
||||||
|
int fd[2], i, n;
|
||||||
|
|
||||||
|
n = ipcpackresp(frame, sizeof frame, 1, "abc", 3, "xy", 2, 1);
|
||||||
|
if(!CT_CHECK(t, n > 0))
|
||||||
|
return;
|
||||||
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
||||||
|
CT_ERRORF(t, "socketpair failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(i = 0; i < n; i++)
|
||||||
|
if(ipcsend(fd[0], frame+i, 1) < 0){
|
||||||
|
CT_ERRORF(t, "fragment send failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(i == n && CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1,
|
||||||
|
commit, sizeof commit, preedit, sizeof preedit, &resp))){
|
||||||
|
CT_EQ_STR(t, "abc", commit);
|
||||||
|
CT_EQ_STR(t, "xy", preedit);
|
||||||
|
}
|
||||||
|
close(fd[0]);
|
||||||
|
close(fd[1]);
|
||||||
|
|
||||||
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
||||||
|
CT_ERRORF(t, "socketpair failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CT_EQ_INT(t, 0, ipcsend(fd[0], frame, n-1));
|
||||||
|
shutdown(fd[0], SHUT_WR);
|
||||||
|
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, sizeof commit,
|
||||||
|
preedit, sizeof preedit, &resp));
|
||||||
|
close(fd[0]);
|
||||||
|
close(fd[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ipc_broken_peer_send(struct ct *t)
|
||||||
|
{
|
||||||
|
int fd[2];
|
||||||
|
|
||||||
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
||||||
|
CT_ERRORF(t, "socketpair failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
close(fd[1]);
|
||||||
|
CT_EQ_INT(t, -1, ipcsend(fd[0], "x", 1));
|
||||||
|
close(fd[0]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,27 @@
|
|||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
|
static Str
|
||||||
|
kotrans(char *keys)
|
||||||
|
{
|
||||||
|
Emit e;
|
||||||
|
Im state;
|
||||||
|
Str out;
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
memset(&state, 0, sizeof state);
|
||||||
|
state.l = getlang(LangKO);
|
||||||
|
sclear(&out);
|
||||||
|
for(p = keys; *p != '\0'; p++){
|
||||||
|
e = transko(&state, (uchar)*p);
|
||||||
|
sappend(&out, &e.s);
|
||||||
|
state.pre = e.next;
|
||||||
|
if(!e.eat)
|
||||||
|
sputr(&out, (uchar)*p);
|
||||||
|
}
|
||||||
|
sappend(&out, &state.pre);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
korean_sequences(struct ct *t)
|
korean_sequences(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -14,39 +36,70 @@ korean_sequences(struct ct *t)
|
|||||||
{ "Rk", "까" },
|
{ "Rk", "까" },
|
||||||
{ "rk1", "가1" },
|
{ "rk1", "가1" },
|
||||||
};
|
};
|
||||||
Emit e;
|
|
||||||
Im state;
|
|
||||||
Str out;
|
Str out;
|
||||||
Rune key;
|
|
||||||
char *p;
|
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for(i = 0; i < nelem(cases); i++){
|
for(i = 0; i < nelem(cases); i++){
|
||||||
memset(&state, 0, sizeof state);
|
out = kotrans(cases[i].keys);
|
||||||
state.l = getlang(LangKO);
|
|
||||||
sclear(&out);
|
|
||||||
for(p = cases[i].keys; *p != '\0'; p++){
|
|
||||||
key = (uchar)*p;
|
|
||||||
e = transko(&state, key);
|
|
||||||
sappend(&out, &e.s);
|
|
||||||
state.pre = e.next;
|
|
||||||
if(!e.eat)
|
|
||||||
sputr(&out, key);
|
|
||||||
}
|
|
||||||
sappend(&out, &state.pre);
|
|
||||||
checkstr(t, cases[i].keys, cases[i].want, &out);
|
checkstr(t, cases[i].keys, cases[i].want, &out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
korean_compound_vowels(struct ct *t)
|
||||||
|
{
|
||||||
|
static const struct { char *keys, *want; } cases[] = {
|
||||||
|
{ "hk", "ㅘ" }, { "ho", "ㅙ" }, { "hl", "ㅚ" },
|
||||||
|
{ "nj", "ㅝ" }, { "np", "ㅞ" }, { "nl", "ㅟ" },
|
||||||
|
{ "ml", "ㅢ" },
|
||||||
|
};
|
||||||
|
Str out;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < nelem(cases); i++){
|
||||||
|
out = kotrans(cases[i].keys);
|
||||||
|
checkstr(t, cases[i].keys, cases[i].want, &out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
korean_compound_finals(struct ct *t)
|
||||||
|
{
|
||||||
|
static const struct { char *keys, *want, *split; } cases[] = {
|
||||||
|
{ "rkrt", "갃", "각사" }, { "rksw", "갅", "간자" },
|
||||||
|
{ "rksg", "갆", "간하" }, { "rkfr", "갉", "갈가" },
|
||||||
|
{ "rkfa", "갊", "갈마" }, { "rkfq", "갋", "갈바" },
|
||||||
|
{ "rkft", "갌", "갈사" }, { "rkfx", "갍", "갈타" },
|
||||||
|
{ "rkfv", "갎", "갈파" }, { "rkfg", "갏", "갈하" },
|
||||||
|
{ "rkqt", "값", "갑사" },
|
||||||
|
};
|
||||||
|
char keys[16];
|
||||||
|
Str out;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < nelem(cases); i++){
|
||||||
|
out = kotrans(cases[i].keys);
|
||||||
|
checkstr(t, cases[i].keys, cases[i].want, &out);
|
||||||
|
snprint(keys, sizeof keys, "%sk", cases[i].keys);
|
||||||
|
out = kotrans(keys);
|
||||||
|
checkstr(t, keys, cases[i].split, &out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
korean_backspace(struct ct *t)
|
korean_backspace(struct ct *t)
|
||||||
{
|
{
|
||||||
static const struct { char *before, *after; } cases[] = {
|
static const struct { char *before, *after; } cases[] = {
|
||||||
{ "ㄱ", "" },
|
{ "ㄱ", "" },
|
||||||
{ "가", "ㄱ" },
|
{ "가", "ㄱ" },
|
||||||
{ "과", "고" },
|
{ "과", "고" }, { "괘", "고" }, { "괴", "고" },
|
||||||
|
{ "궈", "구" }, { "궤", "구" }, { "귀", "구" },
|
||||||
|
{ "긔", "그" },
|
||||||
{ "각", "가" },
|
{ "각", "가" },
|
||||||
{ "갃", "각" },
|
{ "갃", "각" }, { "갅", "간" }, { "갆", "간" },
|
||||||
|
{ "갉", "갈" }, { "갊", "갈" }, { "갋", "갈" },
|
||||||
|
{ "갌", "갈" }, { "갍", "갈" }, { "갎", "갈" },
|
||||||
|
{ "갏", "갈" }, { "값", "갑" },
|
||||||
};
|
};
|
||||||
Im state;
|
Im state;
|
||||||
int i;
|
int i;
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ class MkemojiTest(unittest.TestCase):
|
|||||||
"x\t a\n",
|
"x\t a\n",
|
||||||
"\0\ta\n",
|
"\0\ta\n",
|
||||||
"x\ta\0b\n",
|
"x\ta\0b\n",
|
||||||
|
"x\t;hidden\n",
|
||||||
"x" * 65 + "\ta\n",
|
"x" * 65 + "\ta\n",
|
||||||
"x\t" + "a" * 65 + "\n",
|
"x\t" + "a" * 65 + "\n",
|
||||||
]
|
]
|
||||||
|
|||||||
34
tests/popup_test.c
Normal file
34
tests/popup_test.c
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#include "test.h"
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
popup_layout(struct ct *t)
|
||||||
|
{
|
||||||
|
Rune heart[] = { 0x2764, 0xfe0f };
|
||||||
|
Caret caret;
|
||||||
|
int x, y;
|
||||||
|
|
||||||
|
CT_EQ_INT(t, 1, popupcells(heart, nelem(heart)));
|
||||||
|
memset(&caret, 0, sizeof caret);
|
||||||
|
popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y);
|
||||||
|
CT_EQ_INT(t, 50, x);
|
||||||
|
CT_EQ_INT(t, 60, y);
|
||||||
|
caret.valid = 1;
|
||||||
|
caret.x = 70;
|
||||||
|
caret.y = 80;
|
||||||
|
caret.h = 20;
|
||||||
|
popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y);
|
||||||
|
CT_EQ_INT(t, 70, x);
|
||||||
|
CT_EQ_INT(t, 100, y);
|
||||||
|
caret.x = 790;
|
||||||
|
caret.y = 590;
|
||||||
|
popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y);
|
||||||
|
CT_EQ_INT(t, 700, x);
|
||||||
|
CT_EQ_INT(t, 540, y);
|
||||||
|
caret.x = INT_MIN;
|
||||||
|
caret.y = INT_MAX;
|
||||||
|
caret.h = INT_MAX;
|
||||||
|
popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y);
|
||||||
|
CT_EQ_INT(t, 0, x);
|
||||||
|
CT_EQ_INT(t, 540, y);
|
||||||
|
}
|
||||||
58
tests/skk2ktrans_test.py
Executable file
58
tests/skk2ktrans_test.py
Executable file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
CONVERTER = ROOT / "map" / "skk2ktrans"
|
||||||
|
FIXTURES = Path(__file__).parent / "data" / "skk"
|
||||||
|
|
||||||
|
|
||||||
|
class Skk2KtransTest(unittest.TestCase):
|
||||||
|
def test_euc_jp_conversion_is_stable_and_literal(self):
|
||||||
|
source = (FIXTURES / "source.skk.utf8").read_text(encoding="utf-8")
|
||||||
|
expected = (FIXTURES / "expected.tsv").read_bytes()
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
input_path = Path(directory) / "source.skk"
|
||||||
|
input_path.write_bytes(source.encode("euc_jp"))
|
||||||
|
result = subprocess.run(
|
||||||
|
[CONVERTER, input_path], capture_output=True, check=False)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr.decode())
|
||||||
|
self.assertEqual(result.stdout, expected)
|
||||||
|
|
||||||
|
def test_invalid_euc_jp_is_rejected(self):
|
||||||
|
result = subprocess.run(
|
||||||
|
[CONVERTER], input=b"\xff\xff\n", capture_output=True, check=False)
|
||||||
|
self.assertNotEqual(result.returncode, 0)
|
||||||
|
self.assertTrue(result.stderr)
|
||||||
|
|
||||||
|
def test_escaped_candidate_is_rejected(self):
|
||||||
|
source = b"key /escaped\\/slash/\n"
|
||||||
|
result = subprocess.run(
|
||||||
|
[CONVERTER], input=source, capture_output=True, check=False)
|
||||||
|
self.assertNotEqual(result.returncode, 0)
|
||||||
|
self.assertIn(b"escaped candidates are unsupported", result.stderr)
|
||||||
|
|
||||||
|
def test_expression_candidate_is_rejected(self):
|
||||||
|
source = b'key /(concat "a/b")/literal/\n'
|
||||||
|
result = subprocess.run(
|
||||||
|
[CONVERTER], input=source, capture_output=True, check=False)
|
||||||
|
self.assertNotEqual(result.returncode, 0)
|
||||||
|
self.assertIn(b"expression candidates are unsupported", result.stderr)
|
||||||
|
|
||||||
|
def test_files_have_distinct_row_boundaries(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
first = Path(directory) / "first.skk"
|
||||||
|
second = Path(directory) / "second.skk"
|
||||||
|
first.write_bytes(b"a /A/")
|
||||||
|
second.write_bytes(b"b /B/\n")
|
||||||
|
result = subprocess.run(
|
||||||
|
[CONVERTER, first, second], capture_output=True, check=False)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr.decode())
|
||||||
|
self.assertEqual(result.stdout, b"a\tA\nb\tB\n")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -82,3 +82,21 @@ str_utf8_capacity(struct ct *t)
|
|||||||
CT_EQ_STR(t, "😀", buf);
|
CT_EQ_STR(t, "😀", buf);
|
||||||
CT_EQ_INT(t, 'Z', buf[5]);
|
CT_EQ_INT(t, 'Z', buf[5]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
str_invalid_and_full_appends(struct ct *t)
|
||||||
|
{
|
||||||
|
Str s;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
sinit(&s, nil, 1);
|
||||||
|
CT_EQ_INT(t, 0, s.n);
|
||||||
|
sinit(&s, "x", -1);
|
||||||
|
CT_EQ_INT(t, 0, s.n);
|
||||||
|
for(i = 0; i < Maxrunes; i++)
|
||||||
|
s.r[i] = 'a';
|
||||||
|
s.n = Maxrunes;
|
||||||
|
sputr(&s, 'z');
|
||||||
|
CT_EQ_INT(t, Maxrunes, s.n);
|
||||||
|
CT_EQ_INT(t, 'a', s.r[Maxrunes-1]);
|
||||||
|
}
|
||||||
|
|||||||
37
tests/test.h
37
tests/test.h
@@ -1,12 +1,6 @@
|
|||||||
#ifndef STRANS_TEST_H
|
|
||||||
#define STRANS_TEST_H
|
|
||||||
|
|
||||||
#include "../cutest/cutest.h"
|
#include "../cutest/cutest.h"
|
||||||
|
|
||||||
#ifndef STRANS_TEST_ENGINE
|
|
||||||
#include "../dat.h"
|
#include "../dat.h"
|
||||||
#include "../fn.h"
|
#include "../fn.h"
|
||||||
#endif
|
|
||||||
|
|
||||||
extern Lang testvi;
|
extern Lang testvi;
|
||||||
|
|
||||||
@@ -17,12 +11,21 @@ Str shownpre(Im*);
|
|||||||
void str_init_utf8(struct ct*);
|
void str_init_utf8(struct ct*);
|
||||||
void str_edit_and_alias(struct ct*);
|
void str_edit_and_alias(struct ct*);
|
||||||
void str_utf8_capacity(struct ct*);
|
void str_utf8_capacity(struct ct*);
|
||||||
|
void str_invalid_and_full_appends(struct ct*);
|
||||||
void hmap_set_replace_and_grow(struct ct*);
|
void hmap_set_replace_and_grow(struct ct*);
|
||||||
void hmap_long_utf8_keys(struct ct*);
|
void hmap_long_utf8_keys(struct ct*);
|
||||||
|
void hmap_binary_keys_and_invalid_lengths(struct ct*);
|
||||||
void trie_exact_prefix_and_duplicate(struct ct*);
|
void trie_exact_prefix_and_duplicate(struct ct*);
|
||||||
|
void trie_optional_outputs_and_invalid_lengths(struct ct*);
|
||||||
|
void wayland_press_release_state(struct ct*);
|
||||||
|
void wayland_repeat_state(struct ct*);
|
||||||
|
void wayland_repeat_cancellation(struct ct*);
|
||||||
|
void popup_layout(struct ct*);
|
||||||
void production_maps_load(struct ct*);
|
void production_maps_load(struct ct*);
|
||||||
void transmap_states(struct ct*);
|
void transmap_states(struct ct*);
|
||||||
void korean_sequences(struct ct*);
|
void korean_sequences(struct ct*);
|
||||||
|
void korean_compound_vowels(struct ct*);
|
||||||
|
void korean_compound_finals(struct ct*);
|
||||||
void korean_backspace(struct ct*);
|
void korean_backspace(struct ct*);
|
||||||
void vietnamese_transitions(struct ct*);
|
void vietnamese_transitions(struct ct*);
|
||||||
void vietnamese_backspace(struct ct*);
|
void vietnamese_backspace(struct ct*);
|
||||||
@@ -30,18 +33,36 @@ void vietnamese_state_lifetime(struct ct*);
|
|||||||
void engine_clears_candidates(struct ct*);
|
void engine_clears_candidates(struct ct*);
|
||||||
void engine_backspace_clears_candidates(struct ct*);
|
void engine_backspace_clears_candidates(struct ct*);
|
||||||
void engine_selects_visible_candidate(struct ct*);
|
void engine_selects_visible_candidate(struct ct*);
|
||||||
|
void engine_candidate_shortcut_modifiers(struct ct*);
|
||||||
|
void engine_dictionary_queue_latest_wins(struct ct*);
|
||||||
|
void engine_active_owner_lifecycle(struct ct*);
|
||||||
|
void engine_active_owner_caret(struct ct*);
|
||||||
void engine_commit_contract(struct ct*);
|
void engine_commit_contract(struct ct*);
|
||||||
void engine_language_switch_state(struct ct*);
|
void engine_language_switch_state(struct ct*);
|
||||||
void engine_telex_history_bound(struct ct*);
|
void engine_telex_history_bound(struct ct*);
|
||||||
|
void engine_korean_modifiers_and_backspace(struct ct*);
|
||||||
|
void engine_direct_language_modes(struct ct*);
|
||||||
|
void engine_japanese_readings(struct ct*);
|
||||||
|
void engine_japanese_candidates(struct ct*);
|
||||||
|
void engine_japanese_backspace_and_boundaries(struct ct*);
|
||||||
|
void engine_katakana_sequences(struct ct*);
|
||||||
void engine_emoji_single_candidate(struct ct*);
|
void engine_emoji_single_candidate(struct ct*);
|
||||||
void engine_emoji_queries(struct ct*);
|
void engine_emoji_queries(struct ct*);
|
||||||
|
void engine_emoji_japanese_and_multirune(struct ct*);
|
||||||
void engine_emoji_digit_aliases(struct ct*);
|
void engine_emoji_digit_aliases(struct ct*);
|
||||||
void engine_emoji_navigation(struct ct*);
|
void engine_emoji_navigation(struct ct*);
|
||||||
void engine_emoji_preedit_languages(struct ct*);
|
void engine_emoji_preedit_languages(struct ct*);
|
||||||
void engine_emoji_start_and_unknown(struct ct*);
|
void engine_emoji_start_and_unknown(struct ct*);
|
||||||
void engine_emoji_dictionary_identity(struct ct*);
|
void engine_emoji_dictionary_identity(struct ct*);
|
||||||
|
void engine_randomized_stress(struct ct*);
|
||||||
|
void engine_full_boundary_passthrough(struct ct*);
|
||||||
void dictionary_candidates(struct ct*);
|
void dictionary_candidates(struct ct*);
|
||||||
void dictionary_misses_clear_result(struct ct*);
|
void dictionary_misses_clear_result(struct ct*);
|
||||||
|
void dictionary_emoji_identity(struct ct*);
|
||||||
void ipc_masks_modifiers(struct ct*);
|
void ipc_masks_modifiers(struct ct*);
|
||||||
|
void ipc_runtime_path(struct ct*);
|
||||||
#endif
|
void ipc_response_pack_boundaries(struct ct*);
|
||||||
|
void ipc_response_empty_and_preedit(struct ct*);
|
||||||
|
void ipc_response_max_and_drain(struct ct*);
|
||||||
|
void ipc_response_fragmented_and_truncated(struct ct*);
|
||||||
|
void ipc_broken_peer_send(struct ct*);
|
||||||
|
|||||||
@@ -24,8 +24,18 @@ checkstr(struct ct *t, char *where, char *want, Str *got)
|
|||||||
Str
|
Str
|
||||||
shownpre(Im *state)
|
shownpre(Im *state)
|
||||||
{
|
{
|
||||||
Str shown;
|
Str mapped, shown;
|
||||||
|
|
||||||
|
if(state->l->lang == LangJP || state->l->lang == LangJPK){
|
||||||
|
shown = state->pre;
|
||||||
|
if(state->raw.n == 0)
|
||||||
|
return shown;
|
||||||
|
if(mapget(state->l->map, &state->raw, &mapped))
|
||||||
|
sappend(&shown, &mapped);
|
||||||
|
else
|
||||||
|
sappend(&shown, &state->raw);
|
||||||
|
return shown;
|
||||||
|
}
|
||||||
if(state->l->map != nil && mapget(state->l->map, &state->pre, &shown))
|
if(state->l->map != nil && mapget(state->l->map, &state->pre, &shown))
|
||||||
return shown;
|
return shown;
|
||||||
return state->pre;
|
return state->pre;
|
||||||
|
|||||||
@@ -24,16 +24,34 @@ trie_exact_prefix_and_duplicate(struct ct *t)
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
CT_EQ_INT(t, 6, n);
|
CT_EQ_INT(t, 6, n);
|
||||||
CT_EQ_MEM(t, "second", v, n);
|
CT_EQ_MEM(t, "second", v, n);
|
||||||
v = trieget(trie, "tabs", 4, &n);
|
|
||||||
if(!CT_CHECK(t, v != nil))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_INT(t, 7, n);
|
|
||||||
CT_EQ_MEM(t, "one\ttwo", v, n);
|
|
||||||
CT_EQ_PTR(t, nil, trieget(trie, "missing", 7, &n));
|
CT_EQ_PTR(t, nil, trieget(trie, "missing", 7, &n));
|
||||||
cleanup:
|
cleanup:
|
||||||
trieclose(trie);
|
trieclose(trie);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
trie_optional_outputs_and_invalid_lengths(struct ct *t)
|
||||||
|
{
|
||||||
|
char *v;
|
||||||
|
Trie *trie;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
trie = trieopen("data/trie.map");
|
||||||
|
CT_CHECK(t, trieget(trie, "a", 1, nil) != nil);
|
||||||
|
v = nil;
|
||||||
|
CT_CHECK(t, trielookup(trie, "a", 1, &v, nil));
|
||||||
|
CT_CHECK(t, v != nil);
|
||||||
|
n = -1;
|
||||||
|
CT_CHECK(t, trielookup(trie, "a", 1, nil, &n));
|
||||||
|
CT_EQ_INT(t, 5, n);
|
||||||
|
CT_CHECK(t, trielookup(trie, "dupli", 5, nil, nil));
|
||||||
|
CT_EQ_PTR(t, nil, trieget(trie, nil, 1, &n));
|
||||||
|
CT_EQ_PTR(t, nil, trieget(trie, "a", -1, &n));
|
||||||
|
CT_CHECK(t, !trielookup(trie, nil, 1, &v, &n));
|
||||||
|
CT_CHECK(t, !trielookup(trie, "a", -1, &v, &n));
|
||||||
|
trieclose(trie);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
production_maps_load(struct ct *t)
|
production_maps_load(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -76,10 +94,14 @@ transmap_states(struct ct *t)
|
|||||||
};
|
};
|
||||||
Emit e;
|
Emit e;
|
||||||
Im state;
|
Im state;
|
||||||
|
Trie *fixture, *saved;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
fixture = trieopen("data/hira.map");
|
||||||
memset(&state, 0, sizeof state);
|
memset(&state, 0, sizeof state);
|
||||||
state.l = getlang(LangJP);
|
state.l = getlang(LangJP);
|
||||||
|
saved = state.l->map;
|
||||||
|
state.l->map = fixture;
|
||||||
for(i = 0; i < nelem(cases); i++){
|
for(i = 0; i < nelem(cases); i++){
|
||||||
state.pre = mkstr(cases[i].pre);
|
state.pre = mkstr(cases[i].pre);
|
||||||
e = transmap(&state, cases[i].key);
|
e = transmap(&state, cases[i].key);
|
||||||
@@ -90,4 +112,6 @@ transmap_states(struct ct *t)
|
|||||||
checkstr(t, "next", cases[i].next, &e.next);
|
checkstr(t, "next", cases[i].next, &e.next);
|
||||||
checkstr(t, "mapped", cases[i].mapped, &e.dict);
|
checkstr(t, "mapped", cases[i].mapped, &e.dict);
|
||||||
}
|
}
|
||||||
|
trieclose(fixture);
|
||||||
|
state.l->map = saved;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,9 @@ emalloc(ulong n)
|
|||||||
{
|
{
|
||||||
void *p;
|
void *p;
|
||||||
|
|
||||||
p = malloc(n);
|
p = mallocz(n, 1);
|
||||||
if(p == nil)
|
if(p == nil)
|
||||||
die("out of memory");
|
die("out of memory");
|
||||||
memset(p, 0, n);
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,12 +45,14 @@ erealloc(void *p, ulong n)
|
|||||||
static void
|
static void
|
||||||
testmapinit(void)
|
testmapinit(void)
|
||||||
{
|
{
|
||||||
Lang *jp;
|
Lang *jp, *kata;
|
||||||
|
|
||||||
jp = getlang(LangJP);
|
jp = getlang(LangJP);
|
||||||
if(jp == nil)
|
kata = getlang(LangJPK);
|
||||||
|
if(jp == nil || kata == nil)
|
||||||
die("test language is not registered");
|
die("test language is not registered");
|
||||||
jp->map = trieopen("data/hira.map");
|
jp->map = trieopen("../map/hira.map");
|
||||||
|
kata->map = trieopen("../map/kata.map");
|
||||||
memset(&testvi, 0, sizeof testvi);
|
memset(&testvi, 0, sizeof testvi);
|
||||||
testvi.lang = LangVI;
|
testvi.lang = LangVI;
|
||||||
testvi.mapname = "telex";
|
testvi.mapname = "telex";
|
||||||
@@ -64,12 +65,21 @@ static const struct ct_test tests[] = {
|
|||||||
{ "str/init-utf8", str_init_utf8 },
|
{ "str/init-utf8", str_init_utf8 },
|
||||||
{ "str/edit-and-alias", str_edit_and_alias },
|
{ "str/edit-and-alias", str_edit_and_alias },
|
||||||
{ "str/utf8-capacity", str_utf8_capacity },
|
{ "str/utf8-capacity", str_utf8_capacity },
|
||||||
|
{ "str/invalid-full-appends", str_invalid_and_full_appends },
|
||||||
{ "hmap/set-replace-grow", hmap_set_replace_and_grow },
|
{ "hmap/set-replace-grow", hmap_set_replace_and_grow },
|
||||||
{ "hmap/long-utf8-keys", hmap_long_utf8_keys },
|
{ "hmap/long-utf8-keys", hmap_long_utf8_keys },
|
||||||
|
{ "hmap/binary-invalid-lengths", hmap_binary_keys_and_invalid_lengths },
|
||||||
{ "trie/exact-prefix-duplicate", trie_exact_prefix_and_duplicate },
|
{ "trie/exact-prefix-duplicate", trie_exact_prefix_and_duplicate },
|
||||||
|
{ "trie/optional-invalid-lengths", trie_optional_outputs_and_invalid_lengths },
|
||||||
|
{ "wayland/press-release", wayland_press_release_state },
|
||||||
|
{ "wayland/repeat", wayland_repeat_state },
|
||||||
|
{ "wayland/repeat-cancellation", wayland_repeat_cancellation },
|
||||||
|
{ "popup/layout", popup_layout },
|
||||||
{ "map/production-lifecycle", production_maps_load },
|
{ "map/production-lifecycle", production_maps_load },
|
||||||
{ "transmap/states", transmap_states },
|
{ "transmap/states", transmap_states },
|
||||||
{ "hangul/sequences", korean_sequences },
|
{ "hangul/sequences", korean_sequences },
|
||||||
|
{ "hangul/compound-vowels", korean_compound_vowels },
|
||||||
|
{ "hangul/compound-finals", korean_compound_finals },
|
||||||
{ "hangul/backspace", korean_backspace },
|
{ "hangul/backspace", korean_backspace },
|
||||||
{ "telex/transitions", vietnamese_transitions },
|
{ "telex/transitions", vietnamese_transitions },
|
||||||
{ "telex/backspace", vietnamese_backspace },
|
{ "telex/backspace", vietnamese_backspace },
|
||||||
@@ -77,19 +87,39 @@ static const struct ct_test tests[] = {
|
|||||||
{ "engine/clears-candidates", engine_clears_candidates },
|
{ "engine/clears-candidates", engine_clears_candidates },
|
||||||
{ "engine/backspace-clears-candidates", engine_backspace_clears_candidates },
|
{ "engine/backspace-clears-candidates", engine_backspace_clears_candidates },
|
||||||
{ "engine/selects-visible-candidate", engine_selects_visible_candidate },
|
{ "engine/selects-visible-candidate", engine_selects_visible_candidate },
|
||||||
|
{ "engine/candidate-shortcut-modifiers", engine_candidate_shortcut_modifiers },
|
||||||
|
{ "engine/dictionary-queue-latest", engine_dictionary_queue_latest_wins },
|
||||||
|
{ "engine/active-owner-lifecycle", engine_active_owner_lifecycle },
|
||||||
|
{ "engine/active-owner-caret", engine_active_owner_caret },
|
||||||
{ "engine/commit-contract", engine_commit_contract },
|
{ "engine/commit-contract", engine_commit_contract },
|
||||||
{ "engine/language-switch-state", engine_language_switch_state },
|
{ "engine/language-switch-state", engine_language_switch_state },
|
||||||
{ "engine/telex-history-bound", engine_telex_history_bound },
|
{ "engine/telex-history-bound", engine_telex_history_bound },
|
||||||
|
{ "engine/korean-modifiers-backspace", engine_korean_modifiers_and_backspace },
|
||||||
|
{ "engine/direct-language-modes", engine_direct_language_modes },
|
||||||
|
{ "engine/japanese-readings", engine_japanese_readings },
|
||||||
|
{ "engine/japanese-candidates", engine_japanese_candidates },
|
||||||
|
{ "engine/japanese-backspace-boundaries", engine_japanese_backspace_and_boundaries },
|
||||||
|
{ "engine/katakana-sequences", engine_katakana_sequences },
|
||||||
{ "engine/emoji-single-candidate", engine_emoji_single_candidate },
|
{ "engine/emoji-single-candidate", engine_emoji_single_candidate },
|
||||||
{ "engine/emoji-queries", engine_emoji_queries },
|
{ "engine/emoji-queries", engine_emoji_queries },
|
||||||
|
{ "engine/emoji-japanese-multirune", engine_emoji_japanese_and_multirune },
|
||||||
{ "engine/emoji-digit-aliases", engine_emoji_digit_aliases },
|
{ "engine/emoji-digit-aliases", engine_emoji_digit_aliases },
|
||||||
{ "engine/emoji-navigation", engine_emoji_navigation },
|
{ "engine/emoji-navigation", engine_emoji_navigation },
|
||||||
{ "engine/emoji-preedit-languages", engine_emoji_preedit_languages },
|
{ "engine/emoji-preedit-languages", engine_emoji_preedit_languages },
|
||||||
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
|
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
|
||||||
{ "engine/emoji-dictionary-identity", engine_emoji_dictionary_identity },
|
{ "engine/emoji-dictionary-identity", engine_emoji_dictionary_identity },
|
||||||
|
{ "engine/randomized-stress", engine_randomized_stress },
|
||||||
|
{ "engine/full-boundary-passthrough", engine_full_boundary_passthrough },
|
||||||
{ "dict/candidates", dictionary_candidates },
|
{ "dict/candidates", dictionary_candidates },
|
||||||
{ "dict/misses-clear-result", dictionary_misses_clear_result },
|
{ "dict/misses-clear-result", dictionary_misses_clear_result },
|
||||||
|
{ "dict/emoji-identity", dictionary_emoji_identity },
|
||||||
{ "ipc/masks-modifiers", ipc_masks_modifiers },
|
{ "ipc/masks-modifiers", ipc_masks_modifiers },
|
||||||
|
{ "ipc/runtime-path", ipc_runtime_path },
|
||||||
|
{ "ipc/response-pack-boundaries", ipc_response_pack_boundaries },
|
||||||
|
{ "ipc/response-empty-preedit", ipc_response_empty_and_preedit },
|
||||||
|
{ "ipc/response-max-drain", ipc_response_max_and_drain },
|
||||||
|
{ "ipc/response-fragmented-truncated", ipc_response_fragmented_and_truncated },
|
||||||
|
{ "ipc/broken-peer-send", ipc_broken_peer_send },
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
66
tests/wayland_test.c
Normal file
66
tests/wayland_test.c
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
void
|
||||||
|
wayland_press_release_state(struct ct *t)
|
||||||
|
{
|
||||||
|
Wlstate s;
|
||||||
|
|
||||||
|
wlstateinit(&s);
|
||||||
|
CT_CHECK(t, wlstatepress(&s, 30, 1, 0, 0));
|
||||||
|
CT_CHECK(t, !wlstaterelease(&s, 30));
|
||||||
|
CT_CHECK(t, wlstaterelease(&s, 30));
|
||||||
|
CT_CHECK(t, wlstatepress(&s, 31, 0, 0, 0));
|
||||||
|
CT_CHECK(t, wlstaterelease(&s, 31));
|
||||||
|
CT_CHECK(t, !wlstatepress(&s, Wlkeymax, 1, 0, 0));
|
||||||
|
CT_CHECK(t, wlstaterelease(&s, Wlkeymax));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wayland_repeat_state(struct ct *t)
|
||||||
|
{
|
||||||
|
Wlstate s;
|
||||||
|
u32int key;
|
||||||
|
|
||||||
|
wlstateinit(&s);
|
||||||
|
wlstaterepeatinfo(&s, 25, 400);
|
||||||
|
CT_CHECK(t, wlstatepress(&s, 14, 1, 1, 100));
|
||||||
|
CT_EQ_INT(t, 400, wlstatetimeout(&s, 100));
|
||||||
|
CT_CHECK(t, !wlstaterepeat(&s, 499, &key));
|
||||||
|
CT_CHECK(t, wlstaterepeat(&s, 500, &key));
|
||||||
|
CT_EQ_UINT(t, 14, key);
|
||||||
|
CT_EQ_INT(t, 40, wlstatetimeout(&s, 500));
|
||||||
|
CT_CHECK(t, !wlstaterelease(&s, 14));
|
||||||
|
CT_EQ_INT(t, -1, wlstatetimeout(&s, 500));
|
||||||
|
|
||||||
|
CT_CHECK(t, wlstatepress(&s, 15, 1, 0, 600));
|
||||||
|
CT_EQ_INT(t, -1, wlstatetimeout(&s, 600));
|
||||||
|
CT_CHECK(t, !wlstaterelease(&s, 15));
|
||||||
|
CT_CHECK(t, wlstatepress(&s, 16, 0, 1, 600));
|
||||||
|
CT_EQ_INT(t, -1, wlstatetimeout(&s, 600));
|
||||||
|
CT_CHECK(t, wlstaterelease(&s, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wayland_repeat_cancellation(struct ct *t)
|
||||||
|
{
|
||||||
|
Wlstate s;
|
||||||
|
|
||||||
|
wlstateinit(&s);
|
||||||
|
wlstaterepeatinfo(&s, 20, 300);
|
||||||
|
CT_CHECK(t, wlstatepress(&s, 20, 1, 1, 0));
|
||||||
|
CT_CHECK(t, wlstatepress(&s, 19, 0, 0, 10));
|
||||||
|
CT_EQ_INT(t, -1, wlstatetimeout(&s, 10));
|
||||||
|
CT_CHECK(t, !wlstaterelease(&s, 20));
|
||||||
|
CT_CHECK(t, wlstaterelease(&s, 19));
|
||||||
|
CT_CHECK(t, wlstatepress(&s, 20, 1, 1, 0));
|
||||||
|
wlstaterepeatinfo(&s, 30, 200);
|
||||||
|
CT_EQ_INT(t, -1, wlstatetimeout(&s, 0));
|
||||||
|
CT_CHECK(t, !wlstaterelease(&s, 20));
|
||||||
|
CT_CHECK(t, wlstatepress(&s, 21, 1, 1, 0));
|
||||||
|
wlstatecancelrepeat(&s);
|
||||||
|
CT_EQ_INT(t, -1, wlstatetimeout(&s, 0));
|
||||||
|
CT_CHECK(t, !wlstaterelease(&s, 21));
|
||||||
|
CT_CHECK(t, wlstatepress(&s, 22, 1, 1, 0));
|
||||||
|
wlstateclear(&s);
|
||||||
|
CT_CHECK(t, wlstaterelease(&s, 22));
|
||||||
|
}
|
||||||
82
tests/xim_test.c
Normal file
82
tests/xim_test.c
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <xcb-imdkit/encoding.h>
|
||||||
|
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||||
|
|
||||||
|
uint32_t keymaplookup(const uint32_t*, int, uint16_t);
|
||||||
|
char *ximcompound(const char*, size_t, size_t*);
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
ShiftMask = 1<<0,
|
||||||
|
LockMask = 1<<1,
|
||||||
|
Group1 = 1<<13,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
checktext(const char *s)
|
||||||
|
{
|
||||||
|
char *ct, *utf8;
|
||||||
|
size_t clen, len, ulen;
|
||||||
|
|
||||||
|
len = strlen(s);
|
||||||
|
ct = ximcompound(s, len, &clen);
|
||||||
|
assert(ct != NULL);
|
||||||
|
utf8 = xcb_compound_text_to_utf8(ct, clen, &ulen);
|
||||||
|
assert(utf8 != NULL);
|
||||||
|
assert(ulen == len);
|
||||||
|
assert(memcmp(utf8, s, len) == 0);
|
||||||
|
free(utf8);
|
||||||
|
free(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
checkutf8run(void)
|
||||||
|
{
|
||||||
|
static const char s[] = "😀";
|
||||||
|
char *ct;
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
ct = ximcompound(s, sizeof s - 1, &n);
|
||||||
|
assert(ct != NULL);
|
||||||
|
assert(n >= 6);
|
||||||
|
assert(memcmp(ct, "\033%G", 3) == 0);
|
||||||
|
assert(memcmp(ct+n-3, "\033%@", 3) == 0);
|
||||||
|
free(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void)
|
||||||
|
{
|
||||||
|
static const uint32_t letters[] = {
|
||||||
|
XKB_KEY_a, XKB_KEY_A, XKB_KEY_Cyrillic_ef, XKB_KEY_Cyrillic_EF,
|
||||||
|
};
|
||||||
|
static const uint32_t inferred[] = { XKB_KEY_a, XKB_KEY_NoSymbol };
|
||||||
|
static const uint32_t punctuation[] = { XKB_KEY_1, XKB_KEY_exclam };
|
||||||
|
static const uint32_t missing[] = {
|
||||||
|
XKB_KEY_a, XKB_KEY_A, XKB_KEY_NoSymbol, XKB_KEY_NoSymbol,
|
||||||
|
};
|
||||||
|
|
||||||
|
xcb_compound_text_init();
|
||||||
|
|
||||||
|
assert(keymaplookup(letters, 4, 0) == XKB_KEY_a);
|
||||||
|
assert(keymaplookup(letters, 4, ShiftMask) == XKB_KEY_A);
|
||||||
|
assert(keymaplookup(letters, 4, LockMask) == XKB_KEY_A);
|
||||||
|
assert(keymaplookup(letters, 4, LockMask|ShiftMask) == XKB_KEY_a);
|
||||||
|
assert(keymaplookup(letters, 4, Group1) == XKB_KEY_Cyrillic_ef);
|
||||||
|
assert(keymaplookup(letters, 4, Group1|ShiftMask) == XKB_KEY_Cyrillic_EF);
|
||||||
|
assert(keymaplookup(inferred, 2, ShiftMask) == XKB_KEY_A);
|
||||||
|
assert(keymaplookup(punctuation, 2, LockMask) == XKB_KEY_1);
|
||||||
|
assert(keymaplookup(punctuation, 2, LockMask|ShiftMask) == XKB_KEY_exclam);
|
||||||
|
assert(keymaplookup(missing, 4, Group1) == XKB_KEY_a);
|
||||||
|
assert(keymaplookup(NULL, 0, 0) == XKB_KEY_NoSymbol);
|
||||||
|
checktext("한글");
|
||||||
|
checktext("😀");
|
||||||
|
checktext("❤️");
|
||||||
|
checktext("A😀한");
|
||||||
|
checkutf8run();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
24
trie.c
24
trie.c
@@ -80,18 +80,23 @@ trieopen(char *path)
|
|||||||
vlen = strlen(line);
|
vlen = strlen(line);
|
||||||
if(vlen > 0 && line[vlen-1] == '\r')
|
if(vlen > 0 && line[vlen-1] == '\r')
|
||||||
line[--vlen] = '\0';
|
line[--vlen] = '\0';
|
||||||
if(line[0] == '\0'){
|
if(line[0] == '\0' || line[0] == ';'){
|
||||||
free(line);
|
free(line);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tab = strchr(line, '\t');
|
tab = strchr(line, '\t');
|
||||||
if(tab == nil || tab[1] == '\0')
|
if(tab == nil || tab == line || tab[1] == '\0' ||
|
||||||
|
strchr(tab+1, '\t') != nil)
|
||||||
die("malformed map: %s", path);
|
die("malformed map: %s", path);
|
||||||
*tab = '\0';
|
*tab = '\0';
|
||||||
key = line;
|
key = line;
|
||||||
klen = tab - line;
|
klen = tab - line;
|
||||||
|
if(utflen(key) > Maxrunes)
|
||||||
|
die("map key too long: %s", path);
|
||||||
val = tab + 1;
|
val = tab + 1;
|
||||||
vlen = strlen(val);
|
vlen = strlen(val);
|
||||||
|
if(utflen(val) > Maxrunes)
|
||||||
|
die("map value too long: %s", path);
|
||||||
insert(t, key, klen, val, vlen);
|
insert(t, key, klen, val, vlen);
|
||||||
free(line);
|
free(line);
|
||||||
}
|
}
|
||||||
@@ -118,6 +123,8 @@ trieget(Trie *t, char *key, int klen, int *vlen)
|
|||||||
int ni;
|
int ni;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
if(t == nil || klen < 0 || (klen > 0 && key == nil))
|
||||||
|
return nil;
|
||||||
ni = t->root;
|
ni = t->root;
|
||||||
for(i = 0; i < klen; i++){
|
for(i = 0; i < klen; i++){
|
||||||
ni = find(t, ni, key[i]);
|
ni = find(t, ni, key[i]);
|
||||||
@@ -126,7 +133,8 @@ trieget(Trie *t, char *key, int klen, int *vlen)
|
|||||||
}
|
}
|
||||||
if(t->nodes[ni].val == nil)
|
if(t->nodes[ni].val == nil)
|
||||||
return nil;
|
return nil;
|
||||||
*vlen = t->nodes[ni].vlen;
|
if(vlen != nil)
|
||||||
|
*vlen = t->nodes[ni].vlen;
|
||||||
return t->nodes[ni].val;
|
return t->nodes[ni].val;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,15 +144,19 @@ trielookup(Trie *t, char *key, int klen, char **val, int *vlen)
|
|||||||
int ni;
|
int ni;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
if(t == nil || klen < 0 || (klen > 0 && key == nil))
|
||||||
|
return 0;
|
||||||
ni = t->root;
|
ni = t->root;
|
||||||
for(i = 0; i < klen; i++){
|
for(i = 0; i < klen; i++){
|
||||||
ni = find(t, ni, key[i]);
|
ni = find(t, ni, key[i]);
|
||||||
if(ni < 0)
|
if(ni < 0)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if(val != nil && t->nodes[ni].val != nil){
|
if(t->nodes[ni].val != nil){
|
||||||
*val = t->nodes[ni].val;
|
if(val != nil)
|
||||||
*vlen = t->nodes[ni].vlen;
|
*val = t->nodes[ni].val;
|
||||||
|
if(vlen != nil)
|
||||||
|
*vlen = t->nodes[ni].vlen;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
380
wayland.c
380
wayland.c
@@ -1,18 +1,19 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <errno.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
#include <string.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include <wayland-client.h>
|
#include <wayland-client.h>
|
||||||
#include <xkbcommon/xkbcommon.h>
|
#include <xkbcommon/xkbcommon.h>
|
||||||
#include "input-method-unstable-v2-client-protocol.h"
|
#include "input-method-unstable-v2-client-protocol.h"
|
||||||
#include "virtual-keyboard-unstable-v1-client-protocol.h"
|
#include "virtual-keyboard-unstable-v1-client-protocol.h"
|
||||||
|
|
||||||
static struct wl_display *dpy;
|
static struct wl_display *dpy;
|
||||||
|
static struct wl_registry *reg;
|
||||||
static struct wl_seat *seat;
|
static struct wl_seat *seat;
|
||||||
static struct zwp_input_method_manager_v2 *immgr;
|
static struct zwp_input_method_manager_v2 *immgr;
|
||||||
static struct zwp_virtual_keyboard_manager_v1 *vkmgr;
|
static struct zwp_virtual_keyboard_manager_v1 *vkmgr;
|
||||||
@@ -24,14 +25,34 @@ static struct xkb_keymap *keymap;
|
|||||||
static struct xkb_state *xkbst;
|
static struct xkb_state *xkbst;
|
||||||
static int active;
|
static int active;
|
||||||
static int pending;
|
static int pending;
|
||||||
|
static int newactivation;
|
||||||
|
static int running;
|
||||||
static u32int imserial;
|
static u32int imserial;
|
||||||
|
static u32int seatname;
|
||||||
|
static u32int immgrname;
|
||||||
|
static u32int vkmgrname;
|
||||||
static Channel *replyc;
|
static Channel *replyc;
|
||||||
|
static uvlong owner;
|
||||||
|
static Wlstate keys;
|
||||||
|
|
||||||
|
static uvlong
|
||||||
|
nowms(void)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
||||||
|
return 0;
|
||||||
|
return (uvlong)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sendkey(u32int ks, u32int mod, Keyres *res)
|
sendkey(u32int ks, u32int mod, Keyres *res)
|
||||||
{
|
{
|
||||||
Keyreq kr;
|
Keyreq kr;
|
||||||
|
|
||||||
|
memset(&kr, 0, sizeof kr);
|
||||||
|
kr.owner = owner;
|
||||||
|
kr.op = Keypress;
|
||||||
kr.ks = ks;
|
kr.ks = ks;
|
||||||
kr.mod = mod;
|
kr.mod = mod;
|
||||||
kr.want = 1;
|
kr.want = 1;
|
||||||
@@ -41,11 +62,20 @@ sendkey(u32int ks, u32int mod, Keyres *res)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sendreset(void)
|
releaseowner(void)
|
||||||
{
|
{
|
||||||
|
Keyreq kr;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
|
|
||||||
sendkey(Kesc, 0, &res);
|
if(owner == 0)
|
||||||
|
return;
|
||||||
|
memset(&kr, 0, sizeof kr);
|
||||||
|
kr.owner = owner;
|
||||||
|
kr.op = Keyrelease;
|
||||||
|
kr.reply = replyc;
|
||||||
|
chansend(keyc, &kr);
|
||||||
|
chanrecv(replyc, &res);
|
||||||
|
owner = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static u32int
|
static u32int
|
||||||
@@ -85,47 +115,101 @@ mget(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
kpress(uint32_t time, uint32_t keycode, uint32_t state)
|
putresult(Keyres *res)
|
||||||
|
{
|
||||||
|
char commit[Maxutf], preedit[Maxutf];
|
||||||
|
int plen;
|
||||||
|
|
||||||
|
if(im == nil)
|
||||||
|
return;
|
||||||
|
stoutf(&res->commit, commit, sizeof commit);
|
||||||
|
stoutf(&res->preedit, preedit, sizeof preedit);
|
||||||
|
if(commit[0] != '\0')
|
||||||
|
zwp_input_method_v2_commit_string(im, commit);
|
||||||
|
plen = strlen(preedit);
|
||||||
|
zwp_input_method_v2_set_preedit_string(im, preedit, plen, plen);
|
||||||
|
zwp_input_method_v2_commit(im, imserial);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
forwardkey(u32int time, u32int keycode, u32int state)
|
||||||
|
{
|
||||||
|
if(vk != nil)
|
||||||
|
zwp_virtual_keyboard_v1_key(vk, time, keycode, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
keypress(u32int time, u32int keycode)
|
||||||
{
|
{
|
||||||
xkb_keysym_t ks;
|
xkb_keysym_t ks;
|
||||||
u32int k, mod;
|
u32int k, mod;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
char commit[Maxutf], preedit[Maxutf];
|
int repeatable;
|
||||||
int plen;
|
|
||||||
|
|
||||||
if(state != WL_KEYBOARD_KEY_STATE_PRESSED || xkbst == nil){
|
if(xkbst == nil || keycode >= Wlkeymax){
|
||||||
zwp_virtual_keyboard_v1_key(vk, time, keycode, state);
|
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
wlstatepress(&keys, keycode, 0, 0, nowms());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ks = xkb_state_key_get_one_sym(xkbst, keycode + 8);
|
ks = xkb_state_key_get_one_sym(xkbst, keycode + 8);
|
||||||
k = kget(ks);
|
k = kget(ks);
|
||||||
if(k == 0){
|
if(k == 0){
|
||||||
zwp_virtual_keyboard_v1_key(vk, time, keycode, state);
|
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
wlstatepress(&keys, keycode, 0, 0, nowms());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mod = mget();
|
mod = mget();
|
||||||
sendkey(k, mod, &res);
|
sendkey(k, mod, &res);
|
||||||
stoutf(&res.commit, commit, sizeof commit);
|
putresult(&res);
|
||||||
stoutf(&res.preedit, preedit, sizeof preedit);
|
repeatable = keymap != nil &&
|
||||||
if(commit[0] != '\0'){
|
xkb_keymap_key_repeats(keymap, keycode + 8);
|
||||||
zwp_input_method_v2_commit_string(im, commit);
|
wlstatepress(&keys, keycode, res.eaten, repeatable, nowms());
|
||||||
zwp_input_method_v2_commit(im, imserial);
|
|
||||||
}
|
|
||||||
plen = strlen(preedit);
|
|
||||||
zwp_input_method_v2_set_preedit_string(im, preedit, plen, plen);
|
|
||||||
zwp_input_method_v2_commit(im, imserial);
|
|
||||||
if(!res.eaten)
|
if(!res.eaten)
|
||||||
zwp_virtual_keyboard_v1_key(vk, time, keycode, state);
|
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
keyrepeat(u32int time, u32int keycode)
|
||||||
|
{
|
||||||
|
xkb_keysym_t ks;
|
||||||
|
u32int k;
|
||||||
|
Keyres res;
|
||||||
|
|
||||||
|
if(xkbst == nil)
|
||||||
|
return;
|
||||||
|
ks = xkb_state_key_get_one_sym(xkbst, keycode + 8);
|
||||||
|
k = kget(ks);
|
||||||
|
if(k == 0)
|
||||||
|
return;
|
||||||
|
sendkey(k, mget(), &res);
|
||||||
|
putresult(&res);
|
||||||
|
/* The original eaten press still owns its eventual physical release. */
|
||||||
|
if(!res.eaten){
|
||||||
|
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_RELEASED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
kpress(u32int time, u32int keycode, u32int state)
|
||||||
|
{
|
||||||
|
if(state == WL_KEYBOARD_KEY_STATE_PRESSED){
|
||||||
|
keypress(time, keycode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(wlstaterelease(&keys, keycode))
|
||||||
|
forwardkey(time, keycode, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
kg_keymap(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
|
kg_keymap(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
|
||||||
uint32_t format, int32_t fd, uint32_t size)
|
u32int format, int32_t fd, u32int size)
|
||||||
{
|
{
|
||||||
char *s;
|
char *s;
|
||||||
|
|
||||||
(void)data;
|
(void)data;
|
||||||
(void)g;
|
(void)g;
|
||||||
|
wlstatecancelrepeat(&keys);
|
||||||
if(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1){
|
if(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1){
|
||||||
close(fd);
|
close(fd);
|
||||||
return;
|
return;
|
||||||
@@ -145,13 +229,14 @@ kg_keymap(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
|
|||||||
xkbst = xkb_state_new(keymap);
|
xkbst = xkb_state_new(keymap);
|
||||||
munmap(s, size);
|
munmap(s, size);
|
||||||
}
|
}
|
||||||
zwp_virtual_keyboard_v1_keymap(vk, format, fd, size);
|
if(vk != nil)
|
||||||
|
zwp_virtual_keyboard_v1_keymap(vk, format, fd, size);
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
kg_key(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
|
kg_key(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
|
||||||
uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
|
u32int serial, u32int time, u32int key, u32int state)
|
||||||
{
|
{
|
||||||
(void)data;
|
(void)data;
|
||||||
(void)g;
|
(void)g;
|
||||||
@@ -161,15 +246,15 @@ kg_key(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
kg_mods(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
|
kg_mods(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
|
||||||
uint32_t serial, uint32_t dep, uint32_t lat, uint32_t lck,
|
u32int serial, u32int dep, u32int lat, u32int lck, u32int group)
|
||||||
uint32_t group)
|
|
||||||
{
|
{
|
||||||
(void)data;
|
(void)data;
|
||||||
(void)g;
|
(void)g;
|
||||||
(void)serial;
|
(void)serial;
|
||||||
if(xkbst != nil)
|
if(xkbst != nil)
|
||||||
xkb_state_update_mask(xkbst, dep, lat, lck, 0, 0, group);
|
xkb_state_update_mask(xkbst, dep, lat, lck, 0, 0, group);
|
||||||
zwp_virtual_keyboard_v1_modifiers(vk, dep, lat, lck, group);
|
if(vk != nil)
|
||||||
|
zwp_virtual_keyboard_v1_modifiers(vk, dep, lat, lck, group);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -178,8 +263,7 @@ kg_repeat(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
|
|||||||
{
|
{
|
||||||
(void)data;
|
(void)data;
|
||||||
(void)g;
|
(void)g;
|
||||||
(void)rate;
|
wlstaterepeatinfo(&keys, rate, delay);
|
||||||
(void)delay;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct zwp_input_method_keyboard_grab_v2_listener
|
static const struct zwp_input_method_keyboard_grab_v2_listener
|
||||||
@@ -196,6 +280,7 @@ im_activate(void *data, struct zwp_input_method_v2 *m)
|
|||||||
(void)data;
|
(void)data;
|
||||||
(void)m;
|
(void)m;
|
||||||
pending = 1;
|
pending = 1;
|
||||||
|
newactivation = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -204,11 +289,12 @@ im_deactivate(void *data, struct zwp_input_method_v2 *m)
|
|||||||
(void)data;
|
(void)data;
|
||||||
(void)m;
|
(void)m;
|
||||||
pending = 0;
|
pending = 0;
|
||||||
|
wlstateclear(&keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
im_surrounding(void *data, struct zwp_input_method_v2 *m,
|
im_surrounding(void *data, struct zwp_input_method_v2 *m,
|
||||||
const char *text, uint32_t cursor, uint32_t anchor)
|
const char *text, u32int cursor, u32int anchor)
|
||||||
{
|
{
|
||||||
(void)data;
|
(void)data;
|
||||||
(void)m;
|
(void)m;
|
||||||
@@ -218,7 +304,7 @@ im_surrounding(void *data, struct zwp_input_method_v2 *m,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
im_textchange(void *data, struct zwp_input_method_v2 *m, uint32_t cause)
|
im_textchange(void *data, struct zwp_input_method_v2 *m, u32int cause)
|
||||||
{
|
{
|
||||||
(void)data;
|
(void)data;
|
||||||
(void)m;
|
(void)m;
|
||||||
@@ -227,7 +313,7 @@ im_textchange(void *data, struct zwp_input_method_v2 *m, uint32_t cause)
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
im_content(void *data, struct zwp_input_method_v2 *m,
|
im_content(void *data, struct zwp_input_method_v2 *m,
|
||||||
uint32_t hint, uint32_t purpose)
|
u32int hint, u32int purpose)
|
||||||
{
|
{
|
||||||
(void)data;
|
(void)data;
|
||||||
(void)m;
|
(void)m;
|
||||||
@@ -235,26 +321,42 @@ im_content(void *data, struct zwp_input_method_v2 *m,
|
|||||||
(void)purpose;
|
(void)purpose;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
endactivation(void)
|
||||||
|
{
|
||||||
|
wlstateclear(&keys);
|
||||||
|
active = 0;
|
||||||
|
releaseowner();
|
||||||
|
if(grab != nil){
|
||||||
|
zwp_input_method_keyboard_grab_v2_release(grab);
|
||||||
|
grab = nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
beginactivation(void)
|
||||||
|
{
|
||||||
|
if(active)
|
||||||
|
endactivation();
|
||||||
|
active = 1;
|
||||||
|
owner = ownernew();
|
||||||
|
grab = zwp_input_method_v2_grab_keyboard(im);
|
||||||
|
if(grab != nil)
|
||||||
|
zwp_input_method_keyboard_grab_v2_add_listener(grab,
|
||||||
|
&grab_listener, nil);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
im_done(void *data, struct zwp_input_method_v2 *m)
|
im_done(void *data, struct zwp_input_method_v2 *m)
|
||||||
{
|
{
|
||||||
(void)data;
|
(void)data;
|
||||||
(void)m;
|
(void)m;
|
||||||
imserial++;
|
imserial++;
|
||||||
if(pending && !active){
|
if(newactivation && pending)
|
||||||
active = 1;
|
beginactivation();
|
||||||
grab = zwp_input_method_v2_grab_keyboard(im);
|
else if(!pending && active)
|
||||||
if(grab != nil)
|
endactivation();
|
||||||
zwp_input_method_keyboard_grab_v2_add_listener(grab,
|
newactivation = 0;
|
||||||
&grab_listener, nil);
|
|
||||||
}else if(!pending && active){
|
|
||||||
active = 0;
|
|
||||||
sendreset();
|
|
||||||
if(grab != nil){
|
|
||||||
zwp_input_method_keyboard_grab_v2_release(grab);
|
|
||||||
grab = nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -262,6 +364,8 @@ im_unavail(void *data, struct zwp_input_method_v2 *m)
|
|||||||
{
|
{
|
||||||
(void)data;
|
(void)data;
|
||||||
(void)m;
|
(void)m;
|
||||||
|
wlstateclear(&keys);
|
||||||
|
running = 0;
|
||||||
fprint(2, "strans: wayland: input-method unavailable\n");
|
fprint(2, "strans: wayland: input-method unavailable\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,28 +380,34 @@ static const struct zwp_input_method_v2_listener im_listener = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
reg_global(void *data, struct wl_registry *r, uint32_t name,
|
reg_global(void *data, struct wl_registry *r, u32int name,
|
||||||
const char *iface, uint32_t version)
|
const char *iface, u32int version)
|
||||||
{
|
{
|
||||||
(void)data;
|
(void)data;
|
||||||
if(strcmp(iface, wl_seat_interface.name) == 0)
|
if(strcmp(iface, wl_seat_interface.name) == 0 && seat == nil){
|
||||||
|
seatname = name;
|
||||||
seat = wl_registry_bind(r, name, &wl_seat_interface,
|
seat = wl_registry_bind(r, name, &wl_seat_interface,
|
||||||
version < 5 ? version : 5);
|
version < 5 ? version : 5);
|
||||||
else if(strcmp(iface, zwp_input_method_manager_v2_interface.name) == 0)
|
}else if(strcmp(iface,
|
||||||
|
zwp_input_method_manager_v2_interface.name) == 0 && immgr == nil){
|
||||||
|
immgrname = name;
|
||||||
immgr = wl_registry_bind(r, name,
|
immgr = wl_registry_bind(r, name,
|
||||||
&zwp_input_method_manager_v2_interface, 1);
|
&zwp_input_method_manager_v2_interface, 1);
|
||||||
else if(strcmp(iface,
|
}else if(strcmp(iface,
|
||||||
zwp_virtual_keyboard_manager_v1_interface.name) == 0)
|
zwp_virtual_keyboard_manager_v1_interface.name) == 0 && vkmgr == nil){
|
||||||
|
vkmgrname = name;
|
||||||
vkmgr = wl_registry_bind(r, name,
|
vkmgr = wl_registry_bind(r, name,
|
||||||
&zwp_virtual_keyboard_manager_v1_interface, 1);
|
&zwp_virtual_keyboard_manager_v1_interface, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
reg_remove(void *data, struct wl_registry *r, uint32_t name)
|
reg_remove(void *data, struct wl_registry *r, u32int name)
|
||||||
{
|
{
|
||||||
(void)data;
|
(void)data;
|
||||||
(void)r;
|
(void)r;
|
||||||
(void)name;
|
if(name == seatname || name == immgrname || name == vkmgrname)
|
||||||
|
running = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct wl_registry_listener reg_listener = {
|
static const struct wl_registry_listener reg_listener = {
|
||||||
@@ -305,37 +415,159 @@ static const struct wl_registry_listener reg_listener = {
|
|||||||
.global_remove = reg_remove,
|
.global_remove = reg_remove,
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
static int
|
||||||
waylandthread(void *_)
|
dispatch(void)
|
||||||
{
|
{
|
||||||
struct wl_registry *reg;
|
struct pollfd pfd;
|
||||||
|
u32int key;
|
||||||
|
uvlong now;
|
||||||
|
int flush, timeout, n;
|
||||||
|
|
||||||
(void)_;
|
pfd.fd = wl_display_get_fd(dpy);
|
||||||
threadsetname("wayland");
|
while(running){
|
||||||
while((dpy = wl_display_connect(nil)) == nil)
|
if(wl_display_dispatch_pending(dpy) < 0)
|
||||||
poll(nil, 0, 1000);
|
return -1;
|
||||||
xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
if(!running)
|
||||||
if(xkb == nil){
|
break;
|
||||||
|
now = nowms();
|
||||||
|
if(wlstaterepeat(&keys, now, &key))
|
||||||
|
keyrepeat((u32int)now, key);
|
||||||
|
while(wl_display_prepare_read(dpy) != 0)
|
||||||
|
if(wl_display_dispatch_pending(dpy) < 0)
|
||||||
|
return -1;
|
||||||
|
flush = wl_display_flush(dpy);
|
||||||
|
if(flush < 0 && errno != EAGAIN){
|
||||||
|
wl_display_cancel_read(dpy);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
if(flush < 0)
|
||||||
|
pfd.events |= POLLOUT;
|
||||||
|
pfd.revents = 0;
|
||||||
|
timeout = wlstatetimeout(&keys, nowms());
|
||||||
|
n = poll(&pfd, 1, timeout);
|
||||||
|
if(n < 0){
|
||||||
|
wl_display_cancel_read(dpy);
|
||||||
|
if(errno == EINTR)
|
||||||
|
continue;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if(n == 0){
|
||||||
|
wl_display_cancel_read(dpy);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(pfd.revents & POLLIN){
|
||||||
|
if(wl_display_read_events(dpy) < 0)
|
||||||
|
return -1;
|
||||||
|
}else
|
||||||
|
wl_display_cancel_read(dpy);
|
||||||
|
if(pfd.revents & (POLLERR | POLLHUP | POLLNVAL))
|
||||||
|
return -1;
|
||||||
|
if(pfd.revents & POLLOUT)
|
||||||
|
wl_display_flush(dpy);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
resetconnection(void)
|
||||||
|
{
|
||||||
|
dpy = nil;
|
||||||
|
reg = nil;
|
||||||
|
seat = nil;
|
||||||
|
immgr = nil;
|
||||||
|
vkmgr = nil;
|
||||||
|
im = nil;
|
||||||
|
grab = nil;
|
||||||
|
vk = nil;
|
||||||
|
xkb = nil;
|
||||||
|
keymap = nil;
|
||||||
|
xkbst = nil;
|
||||||
|
active = 0;
|
||||||
|
pending = 0;
|
||||||
|
newactivation = 0;
|
||||||
|
running = 1;
|
||||||
|
imserial = 0;
|
||||||
|
seatname = 0;
|
||||||
|
immgrname = 0;
|
||||||
|
vkmgrname = 0;
|
||||||
|
owner = 0;
|
||||||
|
wlstateinit(&keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
cleanup(void)
|
||||||
|
{
|
||||||
|
running = 0;
|
||||||
|
wlstateclear(&keys);
|
||||||
|
releaseowner();
|
||||||
|
active = 0;
|
||||||
|
if(xkbst != nil)
|
||||||
|
xkb_state_unref(xkbst);
|
||||||
|
if(keymap != nil)
|
||||||
|
xkb_keymap_unref(keymap);
|
||||||
|
if(xkb != nil)
|
||||||
|
xkb_context_unref(xkb);
|
||||||
|
if(grab != nil)
|
||||||
|
wl_proxy_destroy((struct wl_proxy*)grab);
|
||||||
|
if(vk != nil)
|
||||||
|
wl_proxy_destroy((struct wl_proxy*)vk);
|
||||||
|
if(im != nil)
|
||||||
|
wl_proxy_destroy((struct wl_proxy*)im);
|
||||||
|
if(vkmgr != nil)
|
||||||
|
wl_proxy_destroy((struct wl_proxy*)vkmgr);
|
||||||
|
if(immgr != nil)
|
||||||
|
wl_proxy_destroy((struct wl_proxy*)immgr);
|
||||||
|
if(seat != nil)
|
||||||
|
wl_proxy_destroy((struct wl_proxy*)seat);
|
||||||
|
if(reg != nil)
|
||||||
|
wl_registry_destroy(reg);
|
||||||
|
if(dpy != nil){
|
||||||
wl_display_disconnect(dpy);
|
wl_display_disconnect(dpy);
|
||||||
dpy = nil;
|
dpy = nil;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
connectonce(void)
|
||||||
|
{
|
||||||
|
resetconnection();
|
||||||
|
dpy = wl_display_connect(nil);
|
||||||
|
if(dpy == nil)
|
||||||
|
return;
|
||||||
|
xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||||
|
if(xkb == nil)
|
||||||
|
return;
|
||||||
reg = wl_display_get_registry(dpy);
|
reg = wl_display_get_registry(dpy);
|
||||||
|
if(reg == nil)
|
||||||
|
return;
|
||||||
wl_registry_add_listener(reg, ®_listener, nil);
|
wl_registry_add_listener(reg, ®_listener, nil);
|
||||||
wl_display_roundtrip(dpy);
|
if(wl_display_roundtrip(dpy) < 0)
|
||||||
|
return;
|
||||||
|
if(!running)
|
||||||
|
return;
|
||||||
if(seat == nil || immgr == nil || vkmgr == nil){
|
if(seat == nil || immgr == nil || vkmgr == nil){
|
||||||
fprint(2,
|
fprint(2,
|
||||||
"strans: wayland: compositor missing input-method-v2\n");
|
"strans: wayland: compositor missing input-method-v2\n");
|
||||||
wl_display_disconnect(dpy);
|
|
||||||
dpy = nil;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
im = zwp_input_method_manager_v2_get_input_method(immgr, seat);
|
im = zwp_input_method_manager_v2_get_input_method(immgr, seat);
|
||||||
zwp_input_method_v2_add_listener(im, &im_listener, nil);
|
|
||||||
vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(vkmgr, seat);
|
vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(vkmgr, seat);
|
||||||
replyc = chancreate(sizeof(Keyres), 0);
|
if(im == nil || vk == nil)
|
||||||
while(wl_display_dispatch(dpy) != -1)
|
return;
|
||||||
;
|
zwp_input_method_v2_add_listener(im, &im_listener, nil);
|
||||||
chanfree(replyc);
|
dispatch();
|
||||||
replyc = nil;
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
waylandthread(void *_)
|
||||||
|
{
|
||||||
|
(void)_;
|
||||||
|
threadsetname("wayland");
|
||||||
|
replyc = chancreate(sizeof(Keyres), 0);
|
||||||
|
for(;;){
|
||||||
|
connectonce();
|
||||||
|
cleanup();
|
||||||
|
poll(nil, 0, 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
107
wayland_state.c
Normal file
107
wayland_state.c
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
Downforward = 1,
|
||||||
|
Downeaten = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
wlstateinit(Wlstate *s)
|
||||||
|
{
|
||||||
|
memset(s, 0, sizeof *s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wlstatecancelrepeat(Wlstate *s)
|
||||||
|
{
|
||||||
|
s->repeating = 0;
|
||||||
|
s->repeatkey = 0;
|
||||||
|
s->repeatat = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wlstateclear(Wlstate *s)
|
||||||
|
{
|
||||||
|
memset(s->down, 0, sizeof s->down);
|
||||||
|
wlstatecancelrepeat(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wlstaterepeatinfo(Wlstate *s, int rate, int delay)
|
||||||
|
{
|
||||||
|
/* Settings changes end the old schedule; a later press starts a new one. */
|
||||||
|
wlstatecancelrepeat(s);
|
||||||
|
s->rate = rate > 0 ? rate : 0;
|
||||||
|
s->delay = delay > 0 ? delay : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
wlstatepress(Wlstate *s, u32int key, int eaten, int repeatable, uvlong now)
|
||||||
|
{
|
||||||
|
uvlong delay;
|
||||||
|
|
||||||
|
if(key >= Wlkeymax)
|
||||||
|
return 0;
|
||||||
|
s->down[key] = eaten ? Downeaten : Downforward;
|
||||||
|
wlstatecancelrepeat(s);
|
||||||
|
if(eaten && repeatable && s->rate > 0){
|
||||||
|
delay = s->delay;
|
||||||
|
if(~(uvlong)0 - now < delay)
|
||||||
|
s->repeatat = ~(uvlong)0;
|
||||||
|
else
|
||||||
|
s->repeatat = now + delay;
|
||||||
|
s->repeatkey = key;
|
||||||
|
s->repeating = 1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
wlstaterelease(Wlstate *s, u32int key)
|
||||||
|
{
|
||||||
|
int forward;
|
||||||
|
|
||||||
|
if(key >= Wlkeymax)
|
||||||
|
return 1;
|
||||||
|
forward = s->down[key] != Downeaten;
|
||||||
|
s->down[key] = 0;
|
||||||
|
if(s->repeating && s->repeatkey == key)
|
||||||
|
wlstatecancelrepeat(s);
|
||||||
|
return forward;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
wlstatetimeout(Wlstate *s, uvlong now)
|
||||||
|
{
|
||||||
|
uvlong d;
|
||||||
|
|
||||||
|
if(!s->repeating)
|
||||||
|
return -1;
|
||||||
|
if(now >= s->repeatat)
|
||||||
|
return 0;
|
||||||
|
d = s->repeatat - now;
|
||||||
|
return d > INT_MAX ? INT_MAX : (int)d;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
wlstaterepeat(Wlstate *s, uvlong now, u32int *key)
|
||||||
|
{
|
||||||
|
uvlong step;
|
||||||
|
|
||||||
|
if(!s->repeating || now < s->repeatat)
|
||||||
|
return 0;
|
||||||
|
if(key != 0)
|
||||||
|
*key = s->repeatkey;
|
||||||
|
step = 1000 / s->rate;
|
||||||
|
if(step == 0)
|
||||||
|
step = 1;
|
||||||
|
if(~(uvlong)0 - now < step)
|
||||||
|
s->repeatat = ~(uvlong)0;
|
||||||
|
else
|
||||||
|
s->repeatat = now + step;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
151
win.c
151
win.c
@@ -12,6 +12,7 @@ static xcb_connection_t *conn;
|
|||||||
static xcb_screen_t *scr;
|
static xcb_screen_t *scr;
|
||||||
static xcb_window_t win;
|
static xcb_window_t win;
|
||||||
static xcb_gcontext_t gc;
|
static xcb_gcontext_t gc;
|
||||||
|
static xcb_pixmap_t pix;
|
||||||
static u32int *img;
|
static u32int *img;
|
||||||
static int depth;
|
static int depth;
|
||||||
|
|
||||||
@@ -26,7 +27,70 @@ getscr(xcb_connection_t *c, int n)
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static xcb_visualtype_t*
|
||||||
|
getvisual(xcb_screen_t *s)
|
||||||
|
{
|
||||||
|
xcb_depth_iterator_t di;
|
||||||
|
xcb_visualtype_iterator_t vi;
|
||||||
|
|
||||||
|
for(di = xcb_screen_allowed_depths_iterator(s); di.rem;
|
||||||
|
xcb_depth_next(&di)){
|
||||||
|
if(di.data->depth != s->root_depth)
|
||||||
|
continue;
|
||||||
|
for(vi = xcb_depth_visuals_iterator(di.data); vi.rem;
|
||||||
|
xcb_visualtype_next(&vi))
|
||||||
|
if(vi.data->visual_id == s->root_visual)
|
||||||
|
return vi.data;
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
validformat(xcb_connection_t *c, xcb_screen_t *s)
|
||||||
|
{
|
||||||
|
const xcb_setup_t *setup;
|
||||||
|
xcb_format_iterator_t fi;
|
||||||
|
xcb_visualtype_t *v;
|
||||||
|
u32int one;
|
||||||
|
int lsb;
|
||||||
|
|
||||||
|
setup = xcb_get_setup(c);
|
||||||
|
v = getvisual(s);
|
||||||
|
if(setup == nil || v == nil || s->root_depth != 24 ||
|
||||||
|
v->_class != XCB_VISUAL_CLASS_TRUE_COLOR ||
|
||||||
|
v->bits_per_rgb_value != 8 ||
|
||||||
|
v->red_mask != 0xff0000 || v->green_mask != 0x00ff00 ||
|
||||||
|
v->blue_mask != 0x0000ff)
|
||||||
|
return 0;
|
||||||
|
one = 1;
|
||||||
|
lsb = *(uchar*)&one != 0;
|
||||||
|
if((lsb && setup->image_byte_order != XCB_IMAGE_ORDER_LSB_FIRST) ||
|
||||||
|
(!lsb && setup->image_byte_order != XCB_IMAGE_ORDER_MSB_FIRST))
|
||||||
|
return 0;
|
||||||
|
for(fi = xcb_setup_pixmap_formats_iterator(setup); fi.rem;
|
||||||
|
xcb_format_next(&fi))
|
||||||
|
if(fi.data->depth == 24 && fi.data->bits_per_pixel == 32 &&
|
||||||
|
fi.data->scanline_pad == 32)
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
wincleanup(void)
|
||||||
|
{
|
||||||
|
free(img);
|
||||||
|
img = nil;
|
||||||
|
if(conn != nil){
|
||||||
|
xcb_disconnect(conn);
|
||||||
|
conn = nil;
|
||||||
|
}
|
||||||
|
scr = nil;
|
||||||
|
win = 0;
|
||||||
|
gc = 0;
|
||||||
|
pix = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
wininit(void)
|
wininit(void)
|
||||||
{
|
{
|
||||||
int n;
|
int n;
|
||||||
@@ -35,11 +99,17 @@ wininit(void)
|
|||||||
xcb_intern_atom_reply_t *r1, *r2;
|
xcb_intern_atom_reply_t *r1, *r2;
|
||||||
|
|
||||||
conn = xcb_connect(nil, &n);
|
conn = xcb_connect(nil, &n);
|
||||||
if(conn == nil || xcb_connection_has_error(conn))
|
if(conn == nil || xcb_connection_has_error(conn)){
|
||||||
die("xcb_connect");
|
fprint(2, "strans: popup disabled: cannot connect to X display\n");
|
||||||
|
wincleanup();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
scr = getscr(conn, n);
|
scr = getscr(conn, n);
|
||||||
if(scr == nil)
|
if(scr == nil || !validformat(conn, scr)){
|
||||||
die("no screen");
|
fprint(2, "strans: popup disabled: unsupported X root format\n");
|
||||||
|
wincleanup();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
depth = scr->root_depth;
|
depth = scr->root_depth;
|
||||||
win = xcb_generate_id(conn);
|
win = xcb_generate_id(conn);
|
||||||
mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
|
mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
|
||||||
@@ -65,16 +135,28 @@ wininit(void)
|
|||||||
free(r2);
|
free(r2);
|
||||||
gc = xcb_generate_id(conn);
|
gc = xcb_generate_id(conn);
|
||||||
xcb_create_gc(conn, gc, win, 0, nil);
|
xcb_create_gc(conn, gc, win, 0, nil);
|
||||||
|
pix = xcb_generate_id(conn);
|
||||||
|
xcb_create_pixmap(conn, depth, pix, win, Imgw, Imgh);
|
||||||
|
mask = XCB_CW_BACK_PIXMAP;
|
||||||
|
xcb_change_window_attributes(conn, win, mask, &pix);
|
||||||
img = emalloc(Imgw * Imgh * sizeof(img[0]));
|
img = emalloc(Imgw * Imgh * sizeof(img[0]));
|
||||||
fontinit(fontdir);
|
if(!fontinit(fontdir)){
|
||||||
|
fprint(2, "strans: popup disabled: no usable fonts\n");
|
||||||
|
wincleanup();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
drawstr(u32int *buf, int x, int y, Rune *r, int n, int maxw, int maxh)
|
drawstr(u32int *buf, int x, int y, Rune *r, int n, int maxw, int maxh)
|
||||||
{
|
{
|
||||||
while(n-- > 0){
|
while(n-- > 0){
|
||||||
putfont(buf, maxw, maxh, x, y, *r++);
|
if(*r != 0xfe0e && *r != 0xfe0f){
|
||||||
x += Fontsz;
|
putfont(buf, maxw, maxh, x, y, *r);
|
||||||
|
x += Fontsz;
|
||||||
|
}
|
||||||
|
r++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,19 +193,20 @@ drawkouho(Drawcmd *dc, int first, int n, int w, int h)
|
|||||||
static void
|
static void
|
||||||
putimage(int w, int h)
|
putimage(int w, int h)
|
||||||
{
|
{
|
||||||
xcb_put_image(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, win, gc,
|
xcb_put_image(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc,
|
||||||
w, h, 0, 0, 0, depth, w * h * 4, (u8int*)img);
|
w, h, 0, 0, 0, depth, w * h * 4, (u8int*)img);
|
||||||
xcb_flush(conn);
|
/* The retained background pixmap lets the server repaint exposures. */
|
||||||
|
xcb_clear_area(conn, 0, win, 0, 0, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
winhide(void)
|
winhide(void)
|
||||||
{
|
{
|
||||||
xcb_unmap_window(conn, win);
|
xcb_unmap_window(conn, win);
|
||||||
xcb_flush(conn);
|
return xcb_flush(conn) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
winshow(Drawcmd *dc)
|
winshow(Drawcmd *dc)
|
||||||
{
|
{
|
||||||
int npre, px, py, w, h, i, n, maxw;
|
int npre, px, py, w, h, i, n, maxw;
|
||||||
@@ -134,23 +217,27 @@ winshow(Drawcmd *dc)
|
|||||||
n = min(max(dc->nkouho, 0), Maxdisp);
|
n = min(max(dc->nkouho, 0), Maxdisp);
|
||||||
npre = dc->pre.n != 0;
|
npre = dc->pre.n != 0;
|
||||||
if(n == 0 && npre == 0){
|
if(n == 0 && npre == 0){
|
||||||
winhide();
|
return winhide();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
cookie = xcb_query_pointer(conn, scr->root);
|
maxw = popupcells(dc->pre.r, dc->pre.n);
|
||||||
maxw = dc->pre.n;
|
|
||||||
for(i = 0; i < n; i++)
|
for(i = 0; i < n; i++)
|
||||||
maxw = max(maxw, dc->kouho[i].n);
|
maxw = max(maxw, popupcells(dc->kouho[i].r, dc->kouho[i].n));
|
||||||
ptr = xcb_query_pointer_reply(conn, cookie, nil);
|
|
||||||
if(ptr == nil)
|
|
||||||
die("xcb_query_pointer");
|
|
||||||
px = ptr->root_x + 10;
|
|
||||||
py = ptr->root_y + 10;
|
|
||||||
free(ptr);
|
|
||||||
vals[3] = h = (n + npre) * Fontsz;
|
vals[3] = h = (n + npre) * Fontsz;
|
||||||
vals[2] = w = (maxw + 3) * Fontsz;
|
vals[2] = w = (maxw + 3) * Fontsz;
|
||||||
vals[1] = py = max(0, min(py, scr->height_in_pixels - h));
|
px = py = 0;
|
||||||
vals[0] = px = max(0, min(px, scr->width_in_pixels - w));
|
if(!dc->caret.valid){
|
||||||
|
cookie = xcb_query_pointer(conn, scr->root);
|
||||||
|
ptr = xcb_query_pointer_reply(conn, cookie, nil);
|
||||||
|
if(ptr == nil)
|
||||||
|
return 0;
|
||||||
|
px = ptr->root_x;
|
||||||
|
py = ptr->root_y;
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
|
popupposition(&dc->caret, px, py, scr->width_in_pixels,
|
||||||
|
scr->height_in_pixels, w, h, &px, &py);
|
||||||
|
vals[1] = py;
|
||||||
|
vals[0] = px;
|
||||||
xcb_configure_window(conn, win,
|
xcb_configure_window(conn, win,
|
||||||
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
||||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
|
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
|
||||||
@@ -158,6 +245,7 @@ winshow(Drawcmd *dc)
|
|||||||
xcb_map_window(conn, win);
|
xcb_map_window(conn, win);
|
||||||
drawkouho(dc, 0, n, w, h);
|
drawkouho(dc, 0, n, w, h);
|
||||||
putimage(w, h);
|
putimage(w, h);
|
||||||
|
return xcb_flush(conn) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -166,13 +254,16 @@ drawthread(void*)
|
|||||||
Drawcmd dc;
|
Drawcmd dc;
|
||||||
|
|
||||||
threadsetname("draw");
|
threadsetname("draw");
|
||||||
wininit();
|
if(!wininit())
|
||||||
|
return;
|
||||||
while(chanrecv(drawc, &dc) > 0){
|
while(chanrecv(drawc, &dc) > 0){
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
while(channbrecv(drawc, &dc) > 0)
|
||||||
;
|
;
|
||||||
if(dc.nkouho == 0 && dc.pre.n == 0)
|
if(dc.nkouho == 0 && dc.pre.n == 0){
|
||||||
winhide();
|
if(!winhide())
|
||||||
else
|
break;
|
||||||
winshow(&dc);
|
}else if(!winshow(&dc))
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
wincleanup();
|
||||||
}
|
}
|
||||||
|
|||||||
13
xim/Makefile
13
xim/Makefile
@@ -6,6 +6,7 @@ PROG = strans-xim
|
|||||||
|
|
||||||
SRCS = $(wildcard *.c)
|
SRCS = $(wildcard *.c)
|
||||||
OBJS = $(SRCS:.c=.o)
|
OBJS = $(SRCS:.c=.o)
|
||||||
|
TEST = xim_test
|
||||||
|
|
||||||
all: $(PROG)
|
all: $(PROG)
|
||||||
|
|
||||||
@@ -14,7 +15,13 @@ $(PROG): $(OBJS) ../ipc.c ../ipc.h
|
|||||||
|
|
||||||
$(OBJS): ../ipc.h
|
$(OBJS): ../ipc.h
|
||||||
|
|
||||||
clean:
|
$(TEST): ../tests/xim_test.c keymap.c ximtext.c
|
||||||
rm -f $(OBJS) $(PROG)
|
$(CC) $(CFLAGS) -o $@ ../tests/xim_test.c keymap.c ximtext.c $(XIM_LIBS)
|
||||||
|
|
||||||
.PHONY: all clean
|
check: $(TEST)
|
||||||
|
./$(TEST)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(OBJS) $(PROG) $(TEST)
|
||||||
|
|
||||||
|
.PHONY: all check clean
|
||||||
|
|||||||
37
xim/keymap.c
Normal file
37
xim/keymap.c
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
ShiftMask = 1<<0,
|
||||||
|
LockMask = 1<<1,
|
||||||
|
GroupShift = 13,
|
||||||
|
GroupMask = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
keymaplookup(const uint32_t *syms, int nsyms, uint16_t state)
|
||||||
|
{
|
||||||
|
uint32_t lo, hi, lower, upper;
|
||||||
|
int col, group, shift;
|
||||||
|
|
||||||
|
if(syms == NULL || nsyms <= 0)
|
||||||
|
return XKB_KEY_NoSymbol;
|
||||||
|
group = (state >> GroupShift) & GroupMask;
|
||||||
|
col = group * 2;
|
||||||
|
if(col >= nsyms || syms[col] == XKB_KEY_NoSymbol)
|
||||||
|
col = 0;
|
||||||
|
lo = syms[col];
|
||||||
|
if(lo == XKB_KEY_NoSymbol)
|
||||||
|
return XKB_KEY_NoSymbol;
|
||||||
|
hi = col + 1 < nsyms ? syms[col + 1] : XKB_KEY_NoSymbol;
|
||||||
|
lower = xkb_keysym_to_lower(lo);
|
||||||
|
upper = xkb_keysym_to_upper(lo);
|
||||||
|
if(hi == XKB_KEY_NoSymbol)
|
||||||
|
hi = lower != upper ? upper : lo;
|
||||||
|
shift = (state & ShiftMask) != 0;
|
||||||
|
/* Lock reverses Shift only for keys with an alphabetic case pair. */
|
||||||
|
if((state & LockMask) && lower != upper)
|
||||||
|
shift = !shift;
|
||||||
|
return shift ? hi : lo;
|
||||||
|
}
|
||||||
147
xim/main.c
147
xim/main.c
@@ -2,22 +2,28 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/un.h>
|
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
#include <xkbcommon/xkbcommon.h>
|
#include <xkbcommon/xkbcommon.h>
|
||||||
#include <xcb-imdkit/imdkit.h>
|
#include <xcb-imdkit/imdkit.h>
|
||||||
#include <xcb-imdkit/encoding.h>
|
#include <xcb-imdkit/encoding.h>
|
||||||
#include "ipc.h"
|
#include "ipc.h"
|
||||||
|
|
||||||
|
uint32_t keymaplookup(const uint32_t*, int, uint16_t);
|
||||||
|
char *ximcompound(const char*, size_t, size_t*);
|
||||||
|
|
||||||
|
typedef struct Ic Ic;
|
||||||
|
struct Ic
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
};
|
||||||
|
|
||||||
static xcb_connection_t *conn;
|
static xcb_connection_t *conn;
|
||||||
static xcb_im_t *xim;
|
static xcb_im_t *xim;
|
||||||
static int srvfd = -1;
|
|
||||||
static xcb_keysym_t *kmap;
|
static xcb_keysym_t *kmap;
|
||||||
static uint8_t minkc, maxkc;
|
static uint8_t minkc, maxkc;
|
||||||
static uint8_t symsper;
|
static uint8_t symsper;
|
||||||
|
|
||||||
static char *encs[] = {"COMPOUND_TEXT", "en_US.UTF-8", ""};
|
static char *encs[] = {"COMPOUND_TEXT"};
|
||||||
static uint32_t styles[] = {
|
static uint32_t styles[] = {
|
||||||
XCB_IM_PreeditNothing | XCB_IM_StatusNothing,
|
XCB_IM_PreeditNothing | XCB_IM_StatusNothing,
|
||||||
XCB_IM_PreeditNone | XCB_IM_StatusNone,
|
XCB_IM_PreeditNone | XCB_IM_StatusNone,
|
||||||
@@ -37,7 +43,7 @@ kinit(void)
|
|||||||
xcb_get_keyboard_mapping_reply_t *r;
|
xcb_get_keyboard_mapping_reply_t *r;
|
||||||
const xcb_setup_t *setup;
|
const xcb_setup_t *setup;
|
||||||
xcb_keysym_t *syms;
|
xcb_keysym_t *syms;
|
||||||
int i, n;
|
int n;
|
||||||
|
|
||||||
setup = xcb_get_setup(conn);
|
setup = xcb_get_setup(conn);
|
||||||
if(setup == NULL)
|
if(setup == NULL)
|
||||||
@@ -50,26 +56,22 @@ kinit(void)
|
|||||||
die("keyboard mapping failed");
|
die("keyboard mapping failed");
|
||||||
symsper = r->keysyms_per_keycode;
|
symsper = r->keysyms_per_keycode;
|
||||||
n = xcb_get_keyboard_mapping_keysyms_length(r);
|
n = xcb_get_keyboard_mapping_keysyms_length(r);
|
||||||
kmap = malloc(n * sizeof(xcb_keysym_t));
|
syms = malloc(n * sizeof(xcb_keysym_t));
|
||||||
if(kmap == NULL)
|
if(syms == NULL)
|
||||||
die("malloc failed");
|
die("malloc failed");
|
||||||
syms = xcb_get_keyboard_mapping_keysyms(r);
|
memcpy(syms, xcb_get_keyboard_mapping_keysyms(r),
|
||||||
for(i = 0; i < n; i++)
|
n * sizeof(xcb_keysym_t));
|
||||||
kmap[i] = syms[i];
|
free(kmap);
|
||||||
|
kmap = syms;
|
||||||
free(r);
|
free(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t
|
static uint32_t
|
||||||
kget(uint8_t kc, uint16_t state)
|
kget(uint8_t kc, uint16_t state)
|
||||||
{
|
{
|
||||||
int col;
|
|
||||||
|
|
||||||
if(kmap == NULL || kc < minkc || kc > maxkc)
|
if(kmap == NULL || kc < minkc || kc > maxkc)
|
||||||
return 0;
|
return 0;
|
||||||
col = (state & Mshift) ? 1 : 0;
|
return keymaplookup(kmap + (kc - minkc) * symsper, symsper, state);
|
||||||
if(col >= symsper)
|
|
||||||
col = 0;
|
|
||||||
return kmap[(kc - minkc) * symsper + col];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static xcb_screen_t*
|
static xcb_screen_t*
|
||||||
@@ -97,7 +99,7 @@ commit(xcb_im_input_context_t *ic, char *s, int len)
|
|||||||
|
|
||||||
if(len == 0)
|
if(len == 0)
|
||||||
return;
|
return;
|
||||||
ct = xcb_utf8_to_compound_text(s, len, &clen);
|
ct = ximcompound(s, len, &clen);
|
||||||
if(ct == NULL)
|
if(ct == NULL)
|
||||||
return;
|
return;
|
||||||
xcb_im_commit_string(xim, ic, XCB_XIM_LOOKUP_CHARS, ct, clen, 0);
|
xcb_im_commit_string(xim, ic, XCB_XIM_LOOKUP_CHARS, ct, clen, 0);
|
||||||
@@ -106,51 +108,40 @@ commit(xcb_im_input_context_t *ic, char *s, int len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
srvclose(void)
|
srvclose(Ic *state)
|
||||||
{
|
{
|
||||||
if(srvfd < 0)
|
if(state == NULL || state->fd < 0)
|
||||||
return;
|
return;
|
||||||
close(srvfd);
|
close(state->fd);
|
||||||
srvfd = -1;
|
state->fd = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
srvconnect(void)
|
srvconnect(Ic *state)
|
||||||
{
|
{
|
||||||
struct sockaddr_un addr;
|
if(state->fd >= 0)
|
||||||
int fd;
|
|
||||||
|
|
||||||
if(srvfd >= 0)
|
|
||||||
return 0;
|
return 0;
|
||||||
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
state->fd = ipcconnect();
|
||||||
if(fd < 0)
|
if(state->fd < 0)
|
||||||
return -1;
|
return -1;
|
||||||
memset(&addr, 0, sizeof(addr));
|
|
||||||
addr.sun_family = AF_UNIX;
|
|
||||||
snprintf(addr.sun_path, sizeof(addr.sun_path), IPCPATH, getuid());
|
|
||||||
if(connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0){
|
|
||||||
close(fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
srvfd = fd;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
readresp(xcb_im_input_context_t *ic)
|
readresp(Ic *state, xcb_im_input_context_t *ic)
|
||||||
{
|
{
|
||||||
char buf[Ipcfieldmax+1];
|
char buf[Ipcfieldmax+1];
|
||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
|
|
||||||
if(ipcreadresp(srvfd, 0, buf, sizeof buf, NULL, 0, &resp) < 0)
|
if(ipcreadresp(state->fd, 0, buf, sizeof buf, NULL, 0, &resp) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
if(buf[0] != '\0')
|
if(resp.ncommit > 0)
|
||||||
commit(ic, buf, strlen(buf));
|
commit(ic, buf, resp.ncommit);
|
||||||
return resp.eaten;
|
return resp.eaten;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
kpress(xcb_im_input_context_t *ic, xcb_key_press_event_t *ev)
|
kpress(Ic *state, xcb_im_input_context_t *ic, xcb_key_press_event_t *ev)
|
||||||
{
|
{
|
||||||
unsigned char buf[Ipcreqsz];
|
unsigned char buf[Ipcreqsz];
|
||||||
uint32_t key, rune;
|
uint32_t key, rune;
|
||||||
@@ -166,10 +157,10 @@ kpress(xcb_im_input_context_t *ic, xcb_key_press_event_t *ev)
|
|||||||
key = rune;
|
key = rune;
|
||||||
ipcpackreq(buf, 0, ev->state, key);
|
ipcpackreq(buf, 0, ev->state, key);
|
||||||
eaten = 0;
|
eaten = 0;
|
||||||
if(srvconnect() == 0){
|
if(srvconnect(state) == 0){
|
||||||
if(ipcsend(srvfd, buf, sizeof buf) < 0 ||
|
if(ipcsend(state->fd, buf, sizeof buf) < 0 ||
|
||||||
(eaten = readresp(ic)) < 0){
|
(eaten = readresp(state, ic)) < 0){
|
||||||
srvclose();
|
srvclose(state);
|
||||||
eaten = 0;
|
eaten = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,15 +170,43 @@ kpress(xcb_im_input_context_t *ic, xcb_key_press_event_t *ev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
reset(xcb_im_input_context_t *ic)
|
reset(Ic *state, xcb_im_input_context_t *ic, int release)
|
||||||
{
|
{
|
||||||
unsigned char buf[Ipcreqsz];
|
unsigned char buf[Ipcreqsz];
|
||||||
|
|
||||||
ipcpackreq(buf, 0, 0, Kesc);
|
if(state == NULL || state->fd < 0)
|
||||||
if(srvconnect() < 0)
|
|
||||||
return;
|
return;
|
||||||
if(ipcsend(srvfd, buf, sizeof buf) < 0 || readresp(ic) < 0)
|
ipcpackreset(buf, 0);
|
||||||
srvclose();
|
if(ipcsend(state->fd, buf, sizeof buf) < 0 ||
|
||||||
|
readresp(state, ic) < 0)
|
||||||
|
release = 1;
|
||||||
|
if(release)
|
||||||
|
srvclose(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
icfree(void *p)
|
||||||
|
{
|
||||||
|
Ic *state;
|
||||||
|
|
||||||
|
state = p;
|
||||||
|
/* imdkit frees this data on context destruction and client loss. */
|
||||||
|
srvclose(state);
|
||||||
|
free(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
iccreate(xcb_im_input_context_t *ic)
|
||||||
|
{
|
||||||
|
Ic *state;
|
||||||
|
|
||||||
|
if(ic == NULL || xcb_im_input_context_get_data(ic) != NULL)
|
||||||
|
return;
|
||||||
|
state = calloc(1, sizeof *state);
|
||||||
|
if(state == NULL)
|
||||||
|
return;
|
||||||
|
state->fd = -1;
|
||||||
|
xcb_im_input_context_set_data(ic, state, icfree);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -195,20 +214,34 @@ callback(xcb_im_t *im, xcb_im_client_t *client, xcb_im_input_context_t *ic,
|
|||||||
const xcb_im_packet_header_fr_t *hdr, void *frame, void *arg, void *user)
|
const xcb_im_packet_header_fr_t *hdr, void *frame, void *arg, void *user)
|
||||||
{
|
{
|
||||||
xcb_key_press_event_t *ev;
|
xcb_key_press_event_t *ev;
|
||||||
|
Ic *state;
|
||||||
|
|
||||||
(void)im;
|
(void)im;
|
||||||
(void)client;
|
(void)client;
|
||||||
(void)frame;
|
(void)frame;
|
||||||
(void)user;
|
(void)user;
|
||||||
|
|
||||||
|
if(hdr->major_opcode == XCB_XIM_CREATE_IC){
|
||||||
|
iccreate(ic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(ic == NULL)
|
||||||
|
return;
|
||||||
|
state = xcb_im_input_context_get_data(ic);
|
||||||
|
if(state == NULL)
|
||||||
|
return;
|
||||||
switch(hdr->major_opcode){
|
switch(hdr->major_opcode){
|
||||||
case XCB_XIM_FORWARD_EVENT:
|
case XCB_XIM_FORWARD_EVENT:
|
||||||
ev = arg;
|
ev = arg;
|
||||||
if(ev != NULL && (ev->response_type & ~0x80) == XCB_KEY_PRESS)
|
if(ev != NULL && (ev->response_type & ~0x80) == XCB_KEY_PRESS)
|
||||||
kpress(ic, ev);
|
kpress(state, ic, ev);
|
||||||
|
break;
|
||||||
|
case XCB_XIM_RESET_IC:
|
||||||
|
/* ResetIC ends this context's ownership, like focus loss. */
|
||||||
|
reset(state, ic, 1);
|
||||||
break;
|
break;
|
||||||
case XCB_XIM_UNSET_IC_FOCUS:
|
case XCB_XIM_UNSET_IC_FOCUS:
|
||||||
reset(ic);
|
reset(state, ic, 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,7 +257,7 @@ ximinit(void)
|
|||||||
|
|
||||||
st.nStyles = 2;
|
st.nStyles = 2;
|
||||||
st.styles = styles;
|
st.styles = styles;
|
||||||
enc.nEncodings = 3;
|
enc.nEncodings = 1;
|
||||||
enc.encodings = encs;
|
enc.encodings = encs;
|
||||||
xcb_compound_text_init();
|
xcb_compound_text_init();
|
||||||
conn = xcb_connect(NULL, &scr);
|
conn = xcb_connect(NULL, &scr);
|
||||||
@@ -247,15 +280,19 @@ int
|
|||||||
main(void)
|
main(void)
|
||||||
{
|
{
|
||||||
xcb_generic_event_t *ev;
|
xcb_generic_event_t *ev;
|
||||||
|
uint8_t type;
|
||||||
|
|
||||||
ximinit();
|
ximinit();
|
||||||
for(;;){
|
for(;;){
|
||||||
ev = xcb_wait_for_event(conn);
|
ev = xcb_wait_for_event(conn);
|
||||||
if(ev == NULL)
|
if(ev == NULL)
|
||||||
break;
|
break;
|
||||||
xcb_im_filter_event(xim, ev);
|
type = ev->response_type & ~0x80;
|
||||||
|
if(type == XCB_MAPPING_NOTIFY)
|
||||||
|
kinit();
|
||||||
|
else
|
||||||
|
xcb_im_filter_event(xim, ev);
|
||||||
free(ev);
|
free(ev);
|
||||||
}
|
}
|
||||||
srvclose();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
41
xim/ximtext.c
Normal file
41
xim/ximtext.c
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <xcb-imdkit/encoding.h>
|
||||||
|
|
||||||
|
static char*
|
||||||
|
ximutf8compound(const char *s, size_t len, size_t *outlen)
|
||||||
|
{
|
||||||
|
static const char begin[] = "\033%G";
|
||||||
|
static const char end[] = "\033%@";
|
||||||
|
char *ct;
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
if(s == NULL || len > SIZE_MAX - (sizeof begin + sizeof end - 1))
|
||||||
|
return NULL;
|
||||||
|
n = len + sizeof begin + sizeof end - 2;
|
||||||
|
ct = malloc(n + 1);
|
||||||
|
if(ct == NULL)
|
||||||
|
return NULL;
|
||||||
|
memcpy(ct, begin, sizeof begin - 1);
|
||||||
|
memcpy(ct + sizeof begin - 1, s, len);
|
||||||
|
memcpy(ct + sizeof begin - 1 + len, end, sizeof end - 1);
|
||||||
|
ct[n] = '\0';
|
||||||
|
if(outlen != NULL)
|
||||||
|
*outlen = n;
|
||||||
|
return ct;
|
||||||
|
}
|
||||||
|
|
||||||
|
char*
|
||||||
|
ximcompound(const char *s, size_t len, size_t *outlen)
|
||||||
|
{
|
||||||
|
char *ct;
|
||||||
|
|
||||||
|
if(s == NULL)
|
||||||
|
return NULL;
|
||||||
|
ct = xcb_utf8_to_compound_text(s, len, outlen);
|
||||||
|
if(ct != NULL)
|
||||||
|
return ct;
|
||||||
|
/* X.Org Compound Text extension for characters in no legacy charset. */
|
||||||
|
return ximutf8compound(s, len, outlen);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user