Compare commits
5 Commits
master
...
b4fdc6e813
| Author | SHA1 | Date | |
|---|---|---|---|
| b4fdc6e813 | |||
| b22bc95ad5 | |||
| d16b17b4c8 | |||
| d5d3c23f3e | |||
| 032c60e193 |
78
.gitignore
vendored
78
.gitignore
vendored
@@ -1,21 +1,57 @@
|
|||||||
/*.o
|
strans
|
||||||
/strans
|
strans-xim
|
||||||
/imv2.c
|
|
||||||
/imv2.h
|
# Prerequisites
|
||||||
/vkv1.c
|
*.d
|
||||||
/vkv1.h
|
|
||||||
/tests/*.o
|
# Object files
|
||||||
/tests/unit_test
|
*.o
|
||||||
/tests/stress_test
|
*.ko
|
||||||
/tests/ibus_live_test
|
*.obj
|
||||||
/tests/ibus_client_smoke
|
*.elf
|
||||||
/tests/gtk_live_test
|
|
||||||
/tests/xim_live_test
|
# Linker output
|
||||||
/tests/ipc_live_test
|
*.ilk
|
||||||
/tests/daemon_collision_test
|
*.exp
|
||||||
/tests/daemon_failure_test
|
|
||||||
/tests/daemon_restart_test
|
# Precompiled Headers
|
||||||
/gtk/im-strans.so
|
*.gch
|
||||||
/bench/bench
|
*.pch
|
||||||
/bench/perf.data*
|
|
||||||
/ref-*/
|
# 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
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
|||||||
[submodule "cutest"]
|
|
||||||
path = cutest
|
|
||||||
url = ../cutest.git
|
|
||||||
50
Dockerfile
50
Dockerfile
@@ -1,50 +0,0 @@
|
|||||||
FROM archlinux:base@sha256:b0deabeb3d283da2c7f7dbf0eea051b7b2cd0554e0b737cc457fd21683bdcdd1
|
|
||||||
|
|
||||||
RUN pacman -Syu --noconfirm --needed \
|
|
||||||
cairo \
|
|
||||||
fontconfig \
|
|
||||||
diffutils \
|
|
||||||
gcc \
|
|
||||||
gtk3 \
|
|
||||||
ibus \
|
|
||||||
harfbuzz \
|
|
||||||
libx11 \
|
|
||||||
libxcb \
|
|
||||||
libxkbcommon \
|
|
||||||
make \
|
|
||||||
noto-fonts-emoji \
|
|
||||||
pango \
|
|
||||||
pkgconf \
|
|
||||||
plan9port \
|
|
||||||
python \
|
|
||||||
ttf-dejavu \
|
|
||||||
ttf-jigmo \
|
|
||||||
valgrind \
|
|
||||||
wayland \
|
|
||||||
which \
|
|
||||||
xorg-server-xvfb \
|
|
||||||
xcb-imdkit \
|
|
||||||
xcb-util \
|
|
||||||
dbus \
|
|
||||||
&& pacman -Scc --noconfirm
|
|
||||||
|
|
||||||
RUN export DEBUGINFOD_URLS=https://debuginfod.archlinux.org \
|
|
||||||
&& buildid="$(readelf -n /usr/lib/ld-linux-x86-64.so.2 | sed -n 's/.*Build ID: //p')" \
|
|
||||||
&& test -n "$buildid" \
|
|
||||||
&& debugdir="/usr/lib/debug/.build-id/$(printf '%.2s' "$buildid")" \
|
|
||||||
&& mkdir -p "$debugdir" \
|
|
||||||
&& install -m 0644 "$(debuginfod-find debuginfo /usr/lib/ld-linux-x86-64.so.2)" \
|
|
||||||
"$debugdir/${buildid#??}.debug" \
|
|
||||||
&& rm -rf /root/.cache/debuginfod_client
|
|
||||||
|
|
||||||
RUN dbus-uuidgen --ensure=/etc/machine-id
|
|
||||||
|
|
||||||
ENV PLAN9=/usr/lib/plan9
|
|
||||||
ENV PATH=$PATH:/usr/lib/plan9/bin
|
|
||||||
|
|
||||||
# 9c and 9l filter their own output through $egrep, which plan9port never
|
|
||||||
# assigns, so the pipeline dies and takes every warning and error with it.
|
|
||||||
# Both source $PLAN9/config, which the distribution does not ship.
|
|
||||||
RUN echo 'egrep="grep -E"' > $PLAN9/config
|
|
||||||
|
|
||||||
WORKDIR /src
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
Copyright (c) 2005,2006 Choe Hwanjin
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
3. Neither the name of the author nor the names of its contributors
|
|
||||||
may be used to endorse or promote products derived from this software
|
|
||||||
without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
@@ -1,339 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# License inventory
|
|
||||||
|
|
||||||
- `BSD-3-Clause-libhangul-hanja.txt` contains the terms for the Hanja data
|
|
||||||
in `map/hanja.src` and the symbol data in `map/mssymbol.src`, and for the
|
|
||||||
generated `map/hanja.dict` they share, derived from libhangul's
|
|
||||||
`data/hanja/hanja.txt` and `data/hanja/mssymbol.txt`.
|
|
||||||
- `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.
|
|
||||||
- `Unicode-3.0.txt` contains the Unicode License v3, the terms for the
|
|
||||||
emoji names and keywords in `map/emoji.src` and generated
|
|
||||||
`map/emoji.dict`, derived from Unicode's `emoji-test.txt` and CLDR's
|
|
||||||
annotations.
|
|
||||||
- The two protocol descriptions in `proto/` are vendored verbatim from
|
|
||||||
wlroots `012ca825` and carry their own MIT terms inside the XML.
|
|
||||||
|
|
||||||
No repository-wide license has been declared for original strans source.
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
UNICODE LICENSE V3
|
|
||||||
|
|
||||||
COPYRIGHT AND PERMISSION NOTICE
|
|
||||||
|
|
||||||
Copyright © 1991-2026 Unicode, Inc.
|
|
||||||
|
|
||||||
NOTICE TO USER: Carefully read the following legal agreement. BY
|
|
||||||
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
|
|
||||||
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
|
|
||||||
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
|
|
||||||
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
copy of data files and any associated documentation (the "Data Files") or
|
|
||||||
software and any associated documentation (the "Software") to deal in the
|
|
||||||
Data Files or Software without restriction, including without limitation
|
|
||||||
the rights to use, copy, modify, merge, publish, distribute, and/or sell
|
|
||||||
copies of the Data Files or Software, and to permit persons to whom the
|
|
||||||
Data Files or Software are furnished to do so, provided that either (a)
|
|
||||||
this copyright and permission notice appear with all copies of the Data
|
|
||||||
Files or Software, or (b) this copyright and permission notice appear in
|
|
||||||
associated Documentation.
|
|
||||||
|
|
||||||
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
||||||
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
|
||||||
THIRD PARTY RIGHTS.
|
|
||||||
|
|
||||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
|
|
||||||
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
|
|
||||||
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
|
||||||
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
|
||||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
|
|
||||||
FILES OR SOFTWARE.
|
|
||||||
|
|
||||||
Except as contained in this notice, the name of a copyright holder shall
|
|
||||||
not be used in advertising or otherwise to promote the sale, use or other
|
|
||||||
dealings in these Data Files or Software without prior written
|
|
||||||
authorization of the copyright holder.
|
|
||||||
115
Makefile
115
Makefile
@@ -1,118 +1,27 @@
|
|||||||
CC = 9c
|
CC = 9c
|
||||||
LD = 9l
|
LD = 9l
|
||||||
PKG_CONFIG ?= pkg-config
|
CFLAGS = -Wall -Wextra -O2 -g
|
||||||
SCANNER ?= wayland-scanner
|
|
||||||
CFLAGS ?= -O2 -g
|
|
||||||
WARN_CFLAGS = -Wall -Wextra
|
|
||||||
IBUS_CFLAGS = $(shell $(PKG_CONFIG) --cflags dbus-1 xkbcommon)
|
|
||||||
IBUS_LIBS = $(shell $(PKG_CONFIG) --libs dbus-1 xkbcommon)
|
|
||||||
XIM_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
|
||||||
XIM_LIBS = $(shell $(PKG_CONFIG) --libs xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
|
||||||
TEXT_CFLAGS = $(shell $(PKG_CONFIG) --cflags pangocairo cairo fontconfig)
|
|
||||||
TEXT_LIBS = $(shell $(PKG_CONFIG) --libs pangocairo cairo fontconfig)
|
|
||||||
POPUP_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-aux xcb-randr)
|
|
||||||
POPUP_LIBS = $(shell $(PKG_CONFIG) --libs xcb-aux xcb-randr)
|
|
||||||
WL_CFLAGS = $(shell $(PKG_CONFIG) --cflags wayland-client xkbcommon)
|
|
||||||
WL_LIBS = $(shell $(PKG_CONFIG) --libs wayland-client xkbcommon)
|
|
||||||
PROJECT_CPPFLAGS =
|
|
||||||
PROJECT_LDLIBS = -lthread -lbio -lxcb $(IBUS_LIBS) $(TEXT_LIBS) \
|
|
||||||
$(XIM_LIBS) $(POPUP_LIBS) $(WL_LIBS)
|
|
||||||
PROG = strans
|
PROG = strans
|
||||||
DOCKER_IMAGE = strans-build
|
|
||||||
DOCKER_RUN = docker run --rm --user "$$(id -u):$$(id -g)" \
|
|
||||||
-v "$(CURDIR):/src" $(DOCKER_IMAGE)
|
|
||||||
|
|
||||||
SRCS = compose.c dict.c font.c ibus.c ipc.c ko.c main.c popup_layout.c \
|
SRCS = $(wildcard *.c)
|
||||||
srv.c str.c strans.c trie.c vi.c win.c wl.c xim.c
|
|
||||||
OBJS = $(SRCS:.c=.o)
|
OBJS = $(SRCS:.c=.o)
|
||||||
# wayland-scanner writes these from the XML in proto/; only the XML is tracked.
|
|
||||||
PROTO = imv2.c imv2.h vkv1.c vkv1.h
|
|
||||||
PROTOOBJ = imv2.o vkv1.o
|
|
||||||
|
|
||||||
all: $(PROG) gtk
|
all: $(PROG) xim bench
|
||||||
|
|
||||||
$(PROG): $(OBJS) $(PROTOOBJ)
|
$(PROG): $(OBJS)
|
||||||
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(PROTOOBJ) $(PROJECT_LDLIBS) $(LDLIBS)
|
$(LD) -o $@ $(OBJS) -lthread -lString -lbio -lxcb -lm
|
||||||
|
|
||||||
$(OBJS): dat.h fn.h ipc.h
|
$(OBJS): dat.h fn.h ipc.h
|
||||||
wl.o: imv2.h vkv1.h
|
|
||||||
compose.o ibus.o: PROJECT_CPPFLAGS += $(IBUS_CFLAGS)
|
|
||||||
font.o: PROJECT_CPPFLAGS += $(TEXT_CFLAGS)
|
|
||||||
win.o: PROJECT_CPPFLAGS += $(POPUP_CFLAGS)
|
|
||||||
wl.o $(PROTOOBJ): PROJECT_CPPFLAGS += $(WL_CFLAGS)
|
|
||||||
xim.o: PROJECT_CPPFLAGS += $(XIM_CFLAGS)
|
|
||||||
|
|
||||||
%.o: %.c
|
|
||||||
$(CC) $(CPPFLAGS) $(PROJECT_CPPFLAGS) $(WARN_CFLAGS) $(CFLAGS) -c -o $@ $<
|
|
||||||
|
|
||||||
imv2.h: proto/input-method-unstable-v2.xml
|
|
||||||
$(SCANNER) client-header $< $@
|
|
||||||
imv2.c: proto/input-method-unstable-v2.xml
|
|
||||||
$(SCANNER) private-code $< $@
|
|
||||||
vkv1.h: proto/virtual-keyboard-unstable-v1.xml
|
|
||||||
$(SCANNER) client-header $< $@
|
|
||||||
vkv1.c: proto/virtual-keyboard-unstable-v1.xml
|
|
||||||
$(SCANNER) private-code $< $@
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJS) $(PROG) $(PROTO) $(PROTOOBJ)
|
rm -f $(OBJS) $(PROG)
|
||||||
$(MAKE) -C tests clean
|
make -C xim/ clean
|
||||||
$(MAKE) -C gtk/ clean
|
make -C bench/ clean
|
||||||
$(MAKE) -C bench/ clean
|
|
||||||
|
|
||||||
gtk:
|
xim:
|
||||||
$(MAKE) -C gtk/
|
make -C xim/
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
$(MAKE) -C bench/
|
make -C bench/
|
||||||
|
|
||||||
docker-image: Dockerfile
|
.PHONY: all clean xim bench
|
||||||
docker build -t $(DOCKER_IMAGE) - < Dockerfile
|
|
||||||
|
|
||||||
docker-build:
|
|
||||||
$(DOCKER_RUN) sh -c 'make clean && make all'
|
|
||||||
|
|
||||||
docker-check:
|
|
||||||
$(DOCKER_RUN) sh -c 'make clean && \
|
|
||||||
make check check-live check-stress UNITARGS="$(UNITARGS)"'
|
|
||||||
|
|
||||||
docker-check-live:
|
|
||||||
$(DOCKER_RUN) sh -c 'make clean && make check-live'
|
|
||||||
|
|
||||||
docker-check-stress:
|
|
||||||
$(DOCKER_RUN) sh -c 'make clean && make check-stress'
|
|
||||||
|
|
||||||
docker-bench:
|
|
||||||
$(DOCKER_RUN) sh -c 'make clean && make bench'
|
|
||||||
|
|
||||||
docker-valgrind:
|
|
||||||
$(DOCKER_RUN) sh -c 'make clean && make -C tests unit_test && \
|
|
||||||
cd tests && valgrind -q --error-exitcode=1 ./unit_test $(UNITARGS)'
|
|
||||||
|
|
||||||
docker: docker-image
|
|
||||||
$(MAKE) docker-build
|
|
||||||
|
|
||||||
check: verify-map
|
|
||||||
$(MAKE) -C tests check UNITARGS="$(UNITARGS)"
|
|
||||||
|
|
||||||
check-live: $(PROG) gtk
|
|
||||||
$(MAKE) -C tests check-live
|
|
||||||
|
|
||||||
check-stress: $(PROG)
|
|
||||||
$(MAKE) -C tests check-stress
|
|
||||||
|
|
||||||
test: check
|
|
||||||
|
|
||||||
verify-map:
|
|
||||||
test "$$(cut -f1 map/hira.map)" = "$$(cut -f1 map/kata.map)"
|
|
||||||
python3 map/mktelex.py | cmp - map/telex.map
|
|
||||||
python3 map/mkemoji | cmp - map/emoji.dict
|
|
||||||
python3 map/mkhanja | cmp - map/hanja.dict
|
|
||||||
python3 map/verifymap.py map/*.map map/*.dict
|
|
||||||
python3 tests/mkemoji_test.py
|
|
||||||
python3 tests/mkhanja_test.py
|
|
||||||
python3 tests/skk2ktrans_test.py
|
|
||||||
|
|
||||||
.PHONY: all check check-live check-stress test verify-map clean gtk bench \
|
|
||||||
docker docker-image docker-build docker-check docker-check-live \
|
|
||||||
docker-check-stress docker-bench docker-valgrind
|
|
||||||
|
|||||||
218
README.md
218
README.md
@@ -1,194 +1,70 @@
|
|||||||
# strans
|
# strans
|
||||||
|
|
||||||
strans is a small, single-user input method for Korean, Japanese, English,
|
An input method daemon for CJK text entry on X11.
|
||||||
emoji and symbols, with Vietnamese Telex as a compatibility mode. One
|
|
||||||
engine serves four frontends: `zwp_input_method_v2` on Wayland — sway and
|
|
||||||
any other compositor that offers it — IBus, which GTK 4 and Qt use, XIM,
|
|
||||||
and a GTK 3 module.
|
|
||||||
|
|
||||||
## Modes
|
Inspired by 9front's ktrans. Threads communicate via CSP channels.
|
||||||
|
|
||||||
| Key | Mode |
|
## Dependencies
|
||||||
| --- | --- |
|
|
||||||
| `Ctrl+S` | Korean Hangul (2-beolsik) |
|
|
||||||
| `Ctrl+N` | Japanese Hiragana, converting to Kanji |
|
|
||||||
| `Ctrl+K` | Japanese Katakana |
|
|
||||||
| `Ctrl+T` | English |
|
|
||||||
| `Ctrl+V` | Vietnamese Telex |
|
|
||||||
| `Ctrl+E` | Emoji and symbol search |
|
|
||||||
| `Ctrl+H` | One-shot Hanja and symbol search |
|
|
||||||
|
|
||||||
Only a plain `Ctrl` chord is a strans key: with `Shift`, `Alt` or `Super`
|
- plan9port
|
||||||
held it commits what is pending and goes to the application, so
|
- gtk+-3.0 (optional, for GTK IM module)
|
||||||
`Ctrl+Shift+V` still pastes. `Backspace`, `Enter`, `Tab`, `Esc` and the
|
|
||||||
arrow and page keys do the same under `Ctrl`, so `Ctrl+Backspace` still
|
|
||||||
deletes a word. The mode switched to — `한`, `あ`, `ア`, `ă`, `A` — shows
|
|
||||||
in the popup until the next key. A Korean keyboard's 한/영 and 한자 keys
|
|
||||||
stand for `Ctrl+S`/`Ctrl+T` and `Ctrl+H`; a Japanese one's 半角/全角,
|
|
||||||
ひらがな/カタカナ, 変換 and 無変換 for the kana modes, `Space` and English.
|
|
||||||
|
|
||||||
## Composing
|
## Build
|
||||||
|
|
||||||
Japanese composes a whole reading and then offers its Kanji with none
|
make
|
||||||
chosen. `Space` and `Tab` step through the candidates and wrap (`Shift`
|
cd xim && make # XIM adapter
|
||||||
reverses), the reading in Katakana last, so a word the dictionary lacks
|
cd gtk && make docker && make install # GTK IM module
|
||||||
converts with one `Space`; `Up`/`Down` and `PageUp`/`PageDown` move without
|
|
||||||
wrapping. A chosen candidate takes the reading's place in the preedit, so
|
|
||||||
it stands where it will land, and `Enter` commits it, or the reading, as
|
|
||||||
`0` and typing on always do. Once a candidate is chosen `1`-`9` take that
|
|
||||||
row of the page shown; before that the digits type. `Backspace` deletes
|
|
||||||
the last kana shown, and romaji that made no kana stays as typed until it
|
|
||||||
is mended; `Esc` cancels the reading, and on a chosen candidate both go
|
|
||||||
back to it. The dictionary keys verbs and adjectives by stem and
|
|
||||||
okurigana, so `kaku` offers かく's readings and then 書く and its kin —
|
|
||||||
typing the okurigana with `Shift`, as SKK does, `kaKu`, asks for that
|
|
||||||
split first. Katakana mode does not convert, so there `Space` and `Tab`
|
|
||||||
commit the reading and go on to the application.
|
|
||||||
|
|
||||||
Korean composes one syllable at a time. `Enter`, `Tab`, `Esc` and any key
|
|
||||||
that is not a jamo commit the syllable and go on to the application, so
|
|
||||||
`Esc` still leaves insert mode. Two lone consonants join into the compound
|
|
||||||
final they make (rt → ㄳ), and a vowel typed before its consonant is
|
|
||||||
reordered under it (kr → 가), as in libhangul.
|
|
||||||
|
|
||||||
A search takes the keys until `Enter`, `Space` or `1`-`9` picks a result or
|
|
||||||
`Esc` cancels, then returns to the previous mode. Emoji matches the typed
|
|
||||||
keys, and what they spell in the current mode, against a prefix of every
|
|
||||||
emoji's CLDR name and keywords in English, Korean and Japanese, and against
|
|
||||||
ASCII aliases such as `->` and `<=`. `Ctrl+H` takes the syllable being
|
|
||||||
composed as its query and composes on from it, converting a word as well as
|
|
||||||
a syllable — 한자 gives 漢字, 대한민국 gives 大韓民國 — and `Esc` gives the
|
|
||||||
syllable back. A word is committed a syllable at a time, so the reading
|
|
||||||
also reaches back into the text the application already holds, as far as
|
|
||||||
the dictionary knows the whole of it: type 한자 and then `Ctrl+H` and the
|
|
||||||
query is 한자, not 자, and picking 漢字 takes the 한 back. `Backspace`
|
|
||||||
gives that reach back before it deletes what was typed, so 상 written
|
|
||||||
with 태 typed answers 狀態, and one `Backspace` answers 態 for the
|
|
||||||
syllable alone. A reading is also the start of the longer words it
|
|
||||||
begins, whose conversions follow its own, so 대한 answers 大寒 first and
|
|
||||||
大韓民國 further down. A lone consonant is a reading too, and answers
|
|
||||||
with the symbol table a Korean keyboard's 한자 key has always offered:
|
|
||||||
ㅁ gives ※ ○ △ ㈜, ㄴ the brackets 「」『』, ㄹ the units ℃ ㎏ ℓ, ㅇ the
|
|
||||||
circled numbers ①②③.
|
|
||||||
|
|
||||||
Reaching back needs the application to hand over the text around its
|
|
||||||
cursor and to take some of it away again. The Wayland input method, IBus
|
|
||||||
and the GTK 3 module all can; XIM has no such request, so there the
|
|
||||||
reading is what is still being composed and nothing else, and an IBus
|
|
||||||
client that reads a key's effects only after the call returns is not
|
|
||||||
asked for its text either, since a deletion could not be ordered before
|
|
||||||
the commit.
|
|
||||||
|
|
||||||
Dead keys and Compose sequences are composed by strans itself for the
|
|
||||||
Wayland, XIM and IBus frontends, from `XCOMPOSEFILE` or the locale; the
|
|
||||||
GTK 3 module leaves them to GtkIMContextSimple.
|
|
||||||
|
|
||||||
## Preedit and candidates
|
|
||||||
|
|
||||||
| Frontend | Preedit | Candidates | Reaches back |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| Wayland input method | inline in the client | popup | yes |
|
|
||||||
| GTK 3 and IBus | inline in the client | popup | yes |
|
|
||||||
| XIM PreeditCallbacks | inline through XIM callbacks | popup | no |
|
|
||||||
| XIM PreeditPosition, PreeditNothing | popup | popup | no |
|
|
||||||
|
|
||||||
On Wayland the popup is a surface the compositor places at the text cursor,
|
|
||||||
flipping it above the line when there is no room below. Elsewhere it is an
|
|
||||||
X11 window, placed from the XIM spot or from the GTK caret, and XIM text
|
|
||||||
travels as `COMPOUND_TEXT`. Either way `GDK_SCALE` sizes it for HiDPI
|
|
||||||
displays.
|
|
||||||
|
|
||||||
## Build and test
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git submodule update --init
|
|
||||||
make docker-image
|
|
||||||
make docker-build
|
|
||||||
```
|
|
||||||
|
|
||||||
That leaves `strans`, the daemon, and `gtk/im-strans.so`, the GTK 3 module.
|
|
||||||
Tests come in three tiers:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
make check # generated-map validation and unit tests
|
|
||||||
make check-live # one IBus, GTK, XIM and IPC daemon smoke each
|
|
||||||
make check-stress # randomized, capacity, collision, failure and restart
|
|
||||||
```
|
|
||||||
|
|
||||||
`make docker-check` runs all three in the container, and
|
|
||||||
`make docker-valgrind` the unit suite under Valgrind; rerun
|
|
||||||
`make docker-image` after changing `Dockerfile`. `UNITARGS=hangul` filters
|
|
||||||
the unit suite alone, not the other tiers.
|
|
||||||
|
|
||||||
A native build wants a C toolchain, Make, pkg-config and Plan 9 port, plus
|
|
||||||
development files for D-Bus, XCB and xcb-imdkit, Wayland and
|
|
||||||
`wayland-scanner`, xkbcommon, Pango and Cairo, and GTK 3; the tests also
|
|
||||||
want Xvfb, Python 3 and fonts covering Latin, CJK and emoji.
|
|
||||||
[`Dockerfile`](Dockerfile) names the exact packages.
|
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
```sh
|
./strans map font &
|
||||||
./run.sh # restart the daemon in the background
|
|
||||||
./strans map # or run it in the foreground
|
|
||||||
```
|
|
||||||
|
|
||||||
`./strans DIR` reads `hira.map`, `kata.map`, `telex.map`, `kanji.dict`,
|
For XIM apps:
|
||||||
`emoji.dict` and `hanja.dict` from `DIR` at runtime; nothing but the GTK
|
|
||||||
module is installed. A service supervisor wants the session's
|
|
||||||
`XDG_RUNTIME_DIR` and its `WAYLAND_DISPLAY` or `DISPLAY`: the GTK module
|
|
||||||
finds the daemon at `$XDG_RUNTIME_DIR/strans.sock`, else
|
|
||||||
`/tmp/strans.UID`, and IBus clients through the address file libibus
|
|
||||||
expects under `~/.config/ibus/bus/`, which strans writes as `ibus-daemon`
|
|
||||||
would.
|
|
||||||
|
|
||||||
strans shows one popup per session, so it picks a frontend at startup: a
|
./xim/strans-xim &
|
||||||
compositor offering `zwp_input_method_v2` gets the Wayland frontend, and
|
XMODIFIERS=@im=strans xterm
|
||||||
then neither XIM nor the X11 popup runs; every other session gets XIM. The
|
|
||||||
IBus endpoint and the GTK 3 module are served in both.
|
|
||||||
|
|
||||||
On Wayland, then, set none of the variables below. GTK, Qt and Firefox
|
For GTK apps:
|
||||||
speak text-input-v3 themselves — GTK 4 binds it with `GTK_IM_MODULE` unset
|
|
||||||
— and setting these is what pushes them off the path that works: such a
|
|
||||||
client still composes inline but shows no candidates, because the popup
|
|
||||||
belongs to the Wayland frontend. Chromium is beyond help either way, since
|
|
||||||
it asks for `text-input-v1`, which wlroots does not implement.
|
|
||||||
|
|
||||||
```sh
|
GTK_IM_MODULE=strans gedit
|
||||||
export XMODIFIERS=@im=strans # XIM
|
|
||||||
doas make -C gtk install # GTK 3 module
|
|
||||||
export GTK_IM_MODULE=strans
|
|
||||||
GLFW_IM_MODULE=ibus kitty # IBus client example
|
|
||||||
```
|
|
||||||
|
|
||||||
strans provides its own IBus endpoint, so `ibus-daemon` and fcitx are not
|
## Usage
|
||||||
needed. Building the GTK module wants GTK development files; installing
|
|
||||||
one built elsewhere does not. The install target takes `GTK_MODULE_DIR`
|
|
||||||
when set, else asks GTK where its modules live; `doas make -C gtk uninstall`
|
|
||||||
removes it. With neither `WAYLAND_DISPLAY` nor `DISPLAY` the daemon still
|
|
||||||
serves IBus and its socket, but nothing draws a popup.
|
|
||||||
|
|
||||||
## Dictionary data
|
Switch input modes with Ctrl + key:
|
||||||
|
|
||||||
After changing an input source, regenerate and verify:
|
N Hiragana
|
||||||
|
K Katakana
|
||||||
|
S Hangul
|
||||||
|
T English
|
||||||
|
V Vietnamese (Telex)
|
||||||
|
E Emoji
|
||||||
|
|
||||||
```sh
|
Type romanized input. Select candidates with 1-9 or arrow keys.
|
||||||
python3 map/mktelex.py >map/telex.map
|
Tab or Enter to commit.
|
||||||
map/mkemoji >map/emoji.dict
|
|
||||||
map/mkhanja >map/hanja.dict
|
|
||||||
make verify-map
|
|
||||||
```
|
|
||||||
|
|
||||||
[`map/README`](map/README) says where the emoji, Hanja and Japanese data
|
## Architecture
|
||||||
comes from.
|
|
||||||
|
|
||||||
## Benchmark
|
Four threads communicate via CSP channels:
|
||||||
|
|
||||||
`make bench` builds `bench/bench`. With the daemon stopped, `./bench.sh`
|
- [imthread](strans.c#L271): keystroke processing, transliteration
|
||||||
starts one, warms it up and records the workload with Linux `perf` into
|
- [dictthread](dict.c#L38): dictionary lookup
|
||||||
`bench/perf.data`.
|
- [drawthread](win.c#L133): preedit window rendering
|
||||||
|
- [srvthread](srv.c#L42): IPC via unix socket
|
||||||
|
|
||||||
## Licensing
|
Adapters (strans-xim, im-strans.so) bridge X11/GTK events.
|
||||||
|
|
||||||
Third-party notices are in [`LICENSES`](LICENSES). The repository declares
|
## Files
|
||||||
no license for the strans source as a whole.
|
|
||||||
|
strans.c input method engine
|
||||||
|
dict.c dictionary queries
|
||||||
|
win.c xcb window management
|
||||||
|
font.c truetype rendering (stb_truetype)
|
||||||
|
map/ transliteration tables
|
||||||
|
font/ bundled CJK fonts
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- https://git.9front.org/plan9front/plan9front/HEAD/info.html
|
||||||
|
- https://np.mkv.li/kor/9front-ktrans-%ED%95%9C%EA%B8%80-%EB%98%91%EB%B0%94%EB%A1%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0/
|
||||||
|
- https://en.wikipedia.org/wiki/Telex_(input_method)
|
||||||
|
- https://gist.github.com/hieuthi/0f5adb7d3f79e7fb67e0e499004bf558
|
||||||
|
|||||||
82
bench.sh
82
bench.sh
@@ -1,71 +1,23 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
set -eu
|
pkill strans
|
||||||
|
pkill strans-xim
|
||||||
cd "$(dirname "$0")" || exit 1
|
sleep 1
|
||||||
|
|
||||||
if test "$#" -ne 0; then
|
./strans map font &
|
||||||
echo "usage: bench.sh" >&2
|
sleep 1
|
||||||
exit 1
|
|
||||||
fi
|
# warm up glyph cache
|
||||||
|
./bench/bench bench/bench.keys 100
|
||||||
if ! test -x ./bench/bench; then
|
echo "cache warmed up"
|
||||||
echo "bench.sh: ./bench/bench is not executable; run make bench first" >&2
|
|
||||||
exit 1
|
STRANS_PID=$(pgrep -x strans)
|
||||||
fi
|
perf record -g -o bench/perf.data -p "$STRANS_PID" &
|
||||||
|
PERF_PID=$!
|
||||||
strans_pid=
|
|
||||||
perf_pid=
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
status=$?
|
|
||||||
trap - 0 1 2 15
|
|
||||||
if test -n "$perf_pid"; then
|
|
||||||
kill -INT "$perf_pid" 2>/dev/null || :
|
|
||||||
wait "$perf_pid" 2>/dev/null || :
|
|
||||||
fi
|
|
||||||
if test -n "$strans_pid"; then
|
|
||||||
kill "$strans_pid" 2>/dev/null || :
|
|
||||||
wait "$strans_pid" 2>/dev/null || :
|
|
||||||
fi
|
|
||||||
exit "$status"
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup 0
|
|
||||||
trap 'exit 129' 1
|
|
||||||
trap 'exit 130' 2
|
|
||||||
trap 'exit 143' 15
|
|
||||||
|
|
||||||
./strans map &
|
|
||||||
strans_pid=$!
|
|
||||||
|
|
||||||
# The daemon is ready once the benchmark client can complete a round.
|
|
||||||
attempt=0
|
|
||||||
until ./bench/bench bench/bench.keys 1 >/dev/null 2>&1; do
|
|
||||||
if ! kill -0 "$strans_pid" 2>/dev/null; then
|
|
||||||
echo "bench.sh: daemon exited before accepting IPC clients" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
attempt=$((attempt + 1))
|
|
||||||
if test "$attempt" -ge 50; then
|
|
||||||
echo "bench.sh: daemon did not accept IPC clients within 10 seconds" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
sleep 0.2
|
|
||||||
done
|
|
||||||
|
|
||||||
# warm up the renderer
|
|
||||||
./bench/bench bench/bench.keys 100
|
|
||||||
echo "renderer warmed up"
|
|
||||||
|
|
||||||
perf record -g -o bench/perf.data -p "$strans_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
|
||||||
perf_pid=
|
pkill strans
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
CC = cc
|
CC = cc
|
||||||
CFLAGS ?= -O2
|
CFLAGS = -Wall -O2
|
||||||
|
|
||||||
bench: main.c ../ipc.c ../ipc.h
|
bench: main.c
|
||||||
$(CC) $(CPPFLAGS) -I.. -Wall $(CFLAGS) $(LDFLAGS) -o $@ \
|
$(CC) $(CFLAGS) -o $@ main.c
|
||||||
main.c ../ipc.c $(LDLIBS)
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f bench
|
rm -f bench
|
||||||
|
|||||||
BIN
bench/bench
Executable file
BIN
bench/bench
Executable file
Binary file not shown.
99
bench/main.c
99
bench/main.c
@@ -1,13 +1,17 @@
|
|||||||
#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 "ipc.h"
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
Kback = 0xf008,
|
||||||
|
Kret = 0xf00d,
|
||||||
|
Kesc = 0xf01b,
|
||||||
|
Mctrl = 1<<2,
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct Key Key;
|
typedef struct Key Key;
|
||||||
struct Key {
|
struct Key {
|
||||||
@@ -16,7 +20,7 @@ struct Key {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static Key *keys;
|
static Key *keys;
|
||||||
static size_t nkeys;
|
static int nkeys;
|
||||||
static int fd;
|
static int fd;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -29,19 +33,13 @@ die(char *msg)
|
|||||||
static void
|
static void
|
||||||
addkey(int k, int mod)
|
addkey(int k, int mod)
|
||||||
{
|
{
|
||||||
static size_t cap;
|
static int 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;
|
||||||
p = realloc(keys, cap * sizeof(Key));
|
keys = realloc(keys, cap * sizeof(Key));
|
||||||
if(!p)
|
if(!keys)
|
||||||
die("realloc");
|
die("realloc");
|
||||||
keys = p;
|
|
||||||
}
|
}
|
||||||
keys[nkeys].k = k;
|
keys[nkeys].k = k;
|
||||||
keys[nkeys].mod = mod;
|
keys[nkeys].mod = mod;
|
||||||
@@ -83,55 +81,49 @@ 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)
|
|
||||||
goto Bad;
|
|
||||||
return n;
|
|
||||||
Bad:
|
|
||||||
fprintf(stderr, "invalid iteration count: %s\n", s);
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dial(void)
|
dial(void)
|
||||||
{
|
{
|
||||||
fd = ipcconnect();
|
struct sockaddr_un addr;
|
||||||
|
|
||||||
|
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),
|
||||||
|
"/tmp/strans.%d", getuid());
|
||||||
|
if(connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
|
||||||
die("connect");
|
die("connect");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sendkey(int key, int mod)
|
sendkey(int key, int mod)
|
||||||
{
|
{
|
||||||
unsigned char req[Ipcreqsz];
|
unsigned char req[4];
|
||||||
|
|
||||||
ipcpackreq(req, 0, mod, key);
|
req[0] = 0;
|
||||||
if(ipcsend(fd, req, sizeof req) < 0)
|
req[1] = mod;
|
||||||
|
req[2] = key & 0xff;
|
||||||
|
req[3] = (key >> 8) & 0xff;
|
||||||
|
if(write(fd, req, 4) != 4)
|
||||||
die("write");
|
die("write");
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
readresp(void)
|
readresp(void)
|
||||||
{
|
{
|
||||||
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
unsigned char buf[256];
|
||||||
Ipcresp resp;
|
int n;
|
||||||
|
|
||||||
return ipcreadresp(fd, 0, commit, preedit, &resp);
|
if(read(fd, buf, 2) != 2)
|
||||||
|
return -1;
|
||||||
|
n = buf[1];
|
||||||
|
if(n > 0 && read(fd, buf + 2, n) != n)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static double
|
static double
|
||||||
@@ -146,38 +138,31 @@ now(void)
|
|||||||
int
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
size_t j;
|
int i, j, niter;
|
||||||
uint64_t i, niter, total;
|
|
||||||
double t0, t1, dt;
|
double t0, t1, dt;
|
||||||
|
|
||||||
if(argc != 2 && argc != 3){
|
if(argc < 2){
|
||||||
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 == 3 ? iterations(argv[2]) : 1000;
|
niter = argc > 2 ? atoi(argv[2]) : 1000;
|
||||||
if(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 %" PRIu64 " key %zu\n", i, j);
|
fprintf(stderr, "failed at iter %d key %d\n", i, j);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t1 = now();
|
t1 = now();
|
||||||
dt = t1 - t0;
|
dt = t1 - t0;
|
||||||
printf("%" PRIu64 " iters x %zu keys = %" PRIu64 " keys\n",
|
printf("%d iters x %d keys = %d keys\n", niter, nkeys, niter * nkeys);
|
||||||
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 / total, dt * 1e6 / niter);
|
dt * 1000, dt * 1e6 / (niter * nkeys), dt * 1e6 / niter);
|
||||||
close(fd);
|
close(fd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
bench/perf.data
Normal file
BIN
bench/perf.data
Normal file
Binary file not shown.
BIN
bench/perf.data.old
Normal file
BIN
bench/perf.data.old
Normal file
Binary file not shown.
82
compose.c
82
compose.c
@@ -1,82 +0,0 @@
|
|||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
|
|
||||||
#include <locale.h>
|
|
||||||
#include <xkbcommon/xkbcommon.h>
|
|
||||||
#include <xkbcommon/xkbcommon-compose.h>
|
|
||||||
|
|
||||||
/*
|
|
||||||
* One state per frontend, all made before the procs are: xkbcommon locks
|
|
||||||
* nothing, and even its table refcount is a plain increment. cowner is
|
|
||||||
* the context whose sequence a state holds.
|
|
||||||
*/
|
|
||||||
static struct xkb_compose_state *cstate[Ncompose];
|
|
||||||
static void *cowner[Ncompose];
|
|
||||||
|
|
||||||
/* Dead keys and Compose: no frontend's client does them for us. */
|
|
||||||
void
|
|
||||||
composeinit(void)
|
|
||||||
{
|
|
||||||
struct xkb_context *ctx;
|
|
||||||
struct xkb_compose_table *table;
|
|
||||||
char *locale;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
locale = setlocale(LC_CTYPE, "");
|
|
||||||
ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
|
||||||
if(ctx == nil)
|
|
||||||
return;
|
|
||||||
table = xkb_compose_table_new_from_locale(ctx,
|
|
||||||
locale != nil ? locale : "C", XKB_COMPOSE_COMPILE_NO_FLAGS);
|
|
||||||
if(table != nil)
|
|
||||||
for(i = 0; i < Ncompose; i++)
|
|
||||||
cstate[i] = xkb_compose_state_new(table,
|
|
||||||
XKB_COMPOSE_STATE_NO_FLAGS);
|
|
||||||
xkb_compose_table_unref(table);
|
|
||||||
xkb_context_unref(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The context is going, and the sequence it left half typed with it. */
|
|
||||||
void
|
|
||||||
composedrop(int who, void *owner)
|
|
||||||
{
|
|
||||||
if(cstate[who] == nil || owner != cowner[who])
|
|
||||||
return;
|
|
||||||
xkb_compose_state_reset(cstate[who]);
|
|
||||||
cowner[who] = nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Feeds sym to the Compose table. Returns 1 while a sequence is
|
|
||||||
* unfinished: the key belongs to the sequence, not to the engine. A
|
|
||||||
* finished sequence leaves its text in buf, which the caller commits
|
|
||||||
* after whatever the engine had pending. A sequence belongs to the
|
|
||||||
* context that started it: a key from another one starts over.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
composekey(int who, void *owner, u32int sym, char *buf, int n)
|
|
||||||
{
|
|
||||||
struct xkb_compose_state *cs;
|
|
||||||
|
|
||||||
buf[0] = '\0';
|
|
||||||
cs = cstate[who];
|
|
||||||
if(cs == nil)
|
|
||||||
return 0;
|
|
||||||
if(owner != cowner[who]){
|
|
||||||
xkb_compose_state_reset(cs);
|
|
||||||
cowner[who] = owner;
|
|
||||||
}
|
|
||||||
if(xkb_compose_state_feed(cs, sym) != XKB_COMPOSE_FEED_ACCEPTED)
|
|
||||||
return 0;
|
|
||||||
switch(xkb_compose_state_get_status(cs)){
|
|
||||||
case XKB_COMPOSE_COMPOSING:
|
|
||||||
case XKB_COMPOSE_CANCELLED:
|
|
||||||
return 1;
|
|
||||||
case XKB_COMPOSE_COMPOSED:
|
|
||||||
xkb_compose_state_get_utf8(cs, buf, n);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
1
cutest
1
cutest
Submodule cutest deleted from 7894ba7a26
182
dat.h
182
dat.h
@@ -7,113 +7,87 @@
|
|||||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||||
|
|
||||||
/*
|
|
||||||
* plan9port runs the note handlers for a note it goes on to ignore, and
|
|
||||||
* the process lives through it, so what strans announced must outlive
|
|
||||||
* such a note. A daemon that forks nothing gets only the one.
|
|
||||||
*/
|
|
||||||
#define notefatal(note) (strcmp(note, "sys: write on closed pipe") != 0)
|
|
||||||
|
|
||||||
/* A language id is the control code of the Ctrl key that selects it. */
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
LangEN = 0x14,
|
LangEN = 0x14,
|
||||||
LangJP = 0x0e,
|
LangJP = 0x0e,
|
||||||
LangJPK = 0x0b,
|
LangJPK = 0x0b,
|
||||||
LangKO = 0x13,
|
LangKO = 0x13,
|
||||||
LangHANJA = 0x08,
|
|
||||||
LangEMOJI = 0x05,
|
LangEMOJI = 0x05,
|
||||||
LangVI = 0x16,
|
LangVI = 0x16,
|
||||||
|
|
||||||
Maxrunes = 64,
|
Fontsz = 32,
|
||||||
Maxutf = Maxrunes * UTFmax + 1,
|
Fontbase = 4,
|
||||||
|
Nglyphs = 0x20000,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Maxclients = 64,
|
Maxclients = 64,
|
||||||
Ownerpoll = 200, /* ms between a frontend's owner checks while its preedit shows */
|
Maxkouho = 32,
|
||||||
Maxkouho = 128,
|
Maxdisp = 8,
|
||||||
Maxdisp = 9,
|
Imgw = 512,
|
||||||
|
Imgh = Maxdisp * Fontsz + 8,
|
||||||
/* Content purposes whose keys the engine never sees; IBus and
|
|
||||||
* text-input-v3 number them alike, both after GTK. */
|
|
||||||
Purposepassword = 8,
|
|
||||||
Purposepin = 9,
|
|
||||||
|
|
||||||
Colfg = 0x000000,
|
Colfg = 0x000000,
|
||||||
Colbg = 0xffffff,
|
Colbg = 0xffffff,
|
||||||
Colsep = 0xd0d0d0,
|
Colsel = 0xcccccc,
|
||||||
Colsel = 0x333333,
|
|
||||||
Colselfg = 0xffffff,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Each frontend composes on its own: they run in different procs. */
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Composeibus,
|
|
||||||
Composexim,
|
|
||||||
Composewl,
|
|
||||||
Ncompose,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Popup metrics in pixels; popupscale is GDK_SCALE, for HiDPI. */
|
|
||||||
extern int popupscale;
|
|
||||||
#define Fontsz (32*popupscale)
|
|
||||||
#define PopupPad (4*popupscale)
|
|
||||||
#define PopupSep popupscale
|
|
||||||
#define PopupBorder popupscale
|
|
||||||
#define PopupNumw Fontsz
|
|
||||||
#define PopupTextw (12*Fontsz)
|
|
||||||
#define PopupBasew (2*PopupPad + PopupNumw + PopupTextw)
|
|
||||||
|
|
||||||
typedef struct Str Str;
|
typedef struct Str Str;
|
||||||
struct Str
|
struct Str
|
||||||
{
|
{
|
||||||
Rune r[Maxrunes];
|
Rune r[64];
|
||||||
int n;
|
int n;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* What one key does to a composition: text to commit, the new pending
|
|
||||||
* text, and the new raw state (Telex key history, or pending Japanese
|
|
||||||
* romaji), given the old ones in Im.
|
|
||||||
*/
|
|
||||||
typedef struct Emit Emit;
|
typedef struct Emit Emit;
|
||||||
struct Emit
|
struct Emit
|
||||||
{
|
{
|
||||||
int eat;
|
int eat;
|
||||||
Str s;
|
Str s;
|
||||||
Str next;
|
Str next;
|
||||||
Str raw;
|
Str dict;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct Hnode Hnode;
|
||||||
|
struct Hnode
|
||||||
|
{
|
||||||
|
int filled;
|
||||||
|
int next;
|
||||||
|
char *key;
|
||||||
|
int klen;
|
||||||
|
char *val;
|
||||||
|
int vlen;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Tnode Tnode;
|
typedef struct Tnode Tnode;
|
||||||
struct Tnode
|
struct Tnode
|
||||||
{
|
{
|
||||||
char *val;
|
|
||||||
int vlen;
|
|
||||||
int child;
|
int child;
|
||||||
int sibling;
|
int sibling;
|
||||||
Rune c;
|
char c;
|
||||||
|
char *val;
|
||||||
|
int vlen;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Trie Trie;
|
typedef struct Trie Trie;
|
||||||
struct Trie
|
struct Trie
|
||||||
{
|
{
|
||||||
|
int root;
|
||||||
Tnode *nodes;
|
Tnode *nodes;
|
||||||
int n;
|
int n;
|
||||||
int cap;
|
int cap;
|
||||||
/* the last key put and its path, so a sorted file walks it once */
|
|
||||||
Str last;
|
|
||||||
int path[Maxrunes];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum
|
typedef struct Hmap Hmap;
|
||||||
|
struct Hmap
|
||||||
{
|
{
|
||||||
TrieMiss,
|
int nbs;
|
||||||
TriePrefix,
|
int nsz;
|
||||||
TrieExact,
|
int len;
|
||||||
|
int cap;
|
||||||
|
uchar *nodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Lang Lang;
|
typedef struct Lang Lang;
|
||||||
@@ -125,106 +99,56 @@ struct Lang
|
|||||||
char *dictname;
|
char *dictname;
|
||||||
Emit (*trans)(Im*, Rune);
|
Emit (*trans)(Im*, Rune);
|
||||||
void (*back)(Im*);
|
void (*back)(Im*);
|
||||||
|
void (*dictq)(Im*);
|
||||||
Trie *map;
|
Trie *map;
|
||||||
Trie *dict;
|
Hmap *dict;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Im
|
struct Im
|
||||||
{
|
{
|
||||||
Lang *l;
|
Lang *l;
|
||||||
Str pre;
|
Str pre;
|
||||||
/* 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;
|
|
||||||
typedef struct Area Area;
|
|
||||||
struct Area
|
|
||||||
{
|
|
||||||
int x;
|
|
||||||
int y;
|
|
||||||
int w;
|
|
||||||
int h;
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
||||||
int first;
|
|
||||||
int total;
|
|
||||||
Caret caret;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Popup geometry from popuplayout: n rows from row0 of the candidates,
|
|
||||||
* each Fontsz tall; a section's y is -1 when it is absent. Numbers
|
|
||||||
* start at PopupPad, text at PopupPad+PopupNumw.
|
|
||||||
*/
|
|
||||||
typedef struct Popup Popup;
|
|
||||||
struct Popup
|
|
||||||
{
|
|
||||||
int w;
|
|
||||||
int h;
|
|
||||||
int n;
|
|
||||||
int row0;
|
|
||||||
int prey;
|
|
||||||
int sepy;
|
|
||||||
int rowsy;
|
|
||||||
int textw;
|
|
||||||
int marky;
|
|
||||||
int markx;
|
|
||||||
int markw;
|
|
||||||
Str mark;
|
|
||||||
int sely;
|
|
||||||
int selw;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Keyreq Keyreq;
|
typedef struct Keyreq Keyreq;
|
||||||
typedef struct Keyres Keyres;
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Keypress,
|
|
||||||
Keyreset,
|
|
||||||
Keyrelease,
|
|
||||||
Keycaret,
|
|
||||||
Keycap,
|
|
||||||
};
|
|
||||||
struct Keyres
|
|
||||||
{
|
|
||||||
int eaten; /* Keycap: the caller is the owner and clientpre took. */
|
|
||||||
int del; /* runes of the client's own text to take back first */
|
|
||||||
Str commit;
|
|
||||||
Str preedit;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Keyreq
|
struct Keyreq
|
||||||
{
|
{
|
||||||
void *owner; /* stable until the context's release is acknowledged */
|
int fd;
|
||||||
int clientpre; /* the client draws the preedit; the popup does not */
|
|
||||||
int op;
|
|
||||||
u32int ks;
|
u32int ks;
|
||||||
u32int mod;
|
u32int mod;
|
||||||
Caret caret;
|
};
|
||||||
Str surround; /* the client's own text just before the cursor */
|
|
||||||
Channel *reply;
|
typedef struct Dictreq Dictreq;
|
||||||
|
struct Dictreq
|
||||||
|
{
|
||||||
|
Str key;
|
||||||
|
Str pre;
|
||||||
|
int lang;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct Dictres Dictres;
|
||||||
|
struct Dictres
|
||||||
|
{
|
||||||
|
Str key;
|
||||||
|
Str kouho[Maxkouho];
|
||||||
|
int nkouho;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Lang langs[];
|
extern Lang langs[];
|
||||||
extern int nlang;
|
extern int nlang;
|
||||||
extern Channel *drawc;
|
extern Channel *drawc;
|
||||||
extern Channel *keyc;
|
extern Channel *keyc;
|
||||||
|
extern Channel *dictreqc;
|
||||||
|
extern Channel *dictresc;
|
||||||
|
|||||||
156
dict.c
156
dict.c
@@ -1,90 +1,102 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
/* Appends the space-separated words of a node's entry to out[], up to max. */
|
static void
|
||||||
static int
|
dictlookup(Dictreq *req, Dictres *res)
|
||||||
words(Tnode *nd, Str *out, int n, int max)
|
|
||||||
{
|
{
|
||||||
|
Lang *l;
|
||||||
|
Hmap *dict;
|
||||||
|
Hnode *n;
|
||||||
char *p, *e, *sp;
|
char *p, *e, *sp;
|
||||||
Str tmp;
|
Str tmp;
|
||||||
|
|
||||||
if(nd->val == nil)
|
res->nkouho = 0;
|
||||||
return n;
|
if(req->key.n == 0)
|
||||||
p = nd->val;
|
return;
|
||||||
e = p + nd->vlen;
|
l = getlang(req->lang);
|
||||||
while(n < max && p < e){
|
dict = l ? l->dict : nil;
|
||||||
while(p < e && *p == ' ')
|
if(dict == nil)
|
||||||
p++;
|
return;
|
||||||
|
n = hmapget(dict, &req->key);
|
||||||
|
if(n == nil || n->vlen == 0)
|
||||||
|
return;
|
||||||
|
p = n->val;
|
||||||
|
e = p + n->vlen;
|
||||||
|
while(res->nkouho < Maxkouho && p < e){
|
||||||
sp = p;
|
sp = p;
|
||||||
while(p < e && *p != ' ')
|
while(p < e && *p != ' ')
|
||||||
p++;
|
p++;
|
||||||
if(sinit(&tmp, sp, p - sp) && tmp.n > 0)
|
sinit(&tmp, sp, p - sp);
|
||||||
out[n++] = tmp;
|
if(scmp(&tmp, &req->key) != 0)
|
||||||
|
res->kouho[res->nkouho++] = tmp;
|
||||||
|
if(p < e)
|
||||||
|
p++;
|
||||||
}
|
}
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The entries at and below a node, the shortest keys first. */
|
|
||||||
static int
|
|
||||||
below(Trie *t, int ni, Str *out, int n, int max)
|
|
||||||
{
|
|
||||||
Tnode *nd;
|
|
||||||
int ci;
|
|
||||||
|
|
||||||
nd = &t->nodes[ni];
|
|
||||||
n = words(nd, out, n, max);
|
|
||||||
for(ci = nd->child; ci >= 0 && n < max; ci = t->nodes[ci].sibling)
|
|
||||||
n = below(t, ci, out, n, max);
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fills out[] with up to max candidates for key: the words of its entry. */
|
|
||||||
int
|
|
||||||
dictlookup(Trie *t, Str *key, Str *out, int max)
|
|
||||||
{
|
|
||||||
int ni;
|
|
||||||
|
|
||||||
ni = key->n == 0 ? -1 : trienode(t, key);
|
|
||||||
if(ni < 0)
|
|
||||||
return 0;
|
|
||||||
return words(&t->nodes[ni], out, 0, max);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* As dictlookup, for every entry key is a prefix of, key's own first. */
|
|
||||||
int
|
|
||||||
dictprefix(Trie *t, Str *key, Str *out, int max)
|
|
||||||
{
|
|
||||||
int ni;
|
|
||||||
|
|
||||||
ni = key->n == 0 ? -1 : trienode(t, key);
|
|
||||||
if(ni < 0)
|
|
||||||
return 0;
|
|
||||||
return below(t, ni, out, 0, max);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Trie*
|
|
||||||
langopen(char *dir, char *name, char *ext)
|
|
||||||
{
|
|
||||||
char *path;
|
|
||||||
Trie *t;
|
|
||||||
|
|
||||||
path = smprint("%s/%s.%s", dir, name, ext);
|
|
||||||
if(path == nil)
|
|
||||||
die("out of memory");
|
|
||||||
t = trieopen(path);
|
|
||||||
free(path);
|
|
||||||
return t;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
langinit(char *dir)
|
dictthread(void*)
|
||||||
{
|
{
|
||||||
Lang *l;
|
Dictreq req;
|
||||||
|
Dictres res;
|
||||||
|
|
||||||
for(l = langs; l < langs + nlang; l++){
|
threadsetname("dict");
|
||||||
if(l->mapname != nil)
|
for(;;){
|
||||||
l->map = langopen(dir, l->mapname, "map");
|
if(chanrecv(dictreqc, &req) < 0)
|
||||||
if(l->dictname != nil)
|
break;
|
||||||
l->dict = langopen(dir, l->dictname, "dict");
|
while(channbrecv(dictreqc, &req) > 0)
|
||||||
|
;
|
||||||
|
dictlookup(&req, &res);
|
||||||
|
res.key = req.pre;
|
||||||
|
chansend(dictresc, &res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Hmap*
|
||||||
|
dictopen(char *path)
|
||||||
|
{
|
||||||
|
Hmap *h;
|
||||||
|
Biobuf *b;
|
||||||
|
Str key, val;
|
||||||
|
char *line, *tab;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
b = Bopen(path, OREAD);
|
||||||
|
if(b == nil)
|
||||||
|
die("can't open: %s", path);
|
||||||
|
h = hmapalloc(4096, 0);
|
||||||
|
while((line = Brdstr(b, '\n', 1)) != nil){
|
||||||
|
len = strlen(line);
|
||||||
|
if(len == 0 || line[0] == ';'){
|
||||||
|
free(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tab = strchr(line, '\t');
|
||||||
|
if(tab == nil || tab >= line + len - 1){
|
||||||
|
free(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
*tab = '\0';
|
||||||
|
sinit(&key, line, tab - line);
|
||||||
|
sinit(&val, tab+1, len - (tab - line) - 1);
|
||||||
|
hmapset(&h, &key, &val);
|
||||||
|
free(line);
|
||||||
|
}
|
||||||
|
Bterm(b);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
dictinit(char *dir)
|
||||||
|
{
|
||||||
|
char path[1024];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < nlang; i++){
|
||||||
|
if(langs[i].dictname == nil)
|
||||||
|
continue;
|
||||||
|
snprint(path, sizeof(path), "%s/%s.dict", dir, langs[i].dictname);
|
||||||
|
langs[i].dict = dictopen(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
44
fn.h
44
fn.h
@@ -1,7 +1,6 @@
|
|||||||
void die(char*, ...);
|
void die(char*, ...);
|
||||||
|
|
||||||
int sinit(Str*, char*, int);
|
void sinit(Str*, char*, int);
|
||||||
void stail(Str*, char*, int);
|
|
||||||
void sclear(Str*);
|
void sclear(Str*);
|
||||||
void sputr(Str*, Rune);
|
void sputr(Str*, Rune);
|
||||||
void spopr(Str*);
|
void spopr(Str*);
|
||||||
@@ -10,52 +9,33 @@ int scmp(Str*, Str*);
|
|||||||
int stoutf(Str*, char*, int);
|
int stoutf(Str*, char*, int);
|
||||||
Rune slastr(Str*);
|
Rune slastr(Str*);
|
||||||
|
|
||||||
|
Hmap* hmapalloc(int, int);
|
||||||
|
int hmapset(Hmap**, Str*, Str*);
|
||||||
|
Hnode* hmapget(Hmap*, Str*);
|
||||||
int mapget(Trie*, Str*, Str*);
|
int mapget(Trie*, Str*, Str*);
|
||||||
|
|
||||||
Trie* trienew(void);
|
|
||||||
void trieput(Trie*, char*, int, char*, int);
|
|
||||||
Trie* trieopen(char*);
|
Trie* trieopen(char*);
|
||||||
void trieclose(Trie*);
|
char* trieget(Trie*, char*, int, int*);
|
||||||
int trienode(Trie*, Str*);
|
int trielookup(Trie*, char*, int, char**, int*);
|
||||||
int trielookup(Trie*, Str*, char**, int*);
|
|
||||||
|
|
||||||
Lang* getlang(int);
|
Lang* getlang(int);
|
||||||
void langinit(char*);
|
void mapinit(char*);
|
||||||
|
void dictinit(char*);
|
||||||
|
|
||||||
int dictlookup(Trie*, Str*, Str*, int);
|
void dictthread(void*);
|
||||||
int dictprefix(Trie*, Str*, Str*, int);
|
|
||||||
|
|
||||||
void drawthread(void*);
|
void drawthread(void*);
|
||||||
void popuparea(Area*, int, Area*, int, int, Area*);
|
|
||||||
void popupposition(Caret*, int, int, Area*, int, int, int*, int*);
|
|
||||||
int pagemarker(char*, int, int, int, int);
|
|
||||||
void popuplayout(Drawcmd*, int, int, Popup*);
|
|
||||||
void popupdraw(u32int*, Drawcmd*, Popup*);
|
|
||||||
void imthread(void*);
|
void imthread(void*);
|
||||||
void impre(Im*, Str*);
|
|
||||||
void transstr(Lang*, Str*, Str*, Str*);
|
|
||||||
Emit transmap(Im*, Rune);
|
Emit transmap(Im*, Rune);
|
||||||
Emit transko(Im*, Rune);
|
Emit transko(Im*, Rune);
|
||||||
Emit transvi(Im*, Rune);
|
Emit transvi(Im*, Rune);
|
||||||
void backko(Im*);
|
void backko(Im*);
|
||||||
void backvi(Im*);
|
void dictsend(Im*, Str*);
|
||||||
|
|
||||||
void srvinit(void);
|
|
||||||
void srvthread(void*);
|
void srvthread(void*);
|
||||||
void ibusthread(void*);
|
|
||||||
void ximthread(void*);
|
|
||||||
int wlinit(void);
|
|
||||||
void wlthread(void*);
|
|
||||||
int keymeaningful(u32int);
|
|
||||||
|
|
||||||
void composeinit(void);
|
|
||||||
void composedrop(int, void*);
|
|
||||||
int composekey(int, void*, u32int, char*, int);
|
|
||||||
|
|
||||||
void* emalloc(ulong);
|
void* emalloc(ulong);
|
||||||
void* erealloc(void*, ulong);
|
void* erealloc(void*, ulong);
|
||||||
|
|
||||||
void textinit(void);
|
void fontinit(char*);
|
||||||
void textclose(void);
|
void putfont(u32int*, int, int, int, int, Rune);
|
||||||
int textwidth(Str*);
|
|
||||||
void textdraw(u32int*, int, int, int, int, int, u32int, Str*);
|
|
||||||
|
|||||||
233
font.c
233
font.c
@@ -1,142 +1,137 @@
|
|||||||
|
#define STB_TRUETYPE_IMPLEMENTATION
|
||||||
|
#include "stb_truetype.h"
|
||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include <fontconfig/fontconfig.h>
|
|
||||||
#include <pango/pangocairo.h>
|
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
static PangoFontMap *fontmap;
|
typedef struct Glyph Glyph;
|
||||||
static PangoContext *context;
|
struct Glyph
|
||||||
static PangoLayout *layout;
|
|
||||||
|
|
||||||
void
|
|
||||||
textclose(void)
|
|
||||||
{
|
{
|
||||||
if(layout != nil)
|
uchar *bmp;
|
||||||
g_object_unref(layout);
|
int w, h, ox, oy;
|
||||||
if(context != nil)
|
};
|
||||||
g_object_unref(context);
|
|
||||||
if(fontmap != nil)
|
enum { Maxfonts = 4 };
|
||||||
g_object_unref(fontmap);
|
|
||||||
layout = nil;
|
static stbtt_fontinfo fonts[Maxfonts];
|
||||||
context = nil;
|
static uchar *fontdata[Maxfonts];
|
||||||
fontmap = nil;
|
static float scale[Maxfonts];
|
||||||
|
static int nfonts;
|
||||||
|
static Glyph cache[Nglyphs];
|
||||||
|
static u32int blendtab[2][256];
|
||||||
|
|
||||||
|
static u32int
|
||||||
|
blend(u32int bg, int a)
|
||||||
|
{
|
||||||
|
int r, g, b, inv;
|
||||||
|
|
||||||
|
inv = 255 - a;
|
||||||
|
r = (bg >> 16 & 0xff) * inv / 255;
|
||||||
|
g = (bg >> 8 & 0xff) * inv / 255;
|
||||||
|
b = (bg & 0xff) * inv / 255;
|
||||||
|
return (r << 16) | (g << 8) | b;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setfont(void)
|
loadfont(char *path)
|
||||||
{
|
{
|
||||||
PangoFontDescription *font;
|
int fd;
|
||||||
PangoRectangle r;
|
long sz, n;
|
||||||
int size;
|
|
||||||
|
|
||||||
font = pango_font_description_new();
|
if(nfonts >= Maxfonts)
|
||||||
pango_font_description_set_family(font, "sans");
|
die("too many fonts");
|
||||||
pango_font_description_set_absolute_size(font, Fontsz * PANGO_SCALE);
|
fd = open(path, OREAD);
|
||||||
pango_layout_set_font_description(layout, font);
|
if(fd < 0)
|
||||||
pango_layout_set_text(layout, "Mg", -1);
|
die("can't open font: %s", path);
|
||||||
pango_layout_get_pixel_extents(layout, nil, &r);
|
sz = seek(fd, 0, 2);
|
||||||
if(r.height > 0){
|
seek(fd, 0, 0);
|
||||||
/* Fontsz is the popup row height, not a point size. */
|
fontdata[nfonts] = emalloc(sz);
|
||||||
size = Fontsz * PANGO_SCALE * Fontsz / r.height;
|
n = readn(fd, fontdata[nfonts], sz);
|
||||||
pango_font_description_set_absolute_size(font, max(size, PANGO_SCALE));
|
close(fd);
|
||||||
pango_layout_set_font_description(layout, font);
|
if(n != sz)
|
||||||
}
|
die("can't read font: %s", path);
|
||||||
pango_font_description_free(font);
|
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);
|
||||||
void
|
nfonts++;
|
||||||
textinit(void)
|
|
||||||
{
|
|
||||||
FcInit();
|
|
||||||
fontmap = pango_cairo_font_map_new();
|
|
||||||
pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(fontmap), 96);
|
|
||||||
context = pango_font_map_create_context(fontmap);
|
|
||||||
layout = pango_layout_new(context);
|
|
||||||
pango_layout_set_single_paragraph_mode(layout, TRUE);
|
|
||||||
setfont();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
settext(Str *s)
|
isfont(char *name)
|
||||||
{
|
{
|
||||||
char utf[Maxutf];
|
char *p;
|
||||||
int n;
|
|
||||||
|
|
||||||
n = stoutf(s, utf, sizeof utf);
|
p = strrchr(name, '.');
|
||||||
pango_layout_set_text(layout, utf, n);
|
if(p == nil)
|
||||||
return n > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
textextents(PangoRectangle *r)
|
|
||||||
{
|
|
||||||
PangoRectangle ink, logical;
|
|
||||||
int x0, y0, x1, y1;
|
|
||||||
|
|
||||||
pango_layout_get_pixel_extents(layout, &ink, &logical);
|
|
||||||
x0 = min(ink.x, logical.x);
|
|
||||||
y0 = min(ink.y, logical.y);
|
|
||||||
x1 = max(ink.x + ink.width, logical.x + logical.width);
|
|
||||||
y1 = max(ink.y + ink.height, logical.y + logical.height);
|
|
||||||
r->x = x0;
|
|
||||||
r->y = y0;
|
|
||||||
r->width = max(x1 - x0, 0);
|
|
||||||
r->height = max(y1 - y0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
textwidth(Str *s)
|
|
||||||
{
|
|
||||||
PangoRectangle r;
|
|
||||||
|
|
||||||
pango_layout_set_width(layout, -1);
|
|
||||||
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
|
|
||||||
if(!settext(s))
|
|
||||||
return 0;
|
return 0;
|
||||||
textextents(&r);
|
return strcmp(p, ".ttf") == 0 || strcmp(p, ".otf") == 0;
|
||||||
return r.width;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Draws s at (x, y) into the w-by-h buffer, ellipsized to fit pixels and
|
|
||||||
* clipped to its own row so a tall fallback face cannot paint a
|
|
||||||
* neighbour.
|
|
||||||
*/
|
|
||||||
void
|
void
|
||||||
textdraw(u32int *buf, int w, int h, int x, int y, int fit, u32int color,
|
fontinit(char *dir)
|
||||||
Str *s)
|
|
||||||
{
|
{
|
||||||
PangoRectangle r;
|
int fd, n, i, a;
|
||||||
cairo_surface_t *surface;
|
Dir *d;
|
||||||
cairo_t *cr;
|
char path[256];
|
||||||
int b, g, red;
|
|
||||||
|
|
||||||
if(fit <= 0)
|
fd = open(dir, OREAD);
|
||||||
return;
|
if(fd < 0)
|
||||||
pango_layout_set_width(layout, fit * PANGO_SCALE);
|
die("can't open font dir: %s", dir);
|
||||||
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
|
n = dirreadall(fd, &d);
|
||||||
if(w <= 0 || h <= 0 || !settext(s))
|
close(fd);
|
||||||
return;
|
if(n < 0)
|
||||||
surface = cairo_image_surface_create_for_data((uchar*)buf,
|
die("can't read font dir: %s", dir);
|
||||||
CAIRO_FORMAT_RGB24, w, h, w * sizeof buf[0]);
|
for(i = 0; i < n; i++){
|
||||||
if(cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS){
|
if(isfont(d[i].name)){
|
||||||
cairo_surface_destroy(surface);
|
snprint(path, sizeof path, "%s/%s", dir, d[i].name);
|
||||||
return;
|
loadfont(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cairo_surface_mark_dirty(surface);
|
free(d);
|
||||||
cr = cairo_create(surface);
|
if(nfonts == 0)
|
||||||
if(cairo_status(cr) == CAIRO_STATUS_SUCCESS){
|
die("no fonts in %s", dir);
|
||||||
pango_cairo_update_layout(cr, layout);
|
for(a = 0; a < 256; a++){
|
||||||
textextents(&r);
|
blendtab[0][a] = blend(Colbg, a);
|
||||||
cairo_rectangle(cr, x, y, fit, Fontsz);
|
blendtab[1][a] = blend(Colsel, a);
|
||||||
cairo_clip(cr);
|
}
|
||||||
cairo_move_to(cr, x - r.x, y + (Fontsz - r.height) / 2 - r.y);
|
}
|
||||||
red = color >> 16 & 0xff;
|
|
||||||
g = color >> 8 & 0xff;
|
void
|
||||||
b = color & 0xff;
|
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
|
||||||
cairo_set_source_rgb(cr, red / 255.0, g / 255.0, b / 255.0);
|
{
|
||||||
pango_cairo_show_layout(cr, layout);
|
Glyph *g;
|
||||||
|
int i, j, a, sel, f;
|
||||||
|
int y0, j0, j1, x0, i0, i1;
|
||||||
|
u32int *p;
|
||||||
|
|
||||||
|
if(r >= Nglyphs)
|
||||||
|
return;
|
||||||
|
g = &cache[r];
|
||||||
|
if(g->bmp == nil){
|
||||||
|
for(f = 0; f < nfonts; f++){
|
||||||
|
if(stbtt_FindGlyphIndex(&fonts[f], r) == 0)
|
||||||
|
continue;
|
||||||
|
g->bmp = stbtt_GetCodepointBitmap(&fonts[f], scale[f], scale[f], r, &g->w, &g->h, &g->ox, &g->oy);
|
||||||
|
if(g->bmp != nil)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(g->bmp == nil)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
y0 = py + g->oy + Fontsz - Fontbase;
|
||||||
|
j0 = y0 < 0 ? -y0 : 0;
|
||||||
|
j1 = y0 + g->h > h ? h - y0 : g->h;
|
||||||
|
x0 = px + g->ox;
|
||||||
|
i0 = x0 < 0 ? -x0 : 0;
|
||||||
|
i1 = x0 + g->w > w ? w - x0 : g->w;
|
||||||
|
for(j = j0; j < j1; j++){
|
||||||
|
for(i = i0; i < i1; i++){
|
||||||
|
a = g->bmp[j * g->w + i];
|
||||||
|
if(a > 0){
|
||||||
|
p = &buf[(y0 + j) * w + x0 + i];
|
||||||
|
sel = (*p == Colsel) ? 1 : 0;
|
||||||
|
*p = blendtab[sel][a];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cairo_destroy(cr);
|
|
||||||
cairo_surface_flush(surface);
|
|
||||||
cairo_surface_destroy(surface);
|
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
font/NotoEmoji-Regular.ttf
Normal file
BIN
font/NotoEmoji-Regular.ttf
Normal file
Binary file not shown.
BIN
font/NotoSans-Regular.ttf
Normal file
BIN
font/NotoSans-Regular.ttf
Normal file
Binary file not shown.
BIN
font/NotoSansMonoCJKsc-Regular.otf
Normal file
BIN
font/NotoSansMonoCJKsc-Regular.otf
Normal file
Binary file not shown.
3
gtk/Dockerfile
Normal file
3
gtk/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
FROM debian:stable-slim
|
||||||
|
RUN apt-get update && apt-get install -y gcc libgtk-3-dev make
|
||||||
|
WORKDIR /src
|
||||||
51
gtk/Makefile
51
gtk/Makefile
@@ -1,43 +1,22 @@
|
|||||||
PROG = im-strans.so
|
CFLAGS = -Wall -O2 -I.. `pkg-config --cflags gtk+-3.0`
|
||||||
PKG_CONFIG ?= pkg-config
|
LDFLAGS = `pkg-config --libs gtk+-3.0`
|
||||||
CFLAGS ?= -O2
|
DSTDIR ?= $(shell find /usr/lib* -type d -path "*/gtk-3.0/*/immodules" 2>/dev/null | head -1)
|
||||||
GTK_CFLAGS = $(shell $(PKG_CONFIG) --cflags gtk+-3.0)
|
|
||||||
GTK_LIBS = $(shell $(PKG_CONFIG) --libs gtk+-3.0)
|
|
||||||
GTK_QUERY_IMMODULES ?= gtk-query-immodules-3.0
|
|
||||||
GTK_LIBDIR ?= $(shell $(PKG_CONFIG) --variable=libdir gtk+-3.0 2>/dev/null)
|
|
||||||
GTK_BINARY_VERSION ?= $(shell $(PKG_CONFIG) \
|
|
||||||
--variable=gtk_binary_version gtk+-3.0 2>/dev/null)
|
|
||||||
# Without GTK development files, ask the runtime where its modules live.
|
|
||||||
GTK_RUNTIME_MODULE_DIR = $(shell GTK_PATH= GTK_IM_MODULE_FILE= \
|
|
||||||
$(GTK_QUERY_IMMODULES) 2>/dev/null | \
|
|
||||||
awk -F '"' '/^"\// { sub("/[^/]*$$", "", $$2); print $$2; exit }')
|
|
||||||
GTK_MODULE_DIR ?= $(strip $(if \
|
|
||||||
$(and $(strip $(GTK_LIBDIR)),$(strip $(GTK_BINARY_VERSION))),\
|
|
||||||
$(GTK_LIBDIR)/gtk-3.0/$(GTK_BINARY_VERSION)/immodules,\
|
|
||||||
$(GTK_RUNTIME_MODULE_DIR)))
|
|
||||||
|
|
||||||
all: $(PROG)
|
im-strans.so: main.c
|
||||||
|
$(CC) -shared -fPIC $(CFLAGS) -o $@ $< $(LDFLAGS)
|
||||||
|
|
||||||
$(PROG): main.c ../ipc.c ../ipc.h
|
docker: main.c Dockerfile
|
||||||
$(CC) $(CPPFLAGS) -I.. $(GTK_CFLAGS) -Wall $(CFLAGS) $(LDFLAGS) \
|
docker build -t strans-gtk .
|
||||||
-shared -fPIC -o $@ main.c ../ipc.c $(GTK_LIBS) $(LDLIBS)
|
docker run --rm -v $(CURDIR)/..:/src -w /src/gtk strans-gtk make im-strans.so
|
||||||
|
|
||||||
CHECKDIR = test -n "$$module_dir" || { \
|
install:
|
||||||
echo "cannot find GTK 3 module directory; set GTK_MODULE_DIR" >&2; \
|
mkdir -p $(DSTDIR)
|
||||||
exit 1; }
|
cp im-strans.so $(DSTDIR)/
|
||||||
REFRESH = test -n "$(DESTDIR)" || { \
|
gtk-query-immodules-3.0 --update-cache
|
||||||
unset GTK_PATH GTK_IM_MODULE_FILE; $(GTK_QUERY_IMMODULES) --update-cache; }
|
|
||||||
|
|
||||||
install: $(PROG)
|
|
||||||
module_dir="$(GTK_MODULE_DIR)"; $(CHECKDIR); \
|
|
||||||
mkdir -p "$(DESTDIR)$$module_dir" && \
|
|
||||||
cp "$(PROG)" "$(DESTDIR)$$module_dir/" && $(REFRESH)
|
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
module_dir="$(GTK_MODULE_DIR)"; $(CHECKDIR); \
|
rm -f $(DSTDIR)/im-strans.so
|
||||||
rm -f "$(DESTDIR)$$module_dir/$(PROG)" && $(REFRESH)
|
gtk-query-immodules-3.0 --update-cache
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(PROG)
|
rm -f im-strans.so
|
||||||
|
|
||||||
.PHONY: all install uninstall clean
|
|
||||||
|
|||||||
567
gtk/main.c
567
gtk/main.c
@@ -1,545 +1,165 @@
|
|||||||
#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/un.h>
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
#ifdef GDK_WINDOWING_X11
|
|
||||||
#include <gdk/gdkx.h>
|
|
||||||
#endif
|
|
||||||
#include "ipc.h"
|
#include "ipc.h"
|
||||||
|
|
||||||
typedef struct Im Im;
|
typedef struct Im Im;
|
||||||
struct Im
|
struct Im
|
||||||
{
|
{
|
||||||
GtkIMContextSimple parent;
|
GtkIMContext parent;
|
||||||
int fd;
|
int fd;
|
||||||
int usepreedit;
|
|
||||||
int ext;
|
|
||||||
int private;
|
|
||||||
int simpleactive;
|
|
||||||
int simpledone;
|
|
||||||
char pre[Ipcfieldmax+1];
|
|
||||||
int prelen;
|
|
||||||
GdkWindow *win;
|
|
||||||
GdkRectangle cursor;
|
|
||||||
int cursorvalid;
|
|
||||||
int caretsent;
|
|
||||||
unsigned char sent[Ipccaretsz];
|
|
||||||
char surround[Ipcfieldmax];
|
|
||||||
int nsurround;
|
|
||||||
int surroundsent;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct ImClass ImClass;
|
typedef struct ImClass ImClass;
|
||||||
struct ImClass
|
struct ImClass
|
||||||
{
|
{
|
||||||
GtkIMContextSimpleClass parent;
|
GtkIMContextClass parent;
|
||||||
};
|
};
|
||||||
|
|
||||||
static GType imtype;
|
static GType imtype;
|
||||||
static GObjectClass *parentobject;
|
|
||||||
static GtkIMContextClass *parentim;
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setpreedit(Im *im, const char *s, int n)
|
|
||||||
{
|
|
||||||
int was;
|
|
||||||
|
|
||||||
if(n < 0 || n > Ipcfieldmax)
|
|
||||||
n = 0;
|
|
||||||
if(n == 0 && im->prelen == 0)
|
|
||||||
return;
|
|
||||||
was = im->prelen;
|
|
||||||
if(n > 0)
|
|
||||||
memcpy(im->pre, s, n);
|
|
||||||
im->pre[n] = '\0';
|
|
||||||
im->prelen = n;
|
|
||||||
if(was == 0 && n > 0)
|
|
||||||
g_signal_emit_by_name(im, "preedit-start");
|
|
||||||
g_signal_emit_by_name(im, "preedit-changed");
|
|
||||||
if(was > 0 && n == 0)
|
|
||||||
g_signal_emit_by_name(im, "preedit-end");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
srvdrop(Im *im, int notify)
|
|
||||||
{
|
|
||||||
if(im->fd >= 0)
|
|
||||||
close(im->fd);
|
|
||||||
im->fd = -1;
|
|
||||||
im->ext = 0;
|
|
||||||
im->caretsent = 0;
|
|
||||||
im->surroundsent = 0;
|
|
||||||
if(notify)
|
|
||||||
setpreedit(im, "", 0);
|
|
||||||
else{
|
|
||||||
im->pre[0] = '\0';
|
|
||||||
im->prelen = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
srvclose(Im *im)
|
|
||||||
{
|
|
||||||
srvdrop(im, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Both buffers are char[Ipcfieldmax+1]. */
|
|
||||||
static int
|
|
||||||
readresp(Im *im, int want, char *commit, char *pre, Ipcresp *resp)
|
|
||||||
{
|
|
||||||
if(ipcreadresp(im->fd, want, commit, pre, resp) < 0)
|
|
||||||
return -1;
|
|
||||||
if(!g_utf8_validate(commit, resp->commitlen, NULL) ||
|
|
||||||
(want && !g_utf8_validate(pre, resp->preeditlen, NULL))){
|
|
||||||
errno = EPROTO;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
dropwindow(Im *im)
|
|
||||||
{
|
|
||||||
if(im->win != NULL)
|
|
||||||
g_object_unref(im->win);
|
|
||||||
im->win = NULL;
|
|
||||||
im->cursorvalid = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Root-relative caret in device pixels; returns whether it is known. */
|
|
||||||
static int
|
|
||||||
caretget(Im *im, int32_t *x, int32_t *y, int32_t *h)
|
|
||||||
{
|
|
||||||
gint rx, ry, scale;
|
|
||||||
int64_t sx, sy, sh;
|
|
||||||
|
|
||||||
*x = *y = *h = 0;
|
|
||||||
if(im->win != NULL && gdk_window_is_destroyed(im->win))
|
|
||||||
dropwindow(im);
|
|
||||||
if(!im->cursorvalid || im->win == NULL || im->cursor.height < 0)
|
|
||||||
return 0;
|
|
||||||
#ifdef GDK_WINDOWING_X11
|
|
||||||
if(!GDK_IS_X11_WINDOW(im->win))
|
|
||||||
return 0;
|
|
||||||
#else
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
scale = gdk_window_get_scale_factor(im->win);
|
|
||||||
if(scale <= 0)
|
|
||||||
return 0;
|
|
||||||
/* GDK translates the client-relative point before device scaling. */
|
|
||||||
gdk_window_get_root_coords(im->win, 0, 0, &rx, &ry);
|
|
||||||
sx = ((int64_t)rx + im->cursor.x) * scale;
|
|
||||||
sy = ((int64_t)ry + im->cursor.y) * scale;
|
|
||||||
sh = (int64_t)im->cursor.height * scale;
|
|
||||||
if(sx < INT32_MIN || sx > INT32_MAX ||
|
|
||||||
sy < INT32_MIN || sy > INT32_MAX || sh > INT32_MAX)
|
|
||||||
return 0;
|
|
||||||
*x = sx;
|
|
||||||
*y = sy;
|
|
||||||
*h = sh;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
sendcaret(Im *im)
|
|
||||||
{
|
|
||||||
unsigned char buf[Ipccaretsz];
|
|
||||||
int32_t x, y, h;
|
|
||||||
int valid;
|
|
||||||
|
|
||||||
if(im->fd < 0 || !im->ext)
|
|
||||||
return 0;
|
|
||||||
valid = caretget(im, &x, &y, &h);
|
|
||||||
ipcpackcaret(buf, valid, x, y, h);
|
|
||||||
if(im->caretsent && memcmp(buf, im->sent, sizeof buf) == 0)
|
|
||||||
return 0;
|
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0)
|
|
||||||
return -1;
|
|
||||||
memcpy(im->sent, buf, sizeof buf);
|
|
||||||
im->caretsent = 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The text just before the cursor, as much of it as a reading can use,
|
|
||||||
* cut on a rune boundary. A widget that keeps none leaves it empty, and
|
|
||||||
* then the daemon never asks for any of it back.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
readsurround(Im *im, GtkIMContext *ctx)
|
|
||||||
{
|
|
||||||
char *text, *p;
|
|
||||||
gint cursor;
|
|
||||||
int start;
|
|
||||||
|
|
||||||
im->nsurround = 0;
|
|
||||||
if(!gtk_im_context_get_surrounding(ctx, &text, &cursor))
|
|
||||||
return;
|
|
||||||
if(cursor < 0 || cursor > (gint)strlen(text)){
|
|
||||||
g_free(text);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
start = cursor > Ipcfieldmax ? cursor - Ipcfieldmax : 0;
|
|
||||||
for(p = text + start; p < text + cursor && (*p & 0xc0) == 0x80; p++)
|
|
||||||
;
|
|
||||||
im->nsurround = text + cursor - p;
|
|
||||||
memcpy(im->surround, p, im->nsurround);
|
|
||||||
g_free(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sent only when it changes, as the caret is. */
|
|
||||||
static int
|
|
||||||
sendsurround(Im *im, GtkIMContext *ctx)
|
|
||||||
{
|
|
||||||
unsigned char hdr[Ipcreqsz];
|
|
||||||
char was[Ipcfieldmax];
|
|
||||||
int nwas;
|
|
||||||
|
|
||||||
if(im->fd < 0 || !im->ext)
|
|
||||||
return 0;
|
|
||||||
nwas = im->nsurround;
|
|
||||||
memcpy(was, im->surround, nwas);
|
|
||||||
readsurround(im, ctx);
|
|
||||||
if(im->surroundsent && nwas == im->nsurround &&
|
|
||||||
memcmp(was, im->surround, nwas) == 0)
|
|
||||||
return 0;
|
|
||||||
ipcpacksurround(hdr, im->nsurround);
|
|
||||||
if(ipcsend(im->fd, hdr, sizeof hdr) < 0 ||
|
|
||||||
(im->nsurround > 0 &&
|
|
||||||
ipcsend(im->fd, im->surround, im->nsurround) < 0))
|
|
||||||
return -1;
|
|
||||||
im->surroundsent = 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
srvconnect(Im *im)
|
srvconnect(Im *im)
|
||||||
{
|
{
|
||||||
unsigned char buf[Ipcreqsz];
|
struct sockaddr_un addr;
|
||||||
char commit[Ipcfieldmax+1];
|
|
||||||
char pre[Ipcfieldmax+1];
|
|
||||||
Ipcresp resp;
|
|
||||||
|
|
||||||
if(im->fd >= 0)
|
if(im->fd >= 0)
|
||||||
return 0;
|
return;
|
||||||
im->fd = ipcconnect();
|
im->fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
if(im->fd < 0)
|
if(im->fd < 0)
|
||||||
return -1;
|
return;
|
||||||
/* Marker zero is an old daemon's harmless key-zero response. */
|
memset(&addr, 0, sizeof(addr));
|
||||||
ipcpackcap(buf, im->usepreedit);
|
addr.sun_family = AF_UNIX;
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
snprintf(addr.sun_path, sizeof(addr.sun_path), "/tmp/strans.%d", getuid());
|
||||||
readresp(im, im->usepreedit, commit, pre, &resp) < 0){
|
if(connect(im->fd, (struct sockaddr*)&addr, sizeof(addr)) < 0){
|
||||||
srvclose(im);
|
close(im->fd);
|
||||||
return -1;
|
im->fd = -1;
|
||||||
}
|
}
|
||||||
im->ext = resp.eaten != 0;
|
}
|
||||||
if(sendcaret(im) < 0){
|
|
||||||
srvclose(im);
|
static int
|
||||||
|
readresp(Im *im, char *buf, int bufsz)
|
||||||
|
{
|
||||||
|
unsigned char hdr[2];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if(read(im->fd, hdr, 2) != 2)
|
||||||
return -1;
|
return -1;
|
||||||
|
n = hdr[1];
|
||||||
|
buf[0] = '\0';
|
||||||
|
if(n > 0 && n < bufsz){
|
||||||
|
if(read(im->fd, buf, n) != n)
|
||||||
|
return -1;
|
||||||
|
buf[n] = '\0';
|
||||||
}
|
}
|
||||||
return 0;
|
return hdr[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static uint32_t
|
||||||
simplecommit(GtkIMContext *ctx, const char *s, Im *im)
|
kget(uint32_t gdk)
|
||||||
{
|
{
|
||||||
(void)ctx;
|
if(gdk >= 0xff00)
|
||||||
(void)s;
|
return Kspec + (gdk - 0xff00);
|
||||||
im->simpledone = 1;
|
return gdk_keyval_to_unicode(gdk);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static uint32_t
|
||||||
simpleend(GtkIMContext *ctx, Im *im)
|
mget(uint32_t state)
|
||||||
{
|
{
|
||||||
(void)ctx;
|
uint32_t m;
|
||||||
im->simpledone = 1;
|
|
||||||
|
m = 0;
|
||||||
|
if(state & GDK_SHIFT_MASK)
|
||||||
|
m |= Mshift;
|
||||||
|
if(state & GDK_CONTROL_MASK)
|
||||||
|
m |= Mctrl;
|
||||||
|
if(state & GDK_MOD1_MASK)
|
||||||
|
m |= Malt;
|
||||||
|
if(state & GDK_SUPER_MASK)
|
||||||
|
m |= Msuper;
|
||||||
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* GtkIMContextSimple composes dead keys and Compose sequences; it keeps
|
|
||||||
* the key stream until it commits, ends its preedit, or rejects a press.
|
|
||||||
*/
|
|
||||||
static gboolean
|
|
||||||
simplefilter(GtkIMContext *ctx, GdkEventKey *ev, int release)
|
|
||||||
{
|
|
||||||
Im *im;
|
|
||||||
gboolean r;
|
|
||||||
|
|
||||||
im = (Im*)ctx;
|
|
||||||
im->simpleactive = 1;
|
|
||||||
im->simpledone = 0;
|
|
||||||
r = parentim->filter_keypress(ctx, ev);
|
|
||||||
if((!r && !release) || im->simpledone)
|
|
||||||
im->simpleactive = 0;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The daemon hands the pending text back; it becomes committed text. */
|
|
||||||
static void
|
static void
|
||||||
sendreset(Im *im)
|
sendreset(Im *im)
|
||||||
{
|
{
|
||||||
unsigned char buf[Ipcreqsz];
|
unsigned char buf[4];
|
||||||
char commit[Ipcfieldmax+1];
|
char resp[64];
|
||||||
char pre[Ipcfieldmax+1];
|
|
||||||
Ipcresp resp;
|
|
||||||
|
|
||||||
if(im->fd < 0){
|
if(im->fd < 0)
|
||||||
setpreedit(im, "", 0);
|
|
||||||
return;
|
return;
|
||||||
}
|
buf[0] = 0;
|
||||||
ipcpackreset(buf, im->usepreedit);
|
buf[1] = 0;
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
buf[2] = Kesc & 0xff;
|
||||||
readresp(im, im->usepreedit, commit, pre, &resp) < 0){
|
buf[3] = Kesc >> 8;
|
||||||
srvclose(im);
|
if(write(im->fd, buf, 4) == 4)
|
||||||
return;
|
readresp(im, resp, sizeof(resp));
|
||||||
}
|
|
||||||
setpreedit(im, "", 0);
|
|
||||||
if(commit[0] != '\0')
|
|
||||||
g_signal_emit_by_name(im, "commit", commit);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* A password entry hidden the older way sets no purpose and signals nothing. */
|
|
||||||
static int
|
|
||||||
ishidden(Im *im)
|
|
||||||
{
|
|
||||||
GtkWidget *w;
|
|
||||||
|
|
||||||
if(im->win == NULL)
|
|
||||||
return 0;
|
|
||||||
gdk_window_get_user_data(im->win, (gpointer*)&w);
|
|
||||||
return GTK_IS_ENTRY(w) && !gtk_entry_get_visibility(GTK_ENTRY(w));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
||||||
{
|
{
|
||||||
Im *im;
|
Im *im;
|
||||||
unsigned char buf[Ipcreqsz];
|
unsigned char buf[4];
|
||||||
char commit[Ipcfieldmax+1];
|
char resp[64];
|
||||||
char pre[Ipcfieldmax+1];
|
|
||||||
uint32_t key, mod;
|
uint32_t key, mod;
|
||||||
Ipcresp resp;
|
int r;
|
||||||
gboolean r;
|
|
||||||
|
|
||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
if(ev->type != GDK_KEY_PRESS){
|
if(ev->type != GDK_KEY_PRESS)
|
||||||
if(im->simpleactive)
|
|
||||||
return simplefilter(ctx, ev, 1);
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
srvconnect(im);
|
||||||
if(im->simpleactive)
|
if(im->fd < 0)
|
||||||
return simplefilter(ctx, ev, 0);
|
return FALSE;
|
||||||
key = ipckeysym(ev->keyval, gdk_keyval_to_unicode(ev->keyval));
|
key = kget(ev->keyval);
|
||||||
mod = ipcmod(ev->state);
|
if(key == 0)
|
||||||
if(im->private || ishidden(im) || key == 0){
|
return FALSE;
|
||||||
r = simplefilter(ctx, ev, 0);
|
mod = mget(ev->state);
|
||||||
/* A dead key starts a compose: pending text was typed first. */
|
buf[0] = 0;
|
||||||
if(im->simpleactive)
|
buf[1] = mod;
|
||||||
sendreset(im);
|
buf[2] = key & 0xff;
|
||||||
return r;
|
buf[3] = key >> 8;
|
||||||
}
|
if(write(im->fd, buf, 4) != 4)
|
||||||
if(srvconnect(im) < 0)
|
return FALSE;
|
||||||
return simplefilter(ctx, ev, 0);
|
r = readresp(im, resp, sizeof(resp));
|
||||||
/* A retained GDK window may have moved since the last cursor report. */
|
if(r < 0)
|
||||||
if(sendcaret(im) < 0 || sendsurround(im, ctx) < 0){
|
return FALSE;
|
||||||
srvclose(im);
|
if(r != 0 && resp[0] != '\0')
|
||||||
return simplefilter(ctx, ev, 0);
|
g_signal_emit_by_name(ctx, "commit", resp);
|
||||||
}
|
return r != 0;
|
||||||
ipcpackreq(buf, im->usepreedit, mod, key);
|
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
|
||||||
readresp(im, im->usepreedit, commit, pre, &resp) < 0){
|
|
||||||
srvclose(im);
|
|
||||||
return simplefilter(ctx, ev, 0);
|
|
||||||
}
|
|
||||||
if(im->usepreedit && commit[0] != '\0' && im->prelen > 0)
|
|
||||||
setpreedit(im, "", 0);
|
|
||||||
/* The reading reached into the widget's own text: give it back. */
|
|
||||||
if(resp.del > 0){
|
|
||||||
gtk_im_context_delete_surrounding(ctx, -resp.del, resp.del);
|
|
||||||
im->surroundsent = 0;
|
|
||||||
}
|
|
||||||
if(commit[0] != '\0')
|
|
||||||
g_signal_emit_by_name(ctx, "commit", commit);
|
|
||||||
if(im->usepreedit)
|
|
||||||
setpreedit(im, pre, resp.preeditlen);
|
|
||||||
if(resp.eaten)
|
|
||||||
return TRUE;
|
|
||||||
return simplefilter(ctx, ev, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs,
|
|
||||||
gint *cursor_pos)
|
|
||||||
{
|
|
||||||
Im *im;
|
|
||||||
PangoAttribute *u;
|
|
||||||
|
|
||||||
im = (Im*)ctx;
|
|
||||||
if(im->simpleactive){
|
|
||||||
parentim->get_preedit_string(ctx, str, attrs, cursor_pos);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(str)
|
|
||||||
*str = g_strdup(im->pre);
|
|
||||||
if(attrs){
|
|
||||||
*attrs = pango_attr_list_new();
|
|
||||||
if(im->prelen > 0){
|
|
||||||
u = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
|
|
||||||
u->start_index = 0;
|
|
||||||
u->end_index = im->prelen;
|
|
||||||
pango_attr_list_insert(*attrs, u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(cursor_pos)
|
|
||||||
*cursor_pos = g_utf8_strlen(im->pre, -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
reset(GtkIMContext *ctx)
|
reset(GtkIMContext *ctx)
|
||||||
{
|
{
|
||||||
if(parentim->reset != NULL)
|
|
||||||
parentim->reset(ctx);
|
|
||||||
((Im*)ctx)->simpleactive = 0;
|
|
||||||
sendreset((Im*)ctx);
|
sendreset((Im*)ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
focusout(GtkIMContext *ctx)
|
focusout(GtkIMContext *ctx)
|
||||||
{
|
{
|
||||||
Im *im;
|
sendreset((Im*)ctx);
|
||||||
|
|
||||||
im = (Im*)ctx;
|
|
||||||
if(parentim->focus_out != NULL)
|
|
||||||
parentim->focus_out(ctx);
|
|
||||||
im->simpleactive = 0;
|
|
||||||
/* Closing the connection releases the engine. */
|
|
||||||
sendreset(im);
|
|
||||||
srvclose(im);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setusepreedit(GtkIMContext *ctx, gboolean use)
|
finalize(GObject *obj)
|
||||||
{
|
|
||||||
Im *im;
|
|
||||||
unsigned char buf[Ipcreqsz];
|
|
||||||
char commit[Ipcfieldmax+1];
|
|
||||||
char pre[Ipcfieldmax+1];
|
|
||||||
Ipcresp resp;
|
|
||||||
|
|
||||||
im = (Im*)ctx;
|
|
||||||
use = use != FALSE;
|
|
||||||
if(parentim->set_use_preedit != NULL)
|
|
||||||
parentim->set_use_preedit(ctx, use);
|
|
||||||
if(im->usepreedit == use)
|
|
||||||
return;
|
|
||||||
im->usepreedit = use;
|
|
||||||
if(!use)
|
|
||||||
setpreedit(im, "", 0);
|
|
||||||
if(im->fd < 0 || !im->ext)
|
|
||||||
return;
|
|
||||||
ipcpackcap(buf, use);
|
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
|
||||||
readresp(im, use, commit, pre, &resp) < 0 ||
|
|
||||||
!resp.eaten){
|
|
||||||
srvclose(im);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(use)
|
|
||||||
setpreedit(im, pre, resp.preeditlen);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
setclientwindow(GtkIMContext *ctx, GdkWindow *win)
|
|
||||||
{
|
|
||||||
Im *im;
|
|
||||||
|
|
||||||
im = (Im*)ctx;
|
|
||||||
if(win != NULL && gdk_window_is_destroyed(win))
|
|
||||||
win = NULL;
|
|
||||||
if(parentim->set_client_window != NULL)
|
|
||||||
parentim->set_client_window(ctx, win);
|
|
||||||
if(im->win == win)
|
|
||||||
return;
|
|
||||||
if(win != NULL)
|
|
||||||
g_object_ref(win);
|
|
||||||
dropwindow(im);
|
|
||||||
im->win = win;
|
|
||||||
if(sendcaret(im) < 0)
|
|
||||||
srvclose(im);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
setcursorlocation(GtkIMContext *ctx, GdkRectangle *area)
|
|
||||||
{
|
|
||||||
Im *im;
|
|
||||||
|
|
||||||
im = (Im*)ctx;
|
|
||||||
if(parentim->set_cursor_location != NULL)
|
|
||||||
parentim->set_cursor_location(ctx, area);
|
|
||||||
if(im->win != NULL && gdk_window_is_destroyed(im->win))
|
|
||||||
dropwindow(im);
|
|
||||||
if(area == NULL || im->win == NULL)
|
|
||||||
im->cursorvalid = 0;
|
|
||||||
else{
|
|
||||||
im->cursor = *area;
|
|
||||||
im->cursorvalid = 1;
|
|
||||||
}
|
|
||||||
if(sendcaret(im) < 0)
|
|
||||||
srvclose(im);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
isprivate(GtkInputPurpose purpose)
|
|
||||||
{
|
|
||||||
return purpose == GTK_INPUT_PURPOSE_PASSWORD ||
|
|
||||||
purpose == GTK_INPUT_PURPOSE_PIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
purposechanged(GObject *obj, GParamSpec *pspec, gpointer data)
|
|
||||||
{
|
|
||||||
Im *im;
|
|
||||||
GtkInputPurpose purpose;
|
|
||||||
int private;
|
|
||||||
|
|
||||||
(void)pspec;
|
|
||||||
(void)data;
|
|
||||||
im = (Im*)obj;
|
|
||||||
g_object_get(obj, "input-purpose", &purpose, NULL);
|
|
||||||
private = isprivate(purpose);
|
|
||||||
if(private == im->private)
|
|
||||||
return;
|
|
||||||
im->private = private;
|
|
||||||
if(private)
|
|
||||||
reset(GTK_IM_CONTEXT(im));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
dispose(GObject *obj)
|
|
||||||
{
|
{
|
||||||
Im *im;
|
Im *im;
|
||||||
|
|
||||||
im = (Im*)obj;
|
im = (Im*)obj;
|
||||||
srvdrop(im, 0);
|
if(im->fd >= 0)
|
||||||
dropwindow(im);
|
close(im->fd);
|
||||||
parentobject->dispose(obj);
|
G_OBJECT_CLASS(g_type_class_peek_parent(G_OBJECT_GET_CLASS(obj)))->finalize(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
init(Im *im)
|
init(Im *im)
|
||||||
{
|
{
|
||||||
GtkInputPurpose purpose;
|
|
||||||
|
|
||||||
im->fd = -1;
|
im->fd = -1;
|
||||||
im->usepreedit = 1;
|
|
||||||
im->pre[0] = '\0';
|
|
||||||
g_object_get(im, "input-purpose", &purpose, NULL);
|
|
||||||
im->private = isprivate(purpose);
|
|
||||||
g_signal_connect(im, "notify::input-purpose",
|
|
||||||
G_CALLBACK(purposechanged), NULL);
|
|
||||||
g_signal_connect(im, "commit", G_CALLBACK(simplecommit), im);
|
|
||||||
g_signal_connect(im, "preedit-end", G_CALLBACK(simpleend), im);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -550,16 +170,10 @@ classinit(ImClass *klass)
|
|||||||
|
|
||||||
ic = GTK_IM_CONTEXT_CLASS(klass);
|
ic = GTK_IM_CONTEXT_CLASS(klass);
|
||||||
oc = G_OBJECT_CLASS(klass);
|
oc = G_OBJECT_CLASS(klass);
|
||||||
parentim = g_type_class_peek_parent(klass);
|
|
||||||
parentobject = G_OBJECT_CLASS(parentim);
|
|
||||||
ic->filter_keypress = kpress;
|
ic->filter_keypress = kpress;
|
||||||
ic->get_preedit_string = getpreedit;
|
|
||||||
ic->reset = reset;
|
ic->reset = reset;
|
||||||
ic->focus_out = focusout;
|
ic->focus_out = focusout;
|
||||||
ic->set_use_preedit = setusepreedit;
|
oc->finalize = finalize;
|
||||||
ic->set_client_window = setclientwindow;
|
|
||||||
ic->set_cursor_location = setcursorlocation;
|
|
||||||
oc->dispose = dispose;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const GtkIMContextInfo info = {
|
static const GtkIMContextInfo info = {
|
||||||
@@ -584,8 +198,7 @@ im_module_init(GTypeModule *mod)
|
|||||||
0,
|
0,
|
||||||
(GInstanceInitFunc)init,
|
(GInstanceInitFunc)init,
|
||||||
};
|
};
|
||||||
imtype = g_type_module_register_type(mod, GTK_TYPE_IM_CONTEXT_SIMPLE,
|
imtype = g_type_module_register_type(mod, GTK_TYPE_IM_CONTEXT, "strans-gtk", &ti, 0);
|
||||||
"strans-gtk", &ti, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
G_MODULE_EXPORT void
|
G_MODULE_EXPORT void
|
||||||
|
|||||||
129
hash.c
Normal file
129
hash.c
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
Tagsize = sizeof(Hnode),
|
||||||
|
};
|
||||||
|
|
||||||
|
static uvlong
|
||||||
|
hash(Str *s)
|
||||||
|
{
|
||||||
|
uvlong h;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
h = 7;
|
||||||
|
for(i = 0; i < s->n; i++)
|
||||||
|
h = h*31 + s->r[i];
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
Hmap*
|
||||||
|
hmapalloc(int nbuckets, int size)
|
||||||
|
{
|
||||||
|
void *store;
|
||||||
|
Hmap *h;
|
||||||
|
int nsz;
|
||||||
|
|
||||||
|
nsz = Tagsize + size;
|
||||||
|
store = emalloc(sizeof(*h) + nbuckets * nsz);
|
||||||
|
h = store;
|
||||||
|
h->nbs = nbuckets;
|
||||||
|
h->nsz = nsz;
|
||||||
|
h->len = h->cap = nbuckets;
|
||||||
|
h->nodes = (uchar*)store + sizeof(*h);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
keycmp(Hnode *n, Str *key)
|
||||||
|
{
|
||||||
|
char buf[256];
|
||||||
|
int len;
|
||||||
|
|
||||||
|
len = stoutf(key, buf, sizeof(buf));
|
||||||
|
if(n->klen != len)
|
||||||
|
return 1;
|
||||||
|
return memcmp(n->key, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
Hnode*
|
||||||
|
hmapget(Hmap *h, Str *key)
|
||||||
|
{
|
||||||
|
Hnode *n;
|
||||||
|
uchar *v;
|
||||||
|
|
||||||
|
v = h->nodes + (hash(key) % h->nbs) * h->nsz;
|
||||||
|
for(;;){
|
||||||
|
n = (Hnode*)v;
|
||||||
|
if(n->filled && keycmp(n, key) == 0)
|
||||||
|
return n;
|
||||||
|
if(n->next == 0)
|
||||||
|
break;
|
||||||
|
v = h->nodes + n->next * h->nsz;
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char*
|
||||||
|
sdup(Str *s)
|
||||||
|
{
|
||||||
|
char buf[256];
|
||||||
|
char *p;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
n = stoutf(s, buf, sizeof(buf));
|
||||||
|
p = emalloc(n + 1);
|
||||||
|
memmove(p, buf, n);
|
||||||
|
p[n] = '\0';
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
hmapset(Hmap **store, Str *key, Str *val)
|
||||||
|
{
|
||||||
|
Hnode *n;
|
||||||
|
uchar *v;
|
||||||
|
Hmap *h;
|
||||||
|
int next;
|
||||||
|
vlong diff;
|
||||||
|
|
||||||
|
h = *store;
|
||||||
|
v = h->nodes + (hash(key) % h->nbs) * h->nsz;
|
||||||
|
for(;;){
|
||||||
|
n = (Hnode*)v;
|
||||||
|
next = n->next;
|
||||||
|
if(n->filled == 0)
|
||||||
|
goto replace;
|
||||||
|
if(keycmp(n, key) == 0)
|
||||||
|
goto replace;
|
||||||
|
if(next == 0)
|
||||||
|
break;
|
||||||
|
v = h->nodes + next * h->nsz;
|
||||||
|
}
|
||||||
|
if(h->cap == h->len){
|
||||||
|
diff = v - h->nodes;
|
||||||
|
h->cap *= 2;
|
||||||
|
*store = erealloc(*store, sizeof(*h) + h->cap * h->nsz);
|
||||||
|
h = *store;
|
||||||
|
h->nodes = (uchar*)*store + sizeof(*h);
|
||||||
|
v = h->nodes + diff;
|
||||||
|
n = (Hnode*)v;
|
||||||
|
}
|
||||||
|
n->next = h->len;
|
||||||
|
memset(h->nodes + h->len * h->nsz, 0, h->nsz);
|
||||||
|
h->len++;
|
||||||
|
v = h->nodes + n->next * h->nsz;
|
||||||
|
n = (Hnode*)v;
|
||||||
|
replace:
|
||||||
|
if(n->filled == 0){
|
||||||
|
n->key = sdup(key);
|
||||||
|
n->klen = strlen(n->key);
|
||||||
|
n->filled = 1;
|
||||||
|
}
|
||||||
|
n->next = next;
|
||||||
|
if(val->n > 0){
|
||||||
|
n->val = sdup(val);
|
||||||
|
n->vlen = strlen(n->val);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
451
ipc.c
451
ipc.c
@@ -1,451 +0,0 @@
|
|||||||
#define _POSIX_C_SOURCE 200809L
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/un.h>
|
|
||||||
#include "ipc.h"
|
|
||||||
|
|
||||||
static int64_t
|
|
||||||
nowms(void)
|
|
||||||
{
|
|
||||||
struct timespec ts;
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
||||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t
|
|
||||||
deadline(void)
|
|
||||||
{
|
|
||||||
return nowms() + Ipcwaitms;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
waitfd(int fd, short events, int64_t until)
|
|
||||||
{
|
|
||||||
struct pollfd pfd;
|
|
||||||
int64_t left;
|
|
||||||
int n;
|
|
||||||
|
|
||||||
pfd.fd = fd;
|
|
||||||
pfd.events = events;
|
|
||||||
for(;;){
|
|
||||||
left = until - nowms();
|
|
||||||
if(left <= 0){
|
|
||||||
errno = ETIMEDOUT;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
n = poll(&pfd, 1, left);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n < 0)
|
|
||||||
return -1;
|
|
||||||
if(n == 0){
|
|
||||||
errno = ETIMEDOUT;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
readwait(int fd, void *buf, size_t n, int64_t until)
|
|
||||||
{
|
|
||||||
unsigned char *p;
|
|
||||||
ssize_t r;
|
|
||||||
|
|
||||||
p = buf;
|
|
||||||
while(n > 0){
|
|
||||||
r = recv(fd, p, n, MSG_DONTWAIT);
|
|
||||||
if(r < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){
|
|
||||||
if(waitfd(fd, POLLIN, until) < 0)
|
|
||||||
return -1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(r < 0)
|
|
||||||
return -1;
|
|
||||||
if(r == 0){
|
|
||||||
errno = ECONNRESET;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
p += r;
|
|
||||||
n -= r;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
putlen(unsigned char p[Ipclensz], size_t n)
|
|
||||||
{
|
|
||||||
p[0] = n;
|
|
||||||
p[1] = n >> 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t
|
|
||||||
getlen(const unsigned char p[Ipclensz])
|
|
||||||
{
|
|
||||||
return p[0] | (p[1] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
put32(unsigned char *p, int32_t v)
|
|
||||||
{
|
|
||||||
uint32_t u;
|
|
||||||
|
|
||||||
u = v;
|
|
||||||
p[0] = u;
|
|
||||||
p[1] = u >> 8;
|
|
||||||
p[2] = u >> 16;
|
|
||||||
p[3] = u >> 24;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t
|
|
||||||
get32(const unsigned char *p)
|
|
||||||
{
|
|
||||||
return (int32_t)((uint32_t)p[0] |
|
|
||||||
((uint32_t)p[1] << 8) |
|
|
||||||
((uint32_t)p[2] << 16) |
|
|
||||||
((uint32_t)p[3] << 24));
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
ipcpath(char *dst, size_t cap)
|
|
||||||
{
|
|
||||||
const char *dir, *sep;
|
|
||||||
int n;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A nonblocking AF_UNIX connect completes at once or fails with EAGAIN
|
|
||||||
* when the daemon's backlog is full; the socket stays nonblocking because
|
|
||||||
* every transfer below polls with a deadline.
|
|
||||||
*/
|
|
||||||
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|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
|
|
||||||
if(fd < 0)
|
|
||||||
return -1;
|
|
||||||
if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0){
|
|
||||||
e = errno;
|
|
||||||
close(fd);
|
|
||||||
errno = e;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Engine key for an X keysym and its Unicode value: printable characters
|
|
||||||
* as themselves, the function keysyms 0xff00-0xffff as Kspec+offset, and
|
|
||||||
* no key at all for a modifier. The keypad keys and Shift+Tab
|
|
||||||
* (ISO_Left_Tab) fold onto their plain counterparts.
|
|
||||||
*/
|
|
||||||
uint32_t
|
|
||||||
ipckeysym(uint32_t sym, uint32_t unicode)
|
|
||||||
{
|
|
||||||
if(unicode >= ' ' && unicode != 0x7f)
|
|
||||||
return unicode;
|
|
||||||
if(sym >= 0xffe1 && sym <= 0xffee)
|
|
||||||
return 0;
|
|
||||||
switch(sym){
|
|
||||||
case 0xfe20:
|
|
||||||
case 0xff89:
|
|
||||||
return Ktab;
|
|
||||||
case 0xff8d:
|
|
||||||
return Kret;
|
|
||||||
case 0xff97:
|
|
||||||
return Kup;
|
|
||||||
case 0xff99:
|
|
||||||
return Kdown;
|
|
||||||
case 0xff9a:
|
|
||||||
return Kpgup;
|
|
||||||
case 0xff9b:
|
|
||||||
return Kpgdown;
|
|
||||||
}
|
|
||||||
if(sym >= 0xff00 && sym <= 0xffff)
|
|
||||||
return Kspec + (sym - 0xff00);
|
|
||||||
return unicode;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t
|
|
||||||
ipcmod(uint32_t state)
|
|
||||||
{
|
|
||||||
uint32_t m;
|
|
||||||
|
|
||||||
m = state & Mmask;
|
|
||||||
if(state & Msupervirt)
|
|
||||||
m |= Msuper;
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ipcpackreq(unsigned char req[Ipcreqsz], int want, uint32_t mod,
|
|
||||||
uint32_t key)
|
|
||||||
{
|
|
||||||
req[0] = want ? Ipcreqwant : 0;
|
|
||||||
req[1] = mod;
|
|
||||||
req[2] = key;
|
|
||||||
req[3] = key >> 8;
|
|
||||||
req[4] = key >> 16;
|
|
||||||
req[5] = key >> 24;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ipcpackreset(unsigned char req[Ipcreqsz], int want)
|
|
||||||
{
|
|
||||||
memset(req, 0, Ipcreqsz);
|
|
||||||
req[0] = Ipcreqreset | (want ? Ipcreqwant : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* An old server sees this reserved frame as a key-zero request. */
|
|
||||||
void
|
|
||||||
ipcpackcap(unsigned char req[Ipcreqsz], int want)
|
|
||||||
{
|
|
||||||
memset(req, 0, Ipcreqsz);
|
|
||||||
req[0] = Ipcext | (want ? Ipcreqwant : 0);
|
|
||||||
req[1] = Ipcversion;
|
|
||||||
req[2] = Ipcopcap;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ipcpackcaret(unsigned char req[Ipccaretsz], int valid, int32_t x,
|
|
||||||
int32_t y, int32_t h)
|
|
||||||
{
|
|
||||||
memset(req, 0, Ipccaretsz);
|
|
||||||
req[0] = Ipcext;
|
|
||||||
req[1] = Ipcversion;
|
|
||||||
req[2] = Ipcopcaret;
|
|
||||||
req[3] = valid != 0;
|
|
||||||
if(valid){
|
|
||||||
put32(req + 4, x);
|
|
||||||
put32(req + 8, y);
|
|
||||||
put32(req + 12, h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ipcpacksurround(unsigned char req[Ipcreqsz], size_t n)
|
|
||||||
{
|
|
||||||
memset(req, 0, Ipcreqsz);
|
|
||||||
req[0] = Ipcext;
|
|
||||||
req[1] = Ipcversion;
|
|
||||||
req[2] = Ipcopsurround;
|
|
||||||
putlen(req + 4, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
ipcsurroundlen(const unsigned char req[Ipcreqsz])
|
|
||||||
{
|
|
||||||
return getlen(req + 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
|
||||||
uint32_t *key)
|
|
||||||
{
|
|
||||||
*want = (req[0] & Ipcreqwant) != 0;
|
|
||||||
*mod = req[1] & Mmask;
|
|
||||||
*key = (uint32_t)req[2] |
|
|
||||||
((uint32_t)req[3] << 8) |
|
|
||||||
((uint32_t)req[4] << 16) |
|
|
||||||
((uint32_t)req[5] << 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
ipcreqtype(const unsigned char req[Ipcreqsz])
|
|
||||||
{
|
|
||||||
if((req[0] & Ipcext) == 0)
|
|
||||||
return Ipckey;
|
|
||||||
if(req[1] != Ipcversion)
|
|
||||||
return Ipcunknown;
|
|
||||||
switch(req[2]){
|
|
||||||
case Ipcopcap:
|
|
||||||
if((req[0] & ~(Ipcext|Ipcreqwant)) != 0 ||
|
|
||||||
req[3] != 0 || req[4] != 0 || req[5] != 0)
|
|
||||||
return Ipcunknown;
|
|
||||||
return Ipccap;
|
|
||||||
case Ipcopcaret:
|
|
||||||
if(req[0] != Ipcext || req[3] > 1)
|
|
||||||
return Ipcunknown;
|
|
||||||
return Ipccaret;
|
|
||||||
case Ipcopsurround:
|
|
||||||
if(req[0] != Ipcext || req[3] != 0 ||
|
|
||||||
getlen(req + 4) > Ipcfieldmax)
|
|
||||||
return Ipcunknown;
|
|
||||||
return Ipcsurround;
|
|
||||||
}
|
|
||||||
return Ipcunknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
ipcunpackcaret(const unsigned char req[Ipccaretsz], int *valid,
|
|
||||||
int32_t *x, int32_t *y, int32_t *h)
|
|
||||||
{
|
|
||||||
if(ipcreqtype(req) != Ipccaret || get32(req + 12) < 0)
|
|
||||||
return -1;
|
|
||||||
*valid = req[3];
|
|
||||||
*x = *valid ? get32(req + 4) : 0;
|
|
||||||
*y = *valid ? get32(req + 8) : 0;
|
|
||||||
*h = *valid ? get32(req + 12) : 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
ipcreqreset(const unsigned char req[Ipcreqsz])
|
|
||||||
{
|
|
||||||
return (req[0] & Ipcreqreset) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
ipcpackresp(unsigned char *dst, size_t cap, int eaten, int del,
|
|
||||||
const char *commit, size_t ncommit, const char *preedit, size_t npreedit,
|
|
||||||
int want)
|
|
||||||
{
|
|
||||||
size_t n;
|
|
||||||
|
|
||||||
if(ncommit > Ipcfieldmax || npreedit > Ipcfieldmax ||
|
|
||||||
del < 0 || del > 0xff)
|
|
||||||
return -1;
|
|
||||||
n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0);
|
|
||||||
if(cap < n)
|
|
||||||
return -1;
|
|
||||||
dst[0] = eaten != 0;
|
|
||||||
dst[1] = del;
|
|
||||||
putlen(dst + 2, ncommit);
|
|
||||||
memcpy(dst + Ipcresphdrsz, commit, ncommit);
|
|
||||||
if(want){
|
|
||||||
putlen(dst + Ipcresphdrsz + ncommit, npreedit);
|
|
||||||
memcpy(dst + Ipcresphdrsz + ncommit + Ipclensz, preedit, npreedit);
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
ipcreadn(int fd, void *buf, size_t n)
|
|
||||||
{
|
|
||||||
unsigned char *p;
|
|
||||||
ssize_t r;
|
|
||||||
|
|
||||||
p = buf;
|
|
||||||
while(n > 0){
|
|
||||||
r = read(fd, p, n);
|
|
||||||
if(r < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(r <= 0)
|
|
||||||
return -1;
|
|
||||||
p += r;
|
|
||||||
n -= r;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
ipcsend(int fd, const void *buf, size_t n)
|
|
||||||
{
|
|
||||||
const unsigned char *p;
|
|
||||||
ssize_t r;
|
|
||||||
int64_t until;
|
|
||||||
|
|
||||||
p = buf;
|
|
||||||
until = deadline();
|
|
||||||
while(n > 0){
|
|
||||||
r = send(fd, p, n, MSG_NOSIGNAL|MSG_DONTWAIT);
|
|
||||||
if(r < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){
|
|
||||||
if(waitfd(fd, POLLOUT, until) < 0)
|
|
||||||
return -1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(r < 0)
|
|
||||||
return -1;
|
|
||||||
if(r == 0){
|
|
||||||
errno = EPIPE;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
p += r;
|
|
||||||
n -= r;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reads a length-prefixed field into dst[Ipcfieldmax+1]. */
|
|
||||||
static int
|
|
||||||
readfield(int fd, char *dst, int64_t until)
|
|
||||||
{
|
|
||||||
unsigned char len[Ipclensz];
|
|
||||||
size_t n;
|
|
||||||
|
|
||||||
if(readwait(fd, len, sizeof len, until) < 0)
|
|
||||||
return -1;
|
|
||||||
n = getlen(len);
|
|
||||||
if(n > Ipcfieldmax){
|
|
||||||
errno = EPROTO;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if(readwait(fd, dst, n, until) < 0)
|
|
||||||
return -1;
|
|
||||||
dst[n] = '\0';
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
ipcreadresp(int fd, int want, char *commit, char *preedit, Ipcresp *resp)
|
|
||||||
{
|
|
||||||
unsigned char head[2];
|
|
||||||
int64_t until;
|
|
||||||
int n;
|
|
||||||
|
|
||||||
commit[0] = '\0';
|
|
||||||
preedit[0] = '\0';
|
|
||||||
memset(resp, 0, sizeof *resp);
|
|
||||||
until = deadline();
|
|
||||||
if(readwait(fd, head, sizeof head, until) < 0)
|
|
||||||
return -1;
|
|
||||||
if(head[0] > 1){
|
|
||||||
errno = EPROTO;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
resp->eaten = head[0];
|
|
||||||
resp->del = head[1];
|
|
||||||
n = readfield(fd, commit, until);
|
|
||||||
if(n < 0)
|
|
||||||
return -1;
|
|
||||||
resp->commitlen = n;
|
|
||||||
if(!want)
|
|
||||||
return 0;
|
|
||||||
n = readfield(fd, preedit, until);
|
|
||||||
if(n < 0)
|
|
||||||
return -1;
|
|
||||||
resp->preeditlen = n;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
99
ipc.h
99
ipc.h
@@ -1,109 +1,20 @@
|
|||||||
#include <stddef.h>
|
// req: 4 bytes [type, mod, key lo, key hi]
|
||||||
#include <stdint.h>
|
// resp: 2+n bytes [type, n, ...]
|
||||||
|
|
||||||
/*
|
|
||||||
* Key request: [flags, modifiers, key byte 0, ..., key byte 3].
|
|
||||||
* The want flag asks for the preedit text: the client then draws it and
|
|
||||||
* the popup does not. The reset flag is a lifecycle reset, not Escape.
|
|
||||||
* The server identifies the connection as the engine owner; that identity is
|
|
||||||
* not sent on the wire.
|
|
||||||
* Only Mmask modifier bits are retained.
|
|
||||||
* Response: [eaten, take-back, commit-length-low, commit-length-high,
|
|
||||||
* commit...], followed, when requested, by
|
|
||||||
* [preedit-length-low, preedit-length-high, preedit...].
|
|
||||||
* Take-back is the count of runes of the client's own text, just before the
|
|
||||||
* cursor, to remove before inserting the commit.
|
|
||||||
* Lengths are little-endian byte counts; fields are at most Ipcfieldmax bytes,
|
|
||||||
* so callers read them into char[Ipcfieldmax+1].
|
|
||||||
*
|
|
||||||
* Capability control is six bytes: [0x80|want, version, 0, 0, 0, 0]; the
|
|
||||||
* server always answers it eaten, which tells the client that extension
|
|
||||||
* frames are understood.
|
|
||||||
* Caret control is sixteen bytes: [0x80, version, 1, valid, x, y, h],
|
|
||||||
* with the signed coordinates and height encoded as little-endian 32-bit words.
|
|
||||||
* Surrounding-text control is six bytes, [0x80, version, 2, 0, length-low,
|
|
||||||
* length-high], and then that many bytes of the text just before the cursor.
|
|
||||||
* It changes what rides along with the requests that follow and is not
|
|
||||||
* answered.
|
|
||||||
*/
|
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Ipcreqwant = 1<<0,
|
Kspec = 0xf000,
|
||||||
Ipcreqreset = 1<<1,
|
|
||||||
Ipcext = 1<<7,
|
|
||||||
Ipcversion = 2,
|
|
||||||
Ipcopcap = 0,
|
|
||||||
Ipcopcaret = 1,
|
|
||||||
Ipcopsurround = 2,
|
|
||||||
|
|
||||||
Kspec = 0x110000,
|
|
||||||
Kback = Kspec|0x08,
|
Kback = Kspec|0x08,
|
||||||
Ktab = Kspec|0x09,
|
Ktab = Kspec|0x09,
|
||||||
Kret = Kspec|0x0d,
|
Kret = Kspec|0x0d,
|
||||||
Kesc = Kspec|0x1b,
|
Kesc = Kspec|0x1b,
|
||||||
Kup = Kspec|0x52,
|
Kup = Kspec|0x52,
|
||||||
Kdown = Kspec|0x54,
|
Kdown = Kspec|0x54,
|
||||||
Kpgup = Kspec|0x55,
|
Kshift = Kspec|0xe1,
|
||||||
Kpgdown = Kspec|0x56,
|
Ksuper = Kspec|0xeb,
|
||||||
Khangul = Kspec|0x31,
|
|
||||||
Khanja = Kspec|0x34,
|
|
||||||
Kmuhenkan = Kspec|0x22,
|
|
||||||
Khenkan = Kspec|0x23,
|
|
||||||
Kkana = Kspec|0x27,
|
|
||||||
Kzenkaku = Kspec|0x2a,
|
|
||||||
|
|
||||||
/* X core modifier bits; GDK and IBus also flag Super at bit 26. */
|
|
||||||
Mshift = 1<<0,
|
Mshift = 1<<0,
|
||||||
Mctrl = 1<<2,
|
Mctrl = 1<<2,
|
||||||
Malt = 1<<3,
|
Malt = 1<<3,
|
||||||
Msuper = 1<<6,
|
Msuper = 1<<6,
|
||||||
Mmask = Mshift|Mctrl|Malt|Msuper,
|
|
||||||
Msupervirt = 1<<26,
|
|
||||||
|
|
||||||
Ipcreqsz = 6,
|
|
||||||
Ipccaretsz = 16,
|
|
||||||
Ipclensz = 2,
|
|
||||||
Ipcresphdrsz = 2 + Ipclensz,
|
|
||||||
Ipcfieldmax = 256,
|
|
||||||
Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax,
|
|
||||||
Ipcwaitms = 250,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Ipcunknown = -1,
|
|
||||||
Ipckey,
|
|
||||||
Ipccap,
|
|
||||||
Ipccaret,
|
|
||||||
Ipcsurround,
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct Ipcresp Ipcresp;
|
|
||||||
struct Ipcresp
|
|
||||||
{
|
|
||||||
int eaten;
|
|
||||||
int del; /* runes of the client's own text to take back first */
|
|
||||||
/* Bytes copied to each caller buffer, excluding its trailing NUL. */
|
|
||||||
size_t commitlen;
|
|
||||||
size_t preeditlen;
|
|
||||||
};
|
|
||||||
|
|
||||||
uint32_t ipckeysym(uint32_t, uint32_t);
|
|
||||||
uint32_t ipcmod(uint32_t);
|
|
||||||
void ipcpackreq(unsigned char[Ipcreqsz], int, uint32_t, uint32_t);
|
|
||||||
void ipcpackreset(unsigned char[Ipcreqsz], int);
|
|
||||||
void ipcpackcap(unsigned char[Ipcreqsz], int);
|
|
||||||
void ipcpackcaret(unsigned char[Ipccaretsz], int, int32_t, int32_t, int32_t);
|
|
||||||
void ipcpacksurround(unsigned char[Ipcreqsz], size_t);
|
|
||||||
size_t ipcsurroundlen(const unsigned char[Ipcreqsz]);
|
|
||||||
void ipcunpackreq(const unsigned char[Ipcreqsz], int*, uint32_t*, uint32_t*);
|
|
||||||
int ipcunpackcaret(const unsigned char[Ipccaretsz], int*, int32_t*, int32_t*, int32_t*);
|
|
||||||
int ipcreqtype(const unsigned char[Ipcreqsz]);
|
|
||||||
int ipcreqreset(const unsigned char[Ipcreqsz]);
|
|
||||||
int ipcpackresp(unsigned char*, size_t, int, int, const char*, size_t, const char*, size_t, int);
|
|
||||||
int ipcreadn(int, void*, size_t);
|
|
||||||
int ipcsend(int, const void*, size_t);
|
|
||||||
int ipcreadresp(int, int, char*, char*, Ipcresp*);
|
|
||||||
int ipcpath(char*, size_t);
|
|
||||||
int ipcconnect(void);
|
|
||||||
|
|||||||
102
ko.c
102
ko.c
@@ -4,6 +4,8 @@
|
|||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Sbase = 0xAC00,
|
Sbase = 0xAC00,
|
||||||
|
Jbase = 0x3131,
|
||||||
|
Jrange = 51,
|
||||||
Ncho = 19,
|
Ncho = 19,
|
||||||
Njung = 21,
|
Njung = 21,
|
||||||
Njong = 28,
|
Njong = 28,
|
||||||
@@ -33,21 +35,41 @@ static Rune jong[] = {
|
|||||||
L'ㅌ', L'ㅍ', L'ㅎ',
|
L'ㅌ', L'ㅍ', L'ㅎ',
|
||||||
};
|
};
|
||||||
|
|
||||||
/* A jamo's index in cho, jung, or jong, or -1; jong[0] is no jong. */
|
static int choidx[Jrange] = {
|
||||||
static int
|
[L'ㄱ'-Jbase] = 1, [L'ㄲ'-Jbase] = 2, [L'ㄴ'-Jbase] = 3,
|
||||||
idx(Rune *t, int n, Rune r)
|
[L'ㄷ'-Jbase] = 4, [L'ㄸ'-Jbase] = 5, [L'ㄹ'-Jbase] = 6,
|
||||||
{
|
[L'ㅁ'-Jbase] = 7, [L'ㅂ'-Jbase] = 8, [L'ㅃ'-Jbase] = 9,
|
||||||
int i;
|
[L'ㅅ'-Jbase] = 10, [L'ㅆ'-Jbase] = 11, [L'ㅇ'-Jbase] = 12,
|
||||||
|
[L'ㅈ'-Jbase] = 13, [L'ㅉ'-Jbase] = 14, [L'ㅊ'-Jbase] = 15,
|
||||||
|
[L'ㅋ'-Jbase] = 16, [L'ㅌ'-Jbase] = 17, [L'ㅍ'-Jbase] = 18,
|
||||||
|
[L'ㅎ'-Jbase] = 19,
|
||||||
|
};
|
||||||
|
|
||||||
for(i = 0; i < n; i++)
|
static int jungidx[Jrange] = {
|
||||||
if(t[i] == r)
|
[L'ㅏ'-Jbase] = 1, [L'ㅐ'-Jbase] = 2, [L'ㅑ'-Jbase] = 3,
|
||||||
return i;
|
[L'ㅒ'-Jbase] = 4, [L'ㅓ'-Jbase] = 5, [L'ㅔ'-Jbase] = 6,
|
||||||
return -1;
|
[L'ㅕ'-Jbase] = 7, [L'ㅖ'-Jbase] = 8, [L'ㅗ'-Jbase] = 9,
|
||||||
}
|
[L'ㅘ'-Jbase] = 10, [L'ㅙ'-Jbase] = 11, [L'ㅚ'-Jbase] = 12,
|
||||||
|
[L'ㅛ'-Jbase] = 13, [L'ㅜ'-Jbase] = 14, [L'ㅝ'-Jbase] = 15,
|
||||||
|
[L'ㅞ'-Jbase] = 16, [L'ㅟ'-Jbase] = 17, [L'ㅠ'-Jbase] = 18,
|
||||||
|
[L'ㅡ'-Jbase] = 19, [L'ㅢ'-Jbase] = 20, [L'ㅣ'-Jbase] = 21,
|
||||||
|
};
|
||||||
|
|
||||||
#define Choidx(r) idx(cho, Ncho, r)
|
static int jongidx[Jrange] = {
|
||||||
#define Jungidx(r) idx(jung, Njung, r)
|
[L'ㄱ'-Jbase] = 2, [L'ㄲ'-Jbase] = 3, [L'ㄳ'-Jbase] = 4,
|
||||||
#define Jongidx(r) idx(jong, Njong, r)
|
[L'ㄴ'-Jbase] = 5, [L'ㄵ'-Jbase] = 6, [L'ㄶ'-Jbase] = 7,
|
||||||
|
[L'ㄷ'-Jbase] = 8, [L'ㄹ'-Jbase] = 9, [L'ㄺ'-Jbase] = 10,
|
||||||
|
[L'ㄻ'-Jbase] = 11, [L'ㄼ'-Jbase] = 12, [L'ㄽ'-Jbase] = 13,
|
||||||
|
[L'ㄾ'-Jbase] = 14, [L'ㄿ'-Jbase] = 15, [L'ㅀ'-Jbase] = 16,
|
||||||
|
[L'ㅁ'-Jbase] = 17, [L'ㅂ'-Jbase] = 18, [L'ㅄ'-Jbase] = 19,
|
||||||
|
[L'ㅅ'-Jbase] = 20, [L'ㅆ'-Jbase] = 21, [L'ㅇ'-Jbase] = 22,
|
||||||
|
[L'ㅈ'-Jbase] = 23, [L'ㅊ'-Jbase] = 24, [L'ㅋ'-Jbase] = 25,
|
||||||
|
[L'ㅌ'-Jbase] = 26, [L'ㅍ'-Jbase] = 27, [L'ㅎ'-Jbase] = 28,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define Choidx(r) (choidx[(r) - Jbase] - 1)
|
||||||
|
#define Jungidx(r) (jungidx[(r) - Jbase] - 1)
|
||||||
|
#define Jongidx(r) (jongidx[(r) - Jbase] - 1)
|
||||||
|
|
||||||
static Rune jamomap[128] = {
|
static Rune jamomap[128] = {
|
||||||
['r'] = L'ㄱ', ['R'] = L'ㄲ',
|
['r'] = L'ㄱ', ['R'] = L'ㄲ',
|
||||||
@@ -167,10 +189,6 @@ issyl(Rune r)
|
|||||||
return r >= Sbase && r < Sbase + Ncho*Njung*Njong;
|
return r >= Sbase && r < Sbase + Ncho*Njung*Njong;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* The pending text is at most one syllable; a key either extends it,
|
|
||||||
* commits it and starts another, or commits it and passes through.
|
|
||||||
*/
|
|
||||||
Emit
|
Emit
|
||||||
transko(Im *im, Rune c)
|
transko(Im *im, Rune c)
|
||||||
{
|
{
|
||||||
@@ -181,52 +199,44 @@ transko(Im *im, Rune c)
|
|||||||
memset(&e, 0, sizeof e);
|
memset(&e, 0, sizeof e);
|
||||||
jm = keytojamo(c);
|
jm = keytojamo(c);
|
||||||
if(jm == 0){
|
if(jm == 0){
|
||||||
e.s = im->pre;
|
if(im->pre.n > 0){
|
||||||
|
e.s = im->pre;
|
||||||
|
sclear(&im->pre);
|
||||||
|
}
|
||||||
|
sputr(&e.s, c);
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
e.eat = 1;
|
e.eat = 1;
|
||||||
last = slastr(&im->pre);
|
last = slastr(&im->pre);
|
||||||
if(last == 0){
|
if(last == 0){
|
||||||
sputr(&e.next, jm);
|
sputr(&e.next, jm);
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!issyl(last)){
|
if(!issyl(last)){
|
||||||
ci = Choidx(last);
|
ci = Choidx(last);
|
||||||
ji = Jungidx(jm);
|
ji = Jungidx(jm);
|
||||||
if(ci >= 0 && ji >= 0){
|
if(ci >= 0 && ji >= 0){
|
||||||
|
spopr(&im->pre);
|
||||||
sputr(&e.next, compose(ci, ji, 0));
|
sputr(&e.next, compose(ci, ji, 0));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
/* A vowel typed first is reordered under its consonant. */
|
if(Jungidx(last) >= 0 && ji >= 0){
|
||||||
if(Jungidx(last) >= 0 && Choidx(jm) >= 0){
|
comb = combine(cvow, nelem(cvow), last, jm);
|
||||||
sputr(&e.next, compose(Choidx(jm), Jungidx(last), 0));
|
if(comb){
|
||||||
return e;
|
spopr(&im->pre);
|
||||||
}
|
sputr(&e.next, comb);
|
||||||
/* Two vowels or two consonants may be one jamo, ㅘ or ㄳ. */
|
return e;
|
||||||
comb = combine(cvow, nelem(cvow), last, jm);
|
}
|
||||||
if(comb == 0)
|
|
||||||
comb = combine(cjong, nelem(cjong), last, jm);
|
|
||||||
if(comb){
|
|
||||||
sputr(&e.next, comb);
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
if(ji >= 0 && splitpair(cjong, nelem(cjong), last, &stay, &next)){
|
|
||||||
sputr(&e.s, stay);
|
|
||||||
sputr(&e.next, compose(Choidx(next), ji, 0));
|
|
||||||
return e;
|
|
||||||
}
|
}
|
||||||
e.s = im->pre;
|
e.s = im->pre;
|
||||||
sputr(&e.next, jm);
|
sputr(&e.next, jm);
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
decompose(last, &ci, &ji, &joi);
|
decompose(last, &ci, &ji, &joi);
|
||||||
|
|
||||||
if(joi == 0){
|
if(joi == 0){
|
||||||
ni = Jongidx(jm);
|
ni = Jongidx(jm);
|
||||||
if(ni > 0){
|
if(ni > 0){
|
||||||
|
spopr(&im->pre);
|
||||||
sputr(&e.next, compose(ci, ji, ni));
|
sputr(&e.next, compose(ci, ji, ni));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -235,17 +245,18 @@ transko(Im *im, Rune c)
|
|||||||
comb = combine(cvow, nelem(cvow), jung[ji], jm);
|
comb = combine(cvow, nelem(cvow), jung[ji], jm);
|
||||||
if(comb){
|
if(comb){
|
||||||
ni = Jungidx(comb);
|
ni = Jungidx(comb);
|
||||||
|
spopr(&im->pre);
|
||||||
sputr(&e.next, compose(ci, ni, 0));
|
sputr(&e.next, compose(ci, ni, 0));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(joi > 0){
|
if(joi > 0){
|
||||||
comb = combine(cjong, nelem(cjong), jong[joi], jm);
|
comb = combine(cjong, nelem(cjong), jong[joi], jm);
|
||||||
if(comb){
|
if(comb){
|
||||||
ni = Jongidx(comb);
|
ni = Jongidx(comb);
|
||||||
if(ni > 0){
|
if(ni > 0){
|
||||||
|
spopr(&im->pre);
|
||||||
sputr(&e.next, compose(ci, ji, ni));
|
sputr(&e.next, compose(ci, ji, ni));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -256,19 +267,20 @@ transko(Im *im, Rune c)
|
|||||||
if(splitpair(cjong, nelem(cjong), jc, &stay, &next)){
|
if(splitpair(cjong, nelem(cjong), jc, &stay, &next)){
|
||||||
si = Jongidx(stay);
|
si = Jongidx(stay);
|
||||||
ni = Choidx(next);
|
ni = Choidx(next);
|
||||||
|
spopr(&im->pre);
|
||||||
sputr(&e.s, compose(ci, ji, si));
|
sputr(&e.s, compose(ci, ji, si));
|
||||||
sputr(&e.next, compose(ni, vi, 0));
|
sputr(&e.next, compose(ni, vi, 0));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
ni = Choidx(jc);
|
ni = Choidx(jc);
|
||||||
if(ni >= 0){
|
if(ni >= 0){
|
||||||
|
spopr(&im->pre);
|
||||||
sputr(&e.s, compose(ci, ji, 0));
|
sputr(&e.s, compose(ci, ji, 0));
|
||||||
sputr(&e.next, compose(ni, vi, 0));
|
sputr(&e.next, compose(ni, vi, 0));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e.s = im->pre;
|
e.s = im->pre;
|
||||||
sputr(&e.next, jm);
|
sputr(&e.next, jm);
|
||||||
return e;
|
return e;
|
||||||
@@ -283,10 +295,12 @@ backko(Im *im)
|
|||||||
|
|
||||||
last = slastr(&im->pre);
|
last = slastr(&im->pre);
|
||||||
if(!issyl(last)){
|
if(!issyl(last)){
|
||||||
spopr(&im->pre);
|
if(splitpair(cvow, nelem(cvow), last, &stay, &next)){
|
||||||
if(splitpair(cvow, nelem(cvow), last, &stay, &next) ||
|
spopr(&im->pre);
|
||||||
splitpair(cjong, nelem(cjong), last, &stay, &next))
|
|
||||||
sputr(&im->pre, stay);
|
sputr(&im->pre, stay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
spopr(&im->pre);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
decompose(last, &c, &j, &jo);
|
decompose(last, &c, &j, &jo);
|
||||||
|
|||||||
47
main.c
47
main.c
@@ -3,11 +3,20 @@
|
|||||||
|
|
||||||
Channel *drawc;
|
Channel *drawc;
|
||||||
Channel *keyc;
|
Channel *keyc;
|
||||||
|
Channel *dictreqc;
|
||||||
|
Channel *dictresc;
|
||||||
|
char *fontdir;
|
||||||
|
|
||||||
|
int
|
||||||
|
threadmaybackground(void)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
usage(void)
|
usage(void)
|
||||||
{
|
{
|
||||||
fprint(2, "usage: strans mapdir\n");
|
fprint(2, "usage: strans mapdir fontdir\n");
|
||||||
threadexitsall("usage");
|
threadexitsall("usage");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,9 +38,10 @@ emalloc(ulong n)
|
|||||||
{
|
{
|
||||||
void *p;
|
void *p;
|
||||||
|
|
||||||
p = mallocz(n, 1);
|
p = malloc(n);
|
||||||
if(p == nil)
|
if(p == nil)
|
||||||
die("out of memory");
|
die("out of memory");
|
||||||
|
memset(p, 0, n);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,31 +57,20 @@ erealloc(void *p, ulong n)
|
|||||||
void
|
void
|
||||||
threadmain(int argc, char **argv)
|
threadmain(int argc, char **argv)
|
||||||
{
|
{
|
||||||
char *display, *scale;
|
if(argc != 3)
|
||||||
|
|
||||||
if(argc != 2)
|
|
||||||
usage();
|
usage();
|
||||||
|
|
||||||
/* Whichever popup runs is sized from it, and setfont reads Fontsz. */
|
fontdir = argv[2];
|
||||||
scale = getenv("GDK_SCALE");
|
|
||||||
if(scale != nil && atoi(scale) > 1)
|
|
||||||
popupscale = min(atoi(scale), 4);
|
|
||||||
drawc = chancreate(sizeof(Drawcmd), 4);
|
drawc = chancreate(sizeof(Drawcmd), 4);
|
||||||
keyc = chancreate(sizeof(Keyreq), 0);
|
keyc = chancreate(sizeof(Keyreq), 0);
|
||||||
langinit(argv[1]);
|
dictreqc = chancreate(sizeof(Dictreq), 4);
|
||||||
composeinit();
|
dictresc = chancreate(sizeof(Dictres), 0);
|
||||||
srvinit();
|
mapinit(argv[1]);
|
||||||
|
dictinit(argv[1]);
|
||||||
|
proccreate(drawthread, nil, 16384);
|
||||||
proccreate(srvthread, nil, 16384);
|
proccreate(srvthread, nil, 16384);
|
||||||
proccreate(ibusthread, nil, 32768);
|
threadcreate(dictthread, nil, 16384);
|
||||||
/* One popup per session: the Wayland frontend draws its own, so the
|
threadcreate(imthread, nil, 16384);
|
||||||
* X11 one and the XIM it serves stand down. */
|
|
||||||
if(getenv("WAYLAND_DISPLAY") != nil && wlinit())
|
threadexits(nil);
|
||||||
proccreate(wlthread, nil, 32768);
|
|
||||||
else{
|
|
||||||
proccreate(drawthread, nil, 16384);
|
|
||||||
display = getenv("DISPLAY");
|
|
||||||
if(display != nil && display[0] != '\0')
|
|
||||||
proccreate(ximthread, nil, 32768);
|
|
||||||
}
|
|
||||||
imthread(nil);
|
|
||||||
}
|
}
|
||||||
|
|||||||
140
map/README
140
map/README
@@ -1,130 +1,12 @@
|
|||||||
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
## Korean Hanja and symbol data
|
grabskkdicts
|
||||||
|
for(d in skkdicts/SKK-JISYO.*)
|
||||||
`hanja.src` and `mssymbol.src` are the tracked, reviewable sources for what
|
<$d skk2ktrans >$d.jisho
|
||||||
the Hanja search converts. Every data row is a result, a tab, and its
|
|
||||||
Hangul reading. A Hanja reads as a syllable or a word; a symbol reads as
|
|
||||||
the lone consonant a Korean keyboard's 한자 key offers it under:
|
|
||||||
|
|
||||||
```
|
|
||||||
漢 한
|
|
||||||
漢字 한자
|
|
||||||
※ ㅁ
|
|
||||||
```
|
|
||||||
|
|
||||||
`mkhanja` validates that contract and groups the rows of both sources by
|
|
||||||
their reading to produce the runtime dictionary. The two keyspaces do not
|
|
||||||
meet: a reading is either syllables or one jamo. Candidate order follows
|
|
||||||
source order. The sources keep all retained pairs for review; the
|
|
||||||
generated dictionary stores the first 128 candidates per reading because
|
|
||||||
that is the engine's lookup limit.
|
|
||||||
|
|
||||||
The sources are derived from libhangul release tag `libhangul-0.2.0`. The
|
|
||||||
annotated tag object is `20afc38922e3595ee3ed5b186f2ea05afe663763`, its
|
|
||||||
peeled commit is `41c702f5d3581325b646ef6249f1f641b0427ae0`, and
|
|
||||||
`data/hanja/hanja.txt` and `data/hanja/mssymbol.txt` have blob IDs
|
|
||||||
`199cfd70c4b306257ac2e018714a1285f7cf0ed3` and
|
|
||||||
`31c4e63d74293b6a759d4a2005cd1e7333746631`. Import and regenerate them
|
|
||||||
from the repository root with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl -L https://raw.githubusercontent.com/libhangul/libhangul/41c702f5d3581325b646ef6249f1f641b0427ae0/data/hanja/hanja.txt -o upstream
|
|
||||||
printf '%s %s\n' dd44dcc856cf542b1022d0f39c2e9b9f8805fdcc5923be80f04849ed97ce0996 upstream | sha256sum -c -
|
|
||||||
map/libhangul2hanja upstream >map/hanja.src
|
|
||||||
curl -L https://raw.githubusercontent.com/libhangul/libhangul/41c702f5d3581325b646ef6249f1f641b0427ae0/data/hanja/mssymbol.txt -o upstream
|
|
||||||
printf '%s %s\n' b685a4ebe2716b25eb29c42f6da6493716ecd78b604b44a33420987d21948e99 upstream | sha256sum -c -
|
|
||||||
map/libhangul2hanja upstream >map/mssymbol.src
|
|
||||||
map/mkhanja >map/hanja.dict
|
|
||||||
```
|
|
||||||
|
|
||||||
`libhangul2hanja` keeps a syllable reading only when its value is Hanja the
|
|
||||||
popup can draw: U+3400–U+4DBF, U+4E00–U+9FFF, or U+F900–U+FAFF. Thus mixed
|
|
||||||
values and supplementary-plane ideographs are omitted. It keeps a jamo
|
|
||||||
reading, U+3131–U+314E, only when its value is one rune the popup can draw
|
|
||||||
and a candidate row can carry — so `mssymbol.txt`'s ideographic space and
|
|
||||||
soft hyphen are omitted, leaving 985 of its 987 rows, and a Hanja under a
|
|
||||||
jamo reading is omitted as noise. The import preserves each upstream
|
|
||||||
license header and row order. The retained data is BSD 3-Clause licensed
|
|
||||||
by Choe Hwanjin; the complete text is in
|
|
||||||
`LICENSES/BSD-3-Clause-libhangul-hanja.txt`.
|
|
||||||
|
|
||||||
## Emoji and symbol data
|
|
||||||
|
|
||||||
`symbol.src` is hand-written: a symbol, a tab, and its ASCII aliases, kept
|
|
||||||
so that a bare `1`-`9` picks the matching superscript or subscript from a
|
|
||||||
`^` or `_` prefix search. `emoji.src` is generated: every fully-qualified
|
|
||||||
emoji of Unicode's `emoji-test.txt` except the skin-tone variants, with
|
|
||||||
its CLDR names and keywords in English, Korean, and Japanese. `mkemoji`
|
|
||||||
folds the aliases, adds each Japanese one in the other kana so a query
|
|
||||||
typed in either mode finds it, and writes one row per alias to
|
|
||||||
`emoji.dict`; the engine searches that dictionary by prefix, so the rows
|
|
||||||
carry no prefixes.
|
|
||||||
|
|
||||||
The sources are Emoji 17.0 and CLDR 48.2.0. Regenerate them from the
|
|
||||||
repository root with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl -L https://www.unicode.org/Public/17.0.0/emoji/emoji-test.txt -o emoji-test.txt
|
|
||||||
printf '%s %s\n' 1d8a944f88d7952f7ef7c5167fef3c67995bcae24543949710231b03a201acda emoji-test.txt | sha256sum -c -
|
|
||||||
for l in en ko ja; do
|
|
||||||
curl -L https://raw.githubusercontent.com/unicode-org/cldr-json/48.2.0/cldr-json/cldr-annotations-full/annotations/$l/annotations.json -o $l.json
|
|
||||||
curl -L https://raw.githubusercontent.com/unicode-org/cldr-json/48.2.0/cldr-json/cldr-annotations-derived-full/annotationsDerived/$l/annotations.json -o $l-derived.json
|
|
||||||
done
|
|
||||||
map/cldr2emoji emoji-test.txt en.json en-derived.json ko.json ko-derived.json ja.json ja-derived.json >map/emoji.src
|
|
||||||
map/mkemoji >map/emoji.dict
|
|
||||||
```
|
|
||||||
|
|
||||||
The data is under the Unicode License v3; the complete text is in
|
|
||||||
`LICENSES/Unicode-3.0.txt`.
|
|
||||||
|
|
||||||
## Japanese romaji
|
|
||||||
|
|
||||||
`hira.map` and `kata.map` are Mozc's default romaji table, one row per key,
|
|
||||||
in gojūon order; `kata.map` is the same table in Katakana, and
|
|
||||||
`make verify-map` checks that the two have the same keys. A doubled
|
|
||||||
consonant, or `tch`, maps to っ and keeps the consonant pending; the engine
|
|
||||||
knows that rule, the table only lists the rows.
|
|
||||||
|
|
||||||
## Japanese dictionary data
|
|
||||||
|
|
||||||
`kanji.dict` is the historical dictionary bundled with strans. Its own header
|
|
||||||
identifies it as the SKK Medium dictionary, version 8.1 of May 24, 1995,
|
|
||||||
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 used for runtime or validation.
|
|
||||||
The normal build does not install map data.
|
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Write emoji.src: every fully-qualified emoji of Unicode's emoji-test.txt
|
|
||||||
but the skin-tone variants, each with its CLDR names and keywords in the
|
|
||||||
languages given, as result-first TAB-separated rows for mkemoji."""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def emoji(path):
|
|
||||||
out = []
|
|
||||||
for line in path.open(encoding="utf-8"):
|
|
||||||
if "; fully-qualified" not in line:
|
|
||||||
continue
|
|
||||||
cps = [int(cp, 16) for cp in line.split(";")[0].split()]
|
|
||||||
if any(0x1F3FB <= cp <= 0x1F3FF for cp in cps):
|
|
||||||
continue
|
|
||||||
out.append("".join(map(chr, cps)))
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def annotations(path):
|
|
||||||
"""CLDR annotations.json, plain or derived: emoji -> names, keywords."""
|
|
||||||
root = json.load(path.open(encoding="utf-8"))
|
|
||||||
table = root[next(iter(root))]["annotations"]
|
|
||||||
return {e: a.get("tts", []) + a.get("default", []) for e, a in table.items()}
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
|
||||||
if len(sys.argv) < 3:
|
|
||||||
print(f"usage: {sys.argv[0]} emoji-test.txt annotations.json...",
|
|
||||||
file=sys.stderr)
|
|
||||||
return 2
|
|
||||||
tables = [annotations(Path(arg)) for arg in sys.argv[2:]]
|
|
||||||
print("# Result first, then its CLDR names and keywords; see map/README.")
|
|
||||||
for e in emoji(Path(sys.argv[1])):
|
|
||||||
aliases = []
|
|
||||||
for table in tables:
|
|
||||||
for alias in table.get(e.replace("️", ""), table.get(e, [])):
|
|
||||||
alias = alias.strip()
|
|
||||||
if alias and alias not in aliases:
|
|
||||||
aliases.append(alias)
|
|
||||||
if aliases:
|
|
||||||
print(e + "\t" + "\t".join(aliases))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
19041
map/emoji.dict
19041
map/emoji.dict
File diff suppressed because it is too large
Load Diff
179
map/emoji.map
Normal file
179
map/emoji.map
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
! !
|
||||||
|
!! ⚠
|
||||||
|
!= ≠
|
||||||
|
* *
|
||||||
|
** ★
|
||||||
|
+ +
|
||||||
|
+- ±
|
||||||
|
- -
|
||||||
|
-> →
|
||||||
|
. .
|
||||||
|
.. ·
|
||||||
|
... …
|
||||||
|
./ ÷
|
||||||
|
: :
|
||||||
|
:( ☹
|
||||||
|
:) ☺
|
||||||
|
< <
|
||||||
|
<- ←
|
||||||
|
<= ≤
|
||||||
|
<> ≠
|
||||||
|
= =
|
||||||
|
== ≡
|
||||||
|
=> ⇒
|
||||||
|
> >
|
||||||
|
>= ≥
|
||||||
|
^ ^
|
||||||
|
^( ⁽
|
||||||
|
^) ⁾
|
||||||
|
^+ ⁺
|
||||||
|
^- ⁻
|
||||||
|
^= ⁼
|
||||||
|
_ _
|
||||||
|
_( ₍
|
||||||
|
_) ₎
|
||||||
|
_+ ₊
|
||||||
|
_- ₋
|
||||||
|
_= ₌
|
||||||
|
~ ~
|
||||||
|
~= ≈
|
||||||
|
^0 ⁰
|
||||||
|
_0 ₀
|
||||||
|
^1 ¹
|
||||||
|
_1 ₁
|
||||||
|
^2 ²
|
||||||
|
_2 ₂
|
||||||
|
<3 ♥
|
||||||
|
^3 ³
|
||||||
|
_3 ₃
|
||||||
|
^4 ⁴
|
||||||
|
_4 ₄
|
||||||
|
^5 ⁵
|
||||||
|
_5 ₅
|
||||||
|
^6 ⁶
|
||||||
|
_6 ₆
|
||||||
|
^7 ⁷
|
||||||
|
_7 ₇
|
||||||
|
^8 ⁸
|
||||||
|
_8 ₈
|
||||||
|
^9 ⁹
|
||||||
|
_9 ₉
|
||||||
|
_a ₐ
|
||||||
|
a a
|
||||||
|
al al
|
||||||
|
alp alp
|
||||||
|
alph alph
|
||||||
|
alpha α
|
||||||
|
b b
|
||||||
|
be be
|
||||||
|
bet bet
|
||||||
|
beta β
|
||||||
|
c c
|
||||||
|
ch ch
|
||||||
|
chi χ
|
||||||
|
d d
|
||||||
|
D D
|
||||||
|
de de
|
||||||
|
De Δ
|
||||||
|
deg °
|
||||||
|
del del
|
||||||
|
delt delt
|
||||||
|
delta δ
|
||||||
|
dn ↓
|
||||||
|
_e ₑ
|
||||||
|
e e
|
||||||
|
ep ep
|
||||||
|
eps ε
|
||||||
|
et et
|
||||||
|
eta η
|
||||||
|
g g
|
||||||
|
G G
|
||||||
|
ga ga
|
||||||
|
Ga Γ
|
||||||
|
gam gam
|
||||||
|
gamm gamm
|
||||||
|
gamma γ
|
||||||
|
^i ⁱ
|
||||||
|
i i
|
||||||
|
I I
|
||||||
|
II ∫
|
||||||
|
in in
|
||||||
|
inf ∞
|
||||||
|
io io
|
||||||
|
iot iot
|
||||||
|
iota ι
|
||||||
|
k k
|
||||||
|
ka ka
|
||||||
|
kap kap
|
||||||
|
kapp kapp
|
||||||
|
kappa κ
|
||||||
|
l l
|
||||||
|
L L
|
||||||
|
la la
|
||||||
|
La Λ
|
||||||
|
lam lam
|
||||||
|
lamb lamb
|
||||||
|
lambd lambd
|
||||||
|
lambda λ
|
||||||
|
m m
|
||||||
|
mu μ
|
||||||
|
mul ×
|
||||||
|
^n ⁿ
|
||||||
|
n n
|
||||||
|
nu ν
|
||||||
|
_o ₒ
|
||||||
|
o o
|
||||||
|
O O
|
||||||
|
om om
|
||||||
|
Om Ω
|
||||||
|
ome ome
|
||||||
|
omeg omeg
|
||||||
|
omega ω
|
||||||
|
oo ●
|
||||||
|
p p
|
||||||
|
P P
|
||||||
|
ph ph
|
||||||
|
Ph Φ
|
||||||
|
phi φ
|
||||||
|
pi π
|
||||||
|
Pi Π
|
||||||
|
PP ∏
|
||||||
|
ps ps
|
||||||
|
Ps Ψ
|
||||||
|
psi ψ
|
||||||
|
r r
|
||||||
|
rh rh
|
||||||
|
rho ρ
|
||||||
|
s s
|
||||||
|
S S
|
||||||
|
si si
|
||||||
|
Si Σ
|
||||||
|
sig sig
|
||||||
|
sigm sigm
|
||||||
|
sigma σ
|
||||||
|
sq √
|
||||||
|
SS ∑
|
||||||
|
t t
|
||||||
|
T T
|
||||||
|
ta ta
|
||||||
|
tau τ
|
||||||
|
th th
|
||||||
|
Th Θ
|
||||||
|
the the
|
||||||
|
thet thet
|
||||||
|
theta θ
|
||||||
|
u u
|
||||||
|
up ↑
|
||||||
|
ups υ
|
||||||
|
v v
|
||||||
|
vv ✓
|
||||||
|
_x ₓ
|
||||||
|
x x
|
||||||
|
X X
|
||||||
|
xi ξ
|
||||||
|
Xi Ξ
|
||||||
|
xx ✗
|
||||||
|
z z
|
||||||
|
ze ze
|
||||||
|
zet zet
|
||||||
|
zeta ζ
|
||||||
2014
map/emoji.src
2014
map/emoji.src
File diff suppressed because it is too large
Load Diff
0
map/english.map
Normal file
0
map/english.map
Normal file
2
map/grabskkdicts
Executable file
2
map/grabskkdicts
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/rc
|
||||||
|
git/clone https://github.com/skk-dev/dict skkdicts
|
||||||
0
map/hangul.map
Normal file
0
map/hangul.map
Normal file
187357
map/hanja.dict
187357
map/hanja.dict
File diff suppressed because it is too large
Load Diff
266539
map/hanja.src
266539
map/hanja.src
File diff suppressed because it is too large
Load Diff
369
map/hira.map
369
map/hira.map
@@ -1,321 +1,170 @@
|
|||||||
a あ
|
a あ
|
||||||
|
- ー
|
||||||
i い
|
i い
|
||||||
u う
|
u う
|
||||||
e え
|
e え
|
||||||
o お
|
o お
|
||||||
kya きゃ
|
|
||||||
kyi きぃ
|
|
||||||
kyu きゅ
|
|
||||||
kye きぇ
|
|
||||||
kyo きょ
|
|
||||||
ka か
|
ka か
|
||||||
ki き
|
|
||||||
ku く
|
|
||||||
ke け
|
|
||||||
ko こ
|
|
||||||
kwa くぁ
|
|
||||||
kwi くぃ
|
|
||||||
kwu くぅ
|
|
||||||
kwe くぇ
|
|
||||||
kwo くぉ
|
|
||||||
gya ぎゃ
|
|
||||||
gyi ぎぃ
|
|
||||||
gyu ぎゅ
|
|
||||||
gye ぎぇ
|
|
||||||
gyo ぎょ
|
|
||||||
ga が
|
ga が
|
||||||
|
ki き
|
||||||
|
kya きゃ
|
||||||
|
kyu きゅ
|
||||||
|
kyo きょ
|
||||||
gi ぎ
|
gi ぎ
|
||||||
|
gya ぎゃ
|
||||||
|
gyu ぎゅ
|
||||||
|
gyo ぎょ
|
||||||
|
ku く
|
||||||
gu ぐ
|
gu ぐ
|
||||||
|
ke け
|
||||||
ge げ
|
ge げ
|
||||||
|
ko こ
|
||||||
go ご
|
go ご
|
||||||
gwa ぐぁ
|
sa さ
|
||||||
gwi ぐぃ
|
za ざ
|
||||||
gwu ぐぅ
|
si し
|
||||||
gwe ぐぇ
|
|
||||||
gwo ぐぉ
|
|
||||||
sya しゃ
|
|
||||||
syi しぃ
|
|
||||||
syu しゅ
|
|
||||||
sye しぇ
|
|
||||||
syo しょ
|
|
||||||
sha しゃ
|
|
||||||
shi し
|
shi し
|
||||||
|
sha しゃ
|
||||||
shu しゅ
|
shu しゅ
|
||||||
she しぇ
|
she しぇ
|
||||||
sho しょ
|
sho しょ
|
||||||
sa さ
|
syo しょ
|
||||||
si し
|
zi ぢ
|
||||||
su す
|
|
||||||
se せ
|
|
||||||
so そ
|
|
||||||
swa すぁ
|
|
||||||
swi すぃ
|
|
||||||
swu すぅ
|
|
||||||
swe すぇ
|
|
||||||
swo すぉ
|
|
||||||
z/ ・
|
|
||||||
z. …
|
|
||||||
z, ‥
|
|
||||||
zh ←
|
|
||||||
zj ↓
|
|
||||||
zk ↑
|
|
||||||
zl →
|
|
||||||
z- 〜
|
|
||||||
z[ 『
|
|
||||||
z] 』
|
|
||||||
zya じゃ
|
|
||||||
zyi じぃ
|
|
||||||
zyu じゅ
|
|
||||||
zye じぇ
|
|
||||||
zyo じょ
|
|
||||||
zwa ずぁ
|
|
||||||
zwi ずぃ
|
|
||||||
zwu ずぅ
|
|
||||||
zwe ずぇ
|
|
||||||
zwo ずぉ
|
|
||||||
za ざ
|
|
||||||
zi じ
|
|
||||||
zu ず
|
|
||||||
ze ぜ
|
|
||||||
zo ぞ
|
|
||||||
ja じゃ
|
ja じゃ
|
||||||
ji じ
|
|
||||||
ju じゅ
|
ju じゅ
|
||||||
je じぇ
|
je じぇ
|
||||||
jo じょ
|
jo じょ
|
||||||
jya じゃ
|
su す
|
||||||
jyi じぃ
|
zu ず
|
||||||
jyu じゅ
|
se せ
|
||||||
jye じぇ
|
ze ぜ
|
||||||
jyo じょ
|
so そ
|
||||||
tya ちゃ
|
zo ぞ
|
||||||
tyi ちぃ
|
|
||||||
tyu ちゅ
|
|
||||||
tye ちぇ
|
|
||||||
tyo ちょ
|
|
||||||
tsa つぁ
|
|
||||||
tsi つぃ
|
|
||||||
tse つぇ
|
|
||||||
tso つぉ
|
|
||||||
tha てゃ
|
|
||||||
thi てぃ
|
|
||||||
t'i てぃ
|
|
||||||
thu てゅ
|
|
||||||
the てぇ
|
|
||||||
tho てょ
|
|
||||||
t'yu てゅ
|
|
||||||
twa とぁ
|
|
||||||
twi とぃ
|
|
||||||
twu とぅ
|
|
||||||
twe とぇ
|
|
||||||
two とぉ
|
|
||||||
t'u とぅ
|
|
||||||
ta た
|
ta た
|
||||||
|
da だ
|
||||||
ti ち
|
ti ち
|
||||||
|
chi ち
|
||||||
|
cha ちゃ
|
||||||
|
chu ちゅ
|
||||||
|
tyu ちゅ
|
||||||
|
cho ちょ
|
||||||
|
ji じ
|
||||||
tu つ
|
tu つ
|
||||||
tsu つ
|
tsu つ
|
||||||
te て
|
|
||||||
to と
|
|
||||||
dya ぢゃ
|
|
||||||
dyi ぢぃ
|
|
||||||
dyu ぢゅ
|
|
||||||
dye ぢぇ
|
|
||||||
dyo ぢょ
|
|
||||||
dha でゃ
|
|
||||||
dhi でぃ
|
|
||||||
d'i でぃ
|
|
||||||
dhu でゅ
|
|
||||||
dhe でぇ
|
|
||||||
dho でょ
|
|
||||||
d'yu でゅ
|
|
||||||
dwa どぁ
|
|
||||||
dwi どぃ
|
|
||||||
dwu どぅ
|
|
||||||
dwe どぇ
|
|
||||||
dwo どぉ
|
|
||||||
d'u どぅ
|
|
||||||
da だ
|
|
||||||
di ぢ
|
|
||||||
du づ
|
du づ
|
||||||
|
te て
|
||||||
de で
|
de で
|
||||||
|
to と
|
||||||
do ど
|
do ど
|
||||||
cha ちゃ
|
|
||||||
chi ち
|
|
||||||
chu ちゅ
|
|
||||||
che ちぇ
|
|
||||||
cho ちょ
|
|
||||||
cya ちゃ
|
|
||||||
cyi ちぃ
|
|
||||||
cyu ちゅ
|
|
||||||
cye ちぇ
|
|
||||||
cyo ちょ
|
|
||||||
ca か
|
|
||||||
ci し
|
|
||||||
cu く
|
|
||||||
ce せ
|
|
||||||
co こ
|
|
||||||
nya にゃ
|
|
||||||
nyi にぃ
|
|
||||||
nyu にゅ
|
|
||||||
nye にぇ
|
|
||||||
nyo にょ
|
|
||||||
n' ん
|
|
||||||
nn ん
|
|
||||||
n ん
|
|
||||||
na な
|
na な
|
||||||
ni に
|
ni に
|
||||||
|
nya にゃ
|
||||||
|
nyu にゅ
|
||||||
|
nyo にょ
|
||||||
nu ぬ
|
nu ぬ
|
||||||
ne ね
|
ne ね
|
||||||
no の
|
no の
|
||||||
hya ひゃ
|
|
||||||
hyi ひぃ
|
|
||||||
hyu ひゅ
|
|
||||||
hye ひぇ
|
|
||||||
hyo ひょ
|
|
||||||
hwa ふぁ
|
|
||||||
hwi ふぃ
|
|
||||||
hwe ふぇ
|
|
||||||
hwo ふぉ
|
|
||||||
hwyu ふゅ
|
|
||||||
ha は
|
ha は
|
||||||
hi ひ
|
|
||||||
hu ふ
|
|
||||||
he へ
|
|
||||||
ho ほ
|
|
||||||
fa ふぁ
|
|
||||||
fi ふぃ
|
|
||||||
fu ふ
|
|
||||||
fe ふぇ
|
|
||||||
fo ふぉ
|
|
||||||
fya ふゃ
|
|
||||||
fyu ふゅ
|
|
||||||
fyo ふょ
|
|
||||||
bya びゃ
|
|
||||||
byi びぃ
|
|
||||||
byu びゅ
|
|
||||||
bye びぇ
|
|
||||||
byo びょ
|
|
||||||
ba ば
|
ba ば
|
||||||
bi び
|
va ば
|
||||||
bu ぶ
|
|
||||||
be べ
|
|
||||||
bo ぼ
|
|
||||||
pya ぴゃ
|
|
||||||
pyi ぴぃ
|
|
||||||
pyu ぴゅ
|
|
||||||
pye ぴぇ
|
|
||||||
pyo ぴょ
|
|
||||||
pa ぱ
|
pa ぱ
|
||||||
|
hi ひ
|
||||||
|
hya ひゃ
|
||||||
|
hyu ひゅ
|
||||||
|
hyo ひょ
|
||||||
|
bi び
|
||||||
|
bya びゃ
|
||||||
|
byu びゅ
|
||||||
|
byo びょ
|
||||||
|
vi び
|
||||||
pi ぴ
|
pi ぴ
|
||||||
|
pya ぴゃ
|
||||||
|
pyu ぴゅ
|
||||||
|
pyo ぴょ
|
||||||
|
hu ふ
|
||||||
|
fu ふ
|
||||||
|
bu ぶ
|
||||||
|
vu ぶ
|
||||||
pu ぷ
|
pu ぷ
|
||||||
|
he へ
|
||||||
|
be べ
|
||||||
|
ve べ
|
||||||
pe ぺ
|
pe ぺ
|
||||||
|
ho ほ
|
||||||
|
bo ぼ
|
||||||
|
vo ぼ
|
||||||
po ぽ
|
po ぽ
|
||||||
mya みゃ
|
|
||||||
myi みぃ
|
|
||||||
myu みゅ
|
|
||||||
mye みぇ
|
|
||||||
myo みょ
|
|
||||||
ma ま
|
ma ま
|
||||||
mi み
|
mi み
|
||||||
|
mya みゃ
|
||||||
|
myu みゅ
|
||||||
|
myo みょ
|
||||||
mu む
|
mu む
|
||||||
me め
|
me め
|
||||||
mo も
|
mo も
|
||||||
ye いぇ
|
|
||||||
ya や
|
ya や
|
||||||
yu ゆ
|
yu ゆ
|
||||||
yo よ
|
yo よ
|
||||||
rya りゃ
|
|
||||||
ryi りぃ
|
|
||||||
ryu りゅ
|
|
||||||
rye りぇ
|
|
||||||
ryo りょ
|
|
||||||
ra ら
|
ra ら
|
||||||
ri り
|
ri り
|
||||||
|
rya りゃ
|
||||||
|
ryu りゅ
|
||||||
|
ryo りょ
|
||||||
ru る
|
ru る
|
||||||
re れ
|
re れ
|
||||||
ro ろ
|
ro ろ
|
||||||
wu う
|
|
||||||
wyi ゐ
|
|
||||||
wye ゑ
|
|
||||||
wa わ
|
wa わ
|
||||||
wi うぃ
|
wi ゐ
|
||||||
we うぇ
|
we ゑ
|
||||||
wo を
|
wo を
|
||||||
wha うぁ
|
n ん
|
||||||
whi うぃ
|
|
||||||
whu う
|
|
||||||
whe うぇ
|
|
||||||
who うぉ
|
|
||||||
va ゔぁ
|
|
||||||
vi ゔぃ
|
|
||||||
vu ゔ
|
|
||||||
ve ゔぇ
|
|
||||||
vo ゔぉ
|
|
||||||
vya ゔゃ
|
|
||||||
vyi ゔぃ
|
|
||||||
vyu ゔゅ
|
|
||||||
vye ゔぇ
|
|
||||||
vyo ゔょ
|
|
||||||
qa くぁ
|
|
||||||
qi くぃ
|
|
||||||
qu く
|
|
||||||
qe くぇ
|
|
||||||
qo くぉ
|
|
||||||
xn ん
|
xn ん
|
||||||
xa ぁ
|
xa ぁ
|
||||||
xi ぃ
|
xi ぃ
|
||||||
xu ぅ
|
xu ぅ
|
||||||
xe ぇ
|
xe ぇ
|
||||||
xo ぉ
|
xo ぉ
|
||||||
xyi ぃ
|
kka っか
|
||||||
xye ぇ
|
kki っき
|
||||||
xka ヵ
|
kkya っきゃ
|
||||||
xke ヶ
|
kkyu っきゅ
|
||||||
xtu っ
|
kkyo っきょ
|
||||||
xtsu っ
|
kku っく
|
||||||
xya ゃ
|
kke っけ
|
||||||
xyu ゅ
|
kko っこ
|
||||||
xyo ょ
|
ssa っさ
|
||||||
xwa ゎ
|
ssi っし
|
||||||
la ぁ
|
sshi っし
|
||||||
li ぃ
|
ssha っしゃ
|
||||||
lu ぅ
|
sshu っしゅ
|
||||||
le ぇ
|
sshe っしぇ
|
||||||
lo ぉ
|
ssho っしょ
|
||||||
lyi ぃ
|
ssyo っしょ
|
||||||
lye ぇ
|
ssu っす
|
||||||
lka ヵ
|
sse っせ
|
||||||
lke ヶ
|
sso っそ
|
||||||
ltu っ
|
tta った
|
||||||
ltsu っ
|
dda っだ
|
||||||
lya ゃ
|
tti っち
|
||||||
lyu ゅ
|
cchi っち
|
||||||
lyo ょ
|
ccha っちゃ
|
||||||
lwa ゎ
|
cchu っちゅ
|
||||||
qq っ
|
ttyu っちゅ
|
||||||
vv っ
|
ccho っちょ
|
||||||
ll っ
|
ttu っつ
|
||||||
xx っ
|
ttsu っつ
|
||||||
kk っ
|
ddu っづ
|
||||||
gg っ
|
tte って
|
||||||
ss っ
|
dde っで
|
||||||
zz っ
|
tto っと
|
||||||
jj っ
|
ddo っど
|
||||||
tt っ
|
ppa っぱ
|
||||||
tch っ
|
ppi っぴ
|
||||||
dd っ
|
ppya っぴゃ
|
||||||
hh っ
|
ppyu っぴゅ
|
||||||
ff っ
|
ppyo っぴょ
|
||||||
bb っ
|
ppu っぷ
|
||||||
pp っ
|
ppe っぺ
|
||||||
mm っ
|
ppo っぽ
|
||||||
yy っ
|
|
||||||
rr っ
|
|
||||||
ww っ
|
|
||||||
cc っ
|
|
||||||
- ー
|
|
||||||
~ 〜
|
|
||||||
. 。
|
. 。
|
||||||
, 、
|
, 、
|
||||||
[ 「
|
|
||||||
] 」
|
|
||||||
|
|||||||
28
map/judou.map
Normal file
28
map/judou.map
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
, ,
|
||||||
|
. 。
|
||||||
|
< 《
|
||||||
|
> 》
|
||||||
|
/ /
|
||||||
|
? ?
|
||||||
|
; ;
|
||||||
|
: :
|
||||||
|
\ 、
|
||||||
|
| ・
|
||||||
|
` `
|
||||||
|
~ 〜
|
||||||
|
! !
|
||||||
|
@ @
|
||||||
|
# #
|
||||||
|
$ ¥
|
||||||
|
& &
|
||||||
|
* *
|
||||||
|
( (
|
||||||
|
) )
|
||||||
|
- -
|
||||||
|
+ +
|
||||||
|
= =
|
||||||
|
[ 「
|
||||||
|
] 」
|
||||||
|
{ 『
|
||||||
|
} 』
|
||||||
@@ -60,6 +60,7 @@
|
|||||||
あたらs 新
|
あたらs 新
|
||||||
あつi 熱 暑 厚
|
あつi 熱 暑 厚
|
||||||
あつk 厚 熱 暑
|
あつk 厚 熱 暑
|
||||||
|
あつk 厚 熱 暑
|
||||||
あつs 暑
|
あつs 暑
|
||||||
あつm 集 厚
|
あつm 集 厚
|
||||||
あつかe 扱
|
あつかe 扱
|
||||||
@@ -349,6 +350,7 @@
|
|||||||
かわいr 可愛
|
かわいr 可愛
|
||||||
かんj 感
|
かんj 感
|
||||||
かんs 関
|
かんs 関
|
||||||
|
かんj 感
|
||||||
かんz 感
|
かんz 感
|
||||||
かんがe 考
|
かんがe 考
|
||||||
かんしゃs 感謝
|
かんしゃs 感謝
|
||||||
@@ -453,7 +455,7 @@
|
|||||||
こu 乞 請
|
こu 乞 請
|
||||||
こy 来
|
こy 来
|
||||||
こうj 高
|
こうj 高
|
||||||
こうしょう 鉱床 高尚 交渉
|
こうしょう 鉱床 高尚
|
||||||
こうぶつ 鉱物 好物
|
こうぶつ 鉱物 好物
|
||||||
こうりょs 考慮
|
こうりょs 考慮
|
||||||
こえt 超
|
こえt 超
|
||||||
@@ -663,6 +665,8 @@
|
|||||||
ただc 直
|
ただc 直
|
||||||
ただt 直
|
ただt 直
|
||||||
たとe 例
|
たとe 例
|
||||||
|
たおr 倒
|
||||||
|
たおr 倒
|
||||||
たのm 頼
|
たのm 頼
|
||||||
たのn 頼
|
たのn 頼
|
||||||
たのs 楽
|
たのs 楽
|
||||||
@@ -738,6 +742,7 @@
|
|||||||
つみあげr 積み上げ
|
つみあげr 積み上げ
|
||||||
つみあげt 積み上げ
|
つみあげt 積み上げ
|
||||||
つめt 冷
|
つめt 冷
|
||||||
|
つづr 綴
|
||||||
つよi 強
|
つよi 強
|
||||||
つよk 強
|
つよk 強
|
||||||
つらi 辛
|
つらi 辛
|
||||||
@@ -788,6 +793,7 @@
|
|||||||
とt 取 撮 採
|
とt 取 撮 採
|
||||||
とu 問
|
とu 問
|
||||||
とw 問
|
とw 問
|
||||||
|
とj 閉
|
||||||
といあw 問い合
|
といあw 問い合
|
||||||
とうj 投
|
とうj 投
|
||||||
とおi 遠
|
とおi 遠
|
||||||
@@ -907,12 +913,13 @@
|
|||||||
ねら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 残
|
||||||
@@ -1052,11 +1059,12 @@
|
|||||||
ぼうえいs 防衛
|
ぼうえいs 防衛
|
||||||
まc 待
|
まc 待
|
||||||
まi 舞
|
まi 舞
|
||||||
まj 交 混
|
まj 交
|
||||||
まk 負 巻
|
まk 負 巻
|
||||||
まs 増
|
まs 増
|
||||||
まt 待
|
まt 待
|
||||||
まu 舞
|
まu 舞
|
||||||
|
まj 混
|
||||||
まz 混 先
|
まz 混 先
|
||||||
まいr 参 詣
|
まいr 参 詣
|
||||||
まいt 参 詣
|
まいt 参 詣
|
||||||
@@ -1233,6 +1241,8 @@
|
|||||||
よわm 弱
|
よわm 弱
|
||||||
よわs 弱
|
よわs 弱
|
||||||
よわt 弱
|
よわt 弱
|
||||||
|
よろこb 喜 慶
|
||||||
|
よろこb 喜 慶
|
||||||
ろんj 論
|
ろんj 論
|
||||||
ろんz 論
|
ろんz 論
|
||||||
わk 分 湧
|
わk 分 湧
|
||||||
@@ -2257,6 +2267,7 @@
|
|||||||
がくわり 学割
|
がくわり 学割
|
||||||
がけ 崖
|
がけ 崖
|
||||||
がし 樫
|
がし 樫
|
||||||
|
かせい 火星
|
||||||
がそ 画素
|
がそ 画素
|
||||||
がそかん 画素間
|
がそかん 画素間
|
||||||
がそごと 画素毎
|
がそごと 画素毎
|
||||||
@@ -2982,6 +2993,7 @@
|
|||||||
こうしゃ 後者 公社 校舎
|
こうしゃ 後者 公社 校舎
|
||||||
こうしゅう 講習 公衆
|
こうしゅう 講習 公衆
|
||||||
こうしゅうかい 講習会
|
こうしゅうかい 講習会
|
||||||
|
こうしょう 交渉 鉱床
|
||||||
こうしん 更新 交信 後身
|
こうしん 更新 交信 後身
|
||||||
こうじ 麹 公示 高次 工事 孝二
|
こうじ 麹 公示 高次 工事 孝二
|
||||||
こうじつ 口実
|
こうじつ 口実
|
||||||
@@ -4790,7 +4802,7 @@
|
|||||||
たいきゅうりょく 耐久力
|
たいきゅうりょく 耐久力
|
||||||
たいきょく 対極
|
たいきょく 対極
|
||||||
たいきん 大金
|
たいきん 大金
|
||||||
たいく 体躯 体育
|
たいく 体躯
|
||||||
たいくつ 退屈
|
たいくつ 退屈
|
||||||
たいけい 体系 体型
|
たいけい 体系 体型
|
||||||
たいけん 体験
|
たいけん 体験
|
||||||
@@ -4937,6 +4949,8 @@
|
|||||||
たんらく 短絡
|
たんらく 短絡
|
||||||
だ 騨 駄 陀 楕 舵 柁 打 惰 妥 堕 唾 田 朶
|
だ 騨 駄 陀 楕 舵 柁 打 惰 妥 堕 唾 田 朶
|
||||||
だい 大 第 内 代 台 題 醍
|
だい 大 第 内 代 台 題 醍
|
||||||
|
たいく 体育
|
||||||
|
たいいく 体育
|
||||||
だいいち 第一
|
だいいち 第一
|
||||||
だいがく 大学
|
だいがく 大学
|
||||||
だいがくいん 大学院
|
だいがくいん 大学院
|
||||||
@@ -7300,6 +7314,7 @@
|
|||||||
よこう 予行 予稿
|
よこう 予行 予稿
|
||||||
よこうち 横内
|
よこうち 横内
|
||||||
よこく 予告
|
よこく 予告
|
||||||
|
ようたつ
|
||||||
よこて 横手
|
よこて 横手
|
||||||
よこはま 横浜
|
よこはま 横浜
|
||||||
よこもじ 横文字
|
よこもじ 横文字
|
||||||
@@ -7411,7 +7426,7 @@
|
|||||||
りゅうど 粒度
|
りゅうど 粒度
|
||||||
りゆう 理由
|
りゆう 理由
|
||||||
りょ 虜 旅 慮 侶
|
りょ 虜 旅 慮 侶
|
||||||
りょう 量 寮 両 領 陵 遼 諒 良 糧 稜 瞭 療 猟 涼 梁 料 凌 僚 亮 了 漁 粮 霊
|
りょう 量 寮 両 領 陵 遼 諒 良 糧 稜 瞭 療 猟 涼 梁 料 凌 僚 亮 了 漁 糧 粮 霊
|
||||||
りょうあし 両足
|
りょうあし 両足
|
||||||
りょういき 領域
|
りょういき 領域
|
||||||
りょうか 量化
|
りょうか 量化
|
||||||
|
|||||||
343
map/kata.map
343
map/kata.map
@@ -1,321 +1,142 @@
|
|||||||
a ア
|
a ア
|
||||||
|
- ー
|
||||||
i イ
|
i イ
|
||||||
u ウ
|
u ウ
|
||||||
e エ
|
e エ
|
||||||
o オ
|
o オ
|
||||||
kya キャ
|
|
||||||
kyi キィ
|
|
||||||
kyu キュ
|
|
||||||
kye キェ
|
|
||||||
kyo キョ
|
|
||||||
ka カ
|
ka カ
|
||||||
ki キ
|
|
||||||
ku ク
|
|
||||||
ke ケ
|
|
||||||
ko コ
|
|
||||||
kwa クァ
|
|
||||||
kwi クィ
|
|
||||||
kwu クゥ
|
|
||||||
kwe クェ
|
|
||||||
kwo クォ
|
|
||||||
gya ギャ
|
|
||||||
gyi ギィ
|
|
||||||
gyu ギュ
|
|
||||||
gye ギェ
|
|
||||||
gyo ギョ
|
|
||||||
ga ガ
|
ga ガ
|
||||||
|
ki キ
|
||||||
|
kya キャ
|
||||||
|
kyu キュ
|
||||||
|
kyo キョ
|
||||||
gi ギ
|
gi ギ
|
||||||
|
gya ギャ
|
||||||
|
gyu ギュ
|
||||||
|
gyo ギョ
|
||||||
|
ku ク
|
||||||
gu グ
|
gu グ
|
||||||
|
ke ケ
|
||||||
ge ゲ
|
ge ゲ
|
||||||
|
ko コ
|
||||||
go ゴ
|
go ゴ
|
||||||
gwa グァ
|
|
||||||
gwi グィ
|
|
||||||
gwu グゥ
|
|
||||||
gwe グェ
|
|
||||||
gwo グォ
|
|
||||||
sya シャ
|
|
||||||
syi シィ
|
|
||||||
syu シュ
|
|
||||||
sye シェ
|
|
||||||
syo ショ
|
|
||||||
sha シャ
|
|
||||||
shi シ
|
|
||||||
shu シュ
|
|
||||||
she シェ
|
|
||||||
sho ショ
|
|
||||||
sa サ
|
sa サ
|
||||||
si シ
|
|
||||||
su ス
|
|
||||||
se セ
|
|
||||||
so ソ
|
|
||||||
swa スァ
|
|
||||||
swi スィ
|
|
||||||
swu スゥ
|
|
||||||
swe スェ
|
|
||||||
swo スォ
|
|
||||||
z/ ・
|
|
||||||
z. …
|
|
||||||
z, ‥
|
|
||||||
zh ←
|
|
||||||
zj ↓
|
|
||||||
zk ↑
|
|
||||||
zl →
|
|
||||||
z- 〜
|
|
||||||
z[ 『
|
|
||||||
z] 』
|
|
||||||
zya ジャ
|
|
||||||
zyi ジィ
|
|
||||||
zyu ジュ
|
|
||||||
zye ジェ
|
|
||||||
zyo ジョ
|
|
||||||
zwa ズァ
|
|
||||||
zwi ズィ
|
|
||||||
zwu ズゥ
|
|
||||||
zwe ズェ
|
|
||||||
zwo ズォ
|
|
||||||
za ザ
|
za ザ
|
||||||
zi ジ
|
si シ
|
||||||
zu ズ
|
shi シ
|
||||||
ze ゼ
|
|
||||||
zo ゾ
|
|
||||||
ja ジャ
|
|
||||||
ji ジ
|
ji ジ
|
||||||
|
sha シャ
|
||||||
|
she シェ
|
||||||
|
shu シュ
|
||||||
|
je ジェ
|
||||||
|
sho ショ
|
||||||
|
syo ショ
|
||||||
|
ja ジャ
|
||||||
ju ジュ
|
ju ジュ
|
||||||
je ジェ
|
je ジェ
|
||||||
jo ジョ
|
jo ジョ
|
||||||
jya ジャ
|
su ス
|
||||||
jyi ジィ
|
zu ズ
|
||||||
jyu ジュ
|
se セ
|
||||||
jye ジェ
|
ze ゼ
|
||||||
jyo ジョ
|
so ソ
|
||||||
tya チャ
|
zo ゾ
|
||||||
tyi チィ
|
|
||||||
tyu チュ
|
|
||||||
tye チェ
|
|
||||||
tyo チョ
|
|
||||||
tsa ツァ
|
|
||||||
tsi ツィ
|
|
||||||
tse ツェ
|
|
||||||
tso ツォ
|
|
||||||
tha テャ
|
|
||||||
thi ティ
|
|
||||||
t'i ティ
|
|
||||||
thu テュ
|
|
||||||
the テェ
|
|
||||||
tho テョ
|
|
||||||
t'yu テュ
|
|
||||||
twa トァ
|
|
||||||
twi トィ
|
|
||||||
twu トゥ
|
|
||||||
twe トェ
|
|
||||||
two トォ
|
|
||||||
t'u トゥ
|
|
||||||
ta タ
|
ta タ
|
||||||
ti チ
|
|
||||||
tu ツ
|
|
||||||
tsu ツ
|
|
||||||
te テ
|
|
||||||
to ト
|
|
||||||
dya ヂャ
|
|
||||||
dyi ヂィ
|
|
||||||
dyu ヂュ
|
|
||||||
dye ヂェ
|
|
||||||
dyo ヂョ
|
|
||||||
dha デャ
|
|
||||||
dhi ディ
|
|
||||||
d'i ディ
|
|
||||||
dhu デュ
|
|
||||||
dhe デェ
|
|
||||||
dho デョ
|
|
||||||
d'yu デュ
|
|
||||||
dwa ドァ
|
|
||||||
dwi ドィ
|
|
||||||
dwu ドゥ
|
|
||||||
dwe ドェ
|
|
||||||
dwo ドォ
|
|
||||||
d'u ドゥ
|
|
||||||
da ダ
|
da ダ
|
||||||
di ヂ
|
ti ティ
|
||||||
du ヅ
|
|
||||||
de デ
|
|
||||||
do ド
|
|
||||||
cha チャ
|
|
||||||
chi チ
|
chi チ
|
||||||
|
zi ヂ
|
||||||
|
cha チャ
|
||||||
chu チュ
|
chu チュ
|
||||||
|
tyu チュ
|
||||||
che チェ
|
che チェ
|
||||||
cho チョ
|
cho チョ
|
||||||
cya チャ
|
tu ツ
|
||||||
cyi チィ
|
tsu ツ
|
||||||
cyu チュ
|
du ヅ
|
||||||
cye チェ
|
te テ
|
||||||
cyo チョ
|
de デ
|
||||||
ca カ
|
to ト
|
||||||
ci シ
|
do ド
|
||||||
cu ク
|
|
||||||
ce セ
|
|
||||||
co コ
|
|
||||||
nya ニャ
|
|
||||||
nyi ニィ
|
|
||||||
nyu ニュ
|
|
||||||
nye ニェ
|
|
||||||
nyo ニョ
|
|
||||||
n' ン
|
|
||||||
nn ン
|
|
||||||
n ン
|
|
||||||
na ナ
|
na ナ
|
||||||
ni ニ
|
ni ニ
|
||||||
|
nya ニャ
|
||||||
|
nyu ニュ
|
||||||
|
nyo ニョ
|
||||||
nu ヌ
|
nu ヌ
|
||||||
ne ネ
|
ne ネ
|
||||||
no ノ
|
no ノ
|
||||||
hya ヒャ
|
|
||||||
hyi ヒィ
|
|
||||||
hyu ヒュ
|
|
||||||
hye ヒェ
|
|
||||||
hyo ヒョ
|
|
||||||
hwa ファ
|
|
||||||
hwi フィ
|
|
||||||
hwe フェ
|
|
||||||
hwo フォ
|
|
||||||
hwyu フュ
|
|
||||||
ha ハ
|
ha ハ
|
||||||
hi ヒ
|
|
||||||
hu フ
|
|
||||||
he ヘ
|
|
||||||
ho ホ
|
|
||||||
fa ファ
|
|
||||||
fi フィ
|
|
||||||
fu フ
|
|
||||||
fe フェ
|
|
||||||
fo フォ
|
|
||||||
fya フャ
|
|
||||||
fyu フュ
|
|
||||||
fyo フョ
|
|
||||||
bya ビャ
|
|
||||||
byi ビィ
|
|
||||||
byu ビュ
|
|
||||||
bye ビェ
|
|
||||||
byo ビョ
|
|
||||||
ba バ
|
ba バ
|
||||||
bi ビ
|
|
||||||
bu ブ
|
|
||||||
be ベ
|
|
||||||
bo ボ
|
|
||||||
pya ピャ
|
|
||||||
pyi ピィ
|
|
||||||
pyu ピュ
|
|
||||||
pye ピェ
|
|
||||||
pyo ピョ
|
|
||||||
pa パ
|
pa パ
|
||||||
|
hi ヒ
|
||||||
|
hya ヒャ
|
||||||
|
hyu ヒュ
|
||||||
|
hyo ヒョ
|
||||||
|
bi ビ
|
||||||
|
bya ビャ
|
||||||
|
byu ビュ
|
||||||
|
byo ビョ
|
||||||
pi ピ
|
pi ピ
|
||||||
|
pya ピャ
|
||||||
|
pyu ピュ
|
||||||
|
pyo ピョ
|
||||||
|
hu フ
|
||||||
|
fu フ
|
||||||
|
bu ブ
|
||||||
pu プ
|
pu プ
|
||||||
|
he ヘ
|
||||||
|
be ベ
|
||||||
pe ペ
|
pe ペ
|
||||||
|
ho ホ
|
||||||
|
bo ボ
|
||||||
po ポ
|
po ポ
|
||||||
mya ミャ
|
|
||||||
myi ミィ
|
|
||||||
myu ミュ
|
|
||||||
mye ミェ
|
|
||||||
myo ミョ
|
|
||||||
ma マ
|
ma マ
|
||||||
mi ミ
|
mi ミ
|
||||||
|
mya ミャ
|
||||||
|
myu ミュ
|
||||||
|
myo ミョ
|
||||||
mu ム
|
mu ム
|
||||||
me メ
|
me メ
|
||||||
mo モ
|
mo モ
|
||||||
ye イェ
|
|
||||||
ya ヤ
|
ya ヤ
|
||||||
yu ユ
|
yu ユ
|
||||||
yo ヨ
|
yo ヨ
|
||||||
rya リャ
|
|
||||||
ryi リィ
|
|
||||||
ryu リュ
|
|
||||||
rye リェ
|
|
||||||
ryo リョ
|
|
||||||
ra ラ
|
ra ラ
|
||||||
ri リ
|
ri リ
|
||||||
|
rya リャ
|
||||||
|
ryu リュ
|
||||||
|
ryo リョ
|
||||||
ru ル
|
ru ル
|
||||||
re レ
|
re レ
|
||||||
ro ロ
|
ro ロ
|
||||||
wu ウ
|
|
||||||
wyi ヰ
|
|
||||||
wye ヱ
|
|
||||||
wa ワ
|
wa ワ
|
||||||
wi ウィ
|
wi ヰ
|
||||||
we ウェ
|
we ヱ
|
||||||
wo ヲ
|
wo ヲ
|
||||||
wha ウァ
|
n ン
|
||||||
whi ウィ
|
|
||||||
whu ウ
|
|
||||||
whe ウェ
|
|
||||||
who ウォ
|
|
||||||
va ヴァ
|
|
||||||
vi ヴィ
|
|
||||||
vu ヴ
|
|
||||||
ve ヴェ
|
|
||||||
vo ヴォ
|
|
||||||
vya ヴャ
|
|
||||||
vyi ヴィ
|
|
||||||
vyu ヴュ
|
|
||||||
vye ヴェ
|
|
||||||
vyo ヴョ
|
|
||||||
qa クァ
|
|
||||||
qi クィ
|
|
||||||
qu ク
|
|
||||||
qe クェ
|
|
||||||
qo クォ
|
|
||||||
xn ン
|
xn ン
|
||||||
|
v ヴ
|
||||||
xa ァ
|
xa ァ
|
||||||
xi ィ
|
xi ィ
|
||||||
xu ゥ
|
|
||||||
xe ェ
|
xe ェ
|
||||||
xo ォ
|
xo ォ
|
||||||
xyi ィ
|
cc ッ
|
||||||
xye ェ
|
dd ッ
|
||||||
xka ヵ
|
|
||||||
xke ヶ
|
|
||||||
xtu ッ
|
|
||||||
xtsu ッ
|
|
||||||
xya ャ
|
|
||||||
xyu ュ
|
|
||||||
xyo ョ
|
|
||||||
xwa ヮ
|
|
||||||
la ァ
|
|
||||||
li ィ
|
|
||||||
lu ゥ
|
|
||||||
le ェ
|
|
||||||
lo ォ
|
|
||||||
lyi ィ
|
|
||||||
lye ェ
|
|
||||||
lka ヵ
|
|
||||||
lke ヶ
|
|
||||||
ltu ッ
|
|
||||||
ltsu ッ
|
|
||||||
lya ャ
|
|
||||||
lyu ュ
|
|
||||||
lyo ョ
|
|
||||||
lwa ヮ
|
|
||||||
qq ッ
|
|
||||||
vv ッ
|
|
||||||
ll ッ
|
|
||||||
xx ッ
|
|
||||||
kk ッ
|
kk ッ
|
||||||
gg ッ
|
pp ッ
|
||||||
ss ッ
|
|
||||||
zz ッ
|
|
||||||
jj ッ
|
|
||||||
tt ッ
|
tt ッ
|
||||||
tch ッ
|
tch ッ
|
||||||
dd ッ
|
ss ッ
|
||||||
hh ッ
|
xn ン
|
||||||
ff ッ
|
di ディ
|
||||||
bb ッ
|
fa ファ
|
||||||
pp ッ
|
fi フィ
|
||||||
mm ッ
|
fe フェ
|
||||||
yy ッ
|
fo フォ
|
||||||
rr ッ
|
va ヴァ
|
||||||
ww ッ
|
vi ヴィ
|
||||||
cc ッ
|
ve ヴェ
|
||||||
- ー
|
vo ヴォ
|
||||||
~ 〜
|
|
||||||
. 。
|
. 。
|
||||||
, 、
|
, 、
|
||||||
[ 「
|
|
||||||
] 」
|
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Extract Hanja and symbol readings from libhangul data."""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import unicodedata
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def ishangul(s):
|
|
||||||
return s != "" and all(0xAC00 <= ord(c) <= 0xD7A3 for c in s)
|
|
||||||
|
|
||||||
|
|
||||||
def isjamo(s):
|
|
||||||
return len(s) == 1 and 0x3131 <= ord(s) <= 0x314E
|
|
||||||
|
|
||||||
|
|
||||||
def ishanja(s):
|
|
||||||
return s != "" and all(0x3400 <= ord(c) <= 0x4DBF
|
|
||||||
or 0x4E00 <= ord(c) <= 0x9FFF
|
|
||||||
or 0xF900 <= ord(c) <= 0xFAFF for c in s)
|
|
||||||
|
|
||||||
|
|
||||||
def issymbol(s):
|
|
||||||
"""One rune the popup can draw and a candidate row can carry: not a
|
|
||||||
space, which the row separates candidates with, and not a formatting
|
|
||||||
character, which would leave a blank candidate to pick."""
|
|
||||||
return (len(s) == 1 and not s.isspace() and not ishanja(s)
|
|
||||||
and not unicodedata.category(s).startswith("C"))
|
|
||||||
|
|
||||||
|
|
||||||
def keeps(reading, value):
|
|
||||||
"""A syllable reading gives Hanja; a jamo reading gives a symbol."""
|
|
||||||
if ishangul(reading):
|
|
||||||
return ishanja(value)
|
|
||||||
return isjamo(reading) and issymbol(value)
|
|
||||||
|
|
||||||
|
|
||||||
def extract(src, name):
|
|
||||||
comments = []
|
|
||||||
entries = []
|
|
||||||
seen = set()
|
|
||||||
leading = True
|
|
||||||
for lineno, raw in enumerate(src, 1):
|
|
||||||
line = raw.rstrip("\r\n")
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
if line.startswith("#"):
|
|
||||||
if leading:
|
|
||||||
comments.append(line.rstrip())
|
|
||||||
continue
|
|
||||||
leading = False
|
|
||||||
fields = line.split(":")
|
|
||||||
if len(fields) != 3:
|
|
||||||
raise ValueError(f"{name}:{lineno}: need key:value:comment")
|
|
||||||
reading, value, _ = fields
|
|
||||||
if not keeps(reading, value):
|
|
||||||
continue
|
|
||||||
pair = (value, reading)
|
|
||||||
if pair in seen:
|
|
||||||
raise ValueError(f"{name}:{lineno}: duplicate reading")
|
|
||||||
seen.add(pair)
|
|
||||||
entries.append(pair)
|
|
||||||
if not entries:
|
|
||||||
raise ValueError(f"{name}: no readings")
|
|
||||||
return comments, entries
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
sys.stdin.reconfigure(encoding="utf-8")
|
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
|
||||||
if len(sys.argv) > 2:
|
|
||||||
print(f"usage: {sys.argv[0]} [libhangul-hanja.txt]", file=sys.stderr)
|
|
||||||
return 2
|
|
||||||
src = sys.stdin
|
|
||||||
name = "<stdin>"
|
|
||||||
try:
|
|
||||||
if len(sys.argv) == 2:
|
|
||||||
name = sys.argv[1]
|
|
||||||
src = Path(name).open(encoding="utf-8")
|
|
||||||
comments, entries = extract(src, name)
|
|
||||||
for line in comments:
|
|
||||||
print(line)
|
|
||||||
if comments:
|
|
||||||
print()
|
|
||||||
for value, reading in entries:
|
|
||||||
print(f"{value}\t{reading}")
|
|
||||||
except (OSError, UnicodeError, ValueError) as error:
|
|
||||||
print(error, file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
finally:
|
|
||||||
if src is not sys.stdin:
|
|
||||||
src.close()
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
121
map/mkemoji
121
map/mkemoji
@@ -1,86 +1,39 @@
|
|||||||
#!/usr/bin/env python3
|
#!/bin/sh
|
||||||
"""Write emoji.dict to stdout from result-first UTF-8 TSV files."""
|
|
||||||
|
|
||||||
import sys
|
awk '
|
||||||
import unicodedata
|
BEGIN { FS = "\t" }
|
||||||
from pathlib import Path
|
/^$/ { next }
|
||||||
|
{
|
||||||
|
key = $1
|
||||||
|
val = $2
|
||||||
|
map[key] = val
|
||||||
|
for(i = 1; i < length(key); i++){
|
||||||
|
p = substr(key, 1, i)
|
||||||
|
if(!(p in map))
|
||||||
|
map[p] = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
for(k in map)
|
||||||
|
print k "\t" map[k]
|
||||||
|
}
|
||||||
|
' $1 | sort -k1,1 > emoji.map
|
||||||
|
|
||||||
|
awk '
|
||||||
SOURCES = [Path(__file__).with_name(name)
|
BEGIN { FS = "\t" }
|
||||||
for name in ("symbol.src", "emoji.src")]
|
/^$/ { next }
|
||||||
MAXRUNES = 64
|
{
|
||||||
MAXCANDIDATES = 128
|
key = $1
|
||||||
|
val = $2
|
||||||
|
for(i = 1; i <= length(key); i++){
|
||||||
HIRA = {c: c + 0x60 for c in range(0x3041, 0x3097)}
|
p = substr(key, 1, i)
|
||||||
KATA = {c: c - 0x60 for c in range(0x30A1, 0x30F7)}
|
dict[p] = dict[p] " " val
|
||||||
|
}
|
||||||
|
}
|
||||||
def fold(s):
|
END {
|
||||||
s = "".join(chr(ord(c) + 32) if "A" <= c <= "Z" else c for c in s)
|
for(p in dict){
|
||||||
return unicodedata.normalize("NFC", s)
|
sub(/^ /, "", dict[p])
|
||||||
|
print p "\t" dict[p]
|
||||||
|
}
|
||||||
def kana(alias):
|
}
|
||||||
"""The alias, and in the other kana where it has any: a query typed
|
' $1 | sort -k1,1 > emoji.dict
|
||||||
in either Japanese mode finds it."""
|
|
||||||
return {alias, alias.translate(HIRA), alias.translate(KATA)}
|
|
||||||
|
|
||||||
|
|
||||||
def hascontrol(s):
|
|
||||||
return any(unicodedata.category(c) == "Cc" for c in s)
|
|
||||||
|
|
||||||
|
|
||||||
def read(path):
|
|
||||||
entries = []
|
|
||||||
with path.open(encoding="utf-8") as src:
|
|
||||||
for lineno, raw in enumerate(src, 1):
|
|
||||||
line = raw.rstrip("\r\n")
|
|
||||||
if not line.strip():
|
|
||||||
continue
|
|
||||||
if "\t" not in line and line.lstrip().startswith("#"):
|
|
||||||
continue
|
|
||||||
fields = line.split("\t")
|
|
||||||
if len(fields) < 2:
|
|
||||||
raise ValueError(f"{path}:{lineno}: need a result and an alias")
|
|
||||||
result = unicodedata.normalize("NFC", fields[0])
|
|
||||||
if (not result or len(result) > MAXRUNES or hascontrol(result)
|
|
||||||
or any(c.isspace() for c in result)):
|
|
||||||
raise ValueError(f"{path}:{lineno}: bad result")
|
|
||||||
for field in fields[1:]:
|
|
||||||
alias = fold(field)
|
|
||||||
if (not alias or len(alias) > MAXRUNES or hascontrol(alias)
|
|
||||||
or alias != alias.strip() or alias.startswith(";")):
|
|
||||||
raise ValueError(f"{path}:{lineno}: bad alias")
|
|
||||||
entries.extend((result, a) for a in sorted(kana(alias)))
|
|
||||||
return entries
|
|
||||||
|
|
||||||
|
|
||||||
def build(entries):
|
|
||||||
"""One row per alias, in order of first appearance; the engine searches
|
|
||||||
the dictionary by prefix, so no prefix rows are needed."""
|
|
||||||
table = {}
|
|
||||||
for result, alias in entries:
|
|
||||||
values = table.setdefault(alias, [])
|
|
||||||
if result not in values:
|
|
||||||
values.append(result)
|
|
||||||
for alias, values in table.items():
|
|
||||||
yield f"{alias}\t{' '.join(values[:MAXCANDIDATES])}"
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
|
||||||
paths = [Path(arg) for arg in sys.argv[1:]] or SOURCES
|
|
||||||
try:
|
|
||||||
entries = [entry for path in paths for entry in read(path)]
|
|
||||||
for line in build(entries):
|
|
||||||
print(line)
|
|
||||||
except (OSError, UnicodeError, ValueError) as error:
|
|
||||||
print(error, file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
|
|||||||
96
map/mkhanja
96
map/mkhanja
@@ -1,96 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Write hanja.dict from result-per-row UTF-8 sources."""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import unicodedata
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
SOURCES = [Path(__file__).with_name(name)
|
|
||||||
for name in ("hanja.src", "mssymbol.src")]
|
|
||||||
MAXCANDIDATES = 128
|
|
||||||
|
|
||||||
|
|
||||||
def ishangul(s):
|
|
||||||
return s != "" and all(0xAC00 <= ord(c) <= 0xD7A3 for c in s)
|
|
||||||
|
|
||||||
|
|
||||||
def isjamo(s):
|
|
||||||
return len(s) == 1 and 0x3131 <= ord(s) <= 0x314E
|
|
||||||
|
|
||||||
|
|
||||||
def ishanja(s):
|
|
||||||
return s != "" and all(0x3400 <= ord(c) <= 0x4DBF
|
|
||||||
or 0x4E00 <= ord(c) <= 0x9FFF
|
|
||||||
or 0xF900 <= ord(c) <= 0xFAFF for c in s)
|
|
||||||
|
|
||||||
|
|
||||||
def issymbol(s):
|
|
||||||
"""One rune the popup can draw and a candidate row can carry: not a
|
|
||||||
space, which the row separates candidates with, and not a formatting
|
|
||||||
character, which would leave a blank candidate to pick."""
|
|
||||||
return (len(s) == 1 and not s.isspace() and not ishanja(s)
|
|
||||||
and not unicodedata.category(s).startswith("C"))
|
|
||||||
|
|
||||||
|
|
||||||
def read(path, table, seen):
|
|
||||||
"""Adds path's rows to table, keyed by reading, and returns its header."""
|
|
||||||
comments = []
|
|
||||||
leading = True
|
|
||||||
found = False
|
|
||||||
with path.open(encoding="utf-8") as src:
|
|
||||||
for lineno, raw in enumerate(src, 1):
|
|
||||||
line = raw.rstrip("\r\n")
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
if line.startswith("#"):
|
|
||||||
if leading:
|
|
||||||
comments.append(";;" + line[1:])
|
|
||||||
continue
|
|
||||||
leading = False
|
|
||||||
fields = line.split("\t")
|
|
||||||
if len(fields) != 2:
|
|
||||||
raise ValueError(f"{path}:{lineno}: need result<TAB>reading")
|
|
||||||
value, reading = fields
|
|
||||||
if ishangul(reading):
|
|
||||||
if not ishanja(value):
|
|
||||||
raise ValueError(f"{path}:{lineno}: need BMP Hanja")
|
|
||||||
elif isjamo(reading):
|
|
||||||
if not issymbol(value):
|
|
||||||
raise ValueError(f"{path}:{lineno}: need one symbol rune")
|
|
||||||
else:
|
|
||||||
raise ValueError(f"{path}:{lineno}: need a syllable or jamo "
|
|
||||||
"reading")
|
|
||||||
pair = (value, reading)
|
|
||||||
if pair in seen:
|
|
||||||
raise ValueError(f"{path}:{lineno}: duplicate reading")
|
|
||||||
seen.add(pair)
|
|
||||||
found = True
|
|
||||||
table.setdefault(reading, []).append(value)
|
|
||||||
if not found:
|
|
||||||
raise ValueError(f"{path}: no readings")
|
|
||||||
return comments
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
|
||||||
paths = [Path(arg) for arg in sys.argv[1:]] or SOURCES
|
|
||||||
table = {}
|
|
||||||
seen = set()
|
|
||||||
try:
|
|
||||||
comments = [line for path in paths for line in read(path, table, seen)]
|
|
||||||
for line in comments:
|
|
||||||
print(line)
|
|
||||||
if comments:
|
|
||||||
print()
|
|
||||||
for reading, candidates in table.items():
|
|
||||||
print(f"{reading}\t{' '.join(candidates[:MAXCANDIDATES])}")
|
|
||||||
except (OSError, UnicodeError, ValueError) as error:
|
|
||||||
print(error, file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
|
||||||
|
|
||||||
# tone marks: s=rising f=falling r=hook x=tilde j=dot
|
# tone marks: s=rising f=falling r=hook x=tilde j=dot
|
||||||
tone = {
|
tone = {
|
||||||
's': str.maketrans("aăâeêioôơuưy", "áắấéếíóốớúứý"),
|
's': str.maketrans("aăâeêioôơuưy", "áắấéếíóốớúứý"),
|
||||||
@@ -28,21 +24,21 @@ modcons = [
|
|||||||
("dd", "đ"),
|
("dd", "đ"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
upper = str.maketrans(
|
||||||
|
"aăâeêioôơuưyđáắấéếíóốớúứýàằầèềìòồờùừỳảẳẩẻểỉỏổởủửỷãẵẫẽễĩõỗỡũữỹạặậẹệịọộợụựỵ",
|
||||||
|
"AĂÂEÊIOÔƠUƯYĐÁẮẤÉẾÍÓỐỚÚỨÝÀẰẦÈỀÌÒỒỜÙỪỲẢẲẨẺỂỈỎỔỞỦỬỶÃẴẪẼỄĨÕỖỠŨỮỸẠẶẬẸỆỊỌỘỢỤỰỴ")
|
||||||
|
|
||||||
def addtone(v, t):
|
def addtone(v, t):
|
||||||
return v.translate(tone[t])
|
return v.translate(tone[t])
|
||||||
|
|
||||||
entries = []
|
def emit(input, output):
|
||||||
|
print(f"{input}\t{output}")
|
||||||
def initialupper(s):
|
def up(s):
|
||||||
return s[0].upper() + s[1:]
|
c = s[0].translate(upper)
|
||||||
|
if c == s[0]:
|
||||||
def emit(src, dst):
|
c = s[0].upper()
|
||||||
entries.append((src, dst))
|
return c + s[1:]
|
||||||
print(f"{src}\t{dst}")
|
print(f"{up(input)}\t{up(output)}")
|
||||||
usrc = initialupper(src)
|
|
||||||
udst = initialupper(dst)
|
|
||||||
if (usrc, udst) != (src, dst):
|
|
||||||
print(f"{usrc}\t{udst}")
|
|
||||||
|
|
||||||
def vowel1():
|
def vowel1():
|
||||||
for v in "aeiouy":
|
for v in "aeiouy":
|
||||||
@@ -55,15 +51,11 @@ def vowel2():
|
|||||||
tab = [
|
tab = [
|
||||||
("oa", "oa", "a"), ("oe", "oe", "e"), ("ai", "ai", "a"),
|
("oa", "oa", "a"), ("oe", "oe", "e"), ("ai", "ai", "a"),
|
||||||
("ao", "ao", "a"), ("au", "au", "a"), ("ay", "ay", "a"),
|
("ao", "ao", "a"), ("au", "au", "a"), ("ay", "ay", "a"),
|
||||||
("eo", "eo", "e"), ("eu", "eu", "e"), ("ia", "ia", "i"),
|
("eu", "eu", "e"), ("iu", "iu", "i"), ("oi", "oi", "o"),
|
||||||
("iu", "iu", "i"), ("oi", "oi", "o"), ("ua", "ua", "u"),
|
|
||||||
("ui", "ui", "u"), ("uy", "uy", "y"),
|
("ui", "ui", "u"), ("uy", "uy", "y"),
|
||||||
("aau", "âu", "â"), ("aay", "ây", "â"), ("eeu", "êu", "ê"),
|
|
||||||
("ooi", "ôi", "ô"), ("owi", "ơi", "ơ"), ("uwi", "ưi", "ư"),
|
|
||||||
("uwu", "ưu", "ư"), ("uwa", "ưa", "ư"), ("uee", "uê", "ê"),
|
|
||||||
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
||||||
("uow", "ươ", "ơ"), ("uaa", "uâ", "â"), ("oaw", "oă", "ă"),
|
("uow", "ươ", "ơ"), ("uaa", "uâ", "â"), ("oaw", "oă", "ă"),
|
||||||
("uwow", "ươ", "ơ"),
|
("uwa", "ưa", "a"), ("uwow", "ươ", "ơ"),
|
||||||
]
|
]
|
||||||
for i, o, v in tab:
|
for i, o, v in tab:
|
||||||
emit(i, o)
|
emit(i, o)
|
||||||
@@ -72,21 +64,21 @@ def vowel2():
|
|||||||
emit("ie", "ie")
|
emit("ie", "ie")
|
||||||
emit("ye", "ye")
|
emit("ye", "ye")
|
||||||
emit("uo", "uo")
|
emit("uo", "uo")
|
||||||
|
emit("ua", "ua")
|
||||||
|
|
||||||
def vowel3():
|
def vowel3():
|
||||||
# input, output, vowel
|
# input, output, vowel
|
||||||
tab = [
|
tab = [
|
||||||
("ieeu", "iêu", "ê"), ("yeeu", "yêu", "ê"),
|
("ieeu", "iêu", "ê"), ("yeeu", "yêu", "ê"),
|
||||||
("uooi", "uôi", "ô"), ("uowi", "ươi", "ơ"),
|
("uooi", "uôi", "ô"), ("uowi", "ươi", "ơ"),
|
||||||
("uowu", "ươu", "ơ"), ("uwowi", "ươi", "ơ"),
|
("oai", "oai", "a"), ("oay", "oay", "a"),
|
||||||
("uwowu", "ươu", "ơ"), ("uaay", "uây", "â"),
|
("uyee", "uyê", "ê"),
|
||||||
("oai", "oai", "a"), ("oay", "oay", "a"), ("oeo", "oeo", "e"),
|
|
||||||
("uya", "uya", "y"), ("uyu", "uyu", "y"), ("uyee", "uyê", "ê"),
|
|
||||||
]
|
]
|
||||||
for i, o, v in tab:
|
for i, o, v in tab:
|
||||||
emit(i, o)
|
emit(i, o)
|
||||||
for t in tone:
|
for t in tone:
|
||||||
emit(i+t, o.replace(v, addtone(v, t), 1))
|
emit(i+t, o.replace(v, addtone(v, t), 1))
|
||||||
|
emit("uya", "uya")
|
||||||
|
|
||||||
def modvowels():
|
def modvowels():
|
||||||
for i, o in modvowel:
|
for i, o in modvowel:
|
||||||
@@ -102,6 +94,14 @@ def mod1tone():
|
|||||||
for t in tone:
|
for t in tone:
|
||||||
emit(i+t, addtone(o, t))
|
emit(i+t, addtone(o, t))
|
||||||
|
|
||||||
|
modvowel = [
|
||||||
|
("aw", "ă"),
|
||||||
|
("aa", "â"),
|
||||||
|
("ee", "ê"),
|
||||||
|
("oo", "ô"),
|
||||||
|
("ow", "ơ"),
|
||||||
|
("uw", "ư"),
|
||||||
|
]
|
||||||
def tone1mod():
|
def tone1mod():
|
||||||
# a+s+w -> ắ
|
# a+s+w -> ắ
|
||||||
for i, o in modvowel:
|
for i, o in modvowel:
|
||||||
@@ -136,11 +136,10 @@ def final():
|
|||||||
("aw", "ă", "ă"), ("aa", "â", "â"), ("ee", "ê", "ê"),
|
("aw", "ă", "ă"), ("aa", "â", "â"), ("ee", "ê", "ê"),
|
||||||
("oo", "ô", "ô"), ("ow", "ơ", "ơ"), ("uw", "ư", "ư"),
|
("oo", "ô", "ô"), ("ow", "ơ", "ơ"), ("uw", "ư", "ư"),
|
||||||
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
||||||
("uow", "ươ", "ơ"), ("uwow", "ươ", "ơ"), ("uyee", "uyê", "ê"),
|
("uow", "ươ", "ơ"), ("uaa", "uâ", "â"), ("oaw", "oă", "ă"),
|
||||||
("uaa", "uâ", "â"), ("uee", "uê", "ê"), ("oaw", "oă", "ă"),
|
|
||||||
("ai", "ai", "a"), ("ao", "ao", "a"), ("au", "au", "a"),
|
("ai", "ai", "a"), ("ao", "ao", "a"), ("au", "au", "a"),
|
||||||
("ay", "ay", "a"), ("oa", "oa", "a"), ("oe", "oe", "e"),
|
("ay", "ay", "a"), ("oa", "oa", "a"), ("oi", "oi", "o"),
|
||||||
("oi", "oi", "o"), ("ui", "ui", "u"), ("uy", "uy", "y"),
|
("ui", "ui", "u"), ("uy", "uy", "y"),
|
||||||
]
|
]
|
||||||
for i, o, v in tab:
|
for i, o, v in tab:
|
||||||
for c in coda:
|
for c in coda:
|
||||||
@@ -148,24 +147,6 @@ def final():
|
|||||||
for t in tone:
|
for t in tone:
|
||||||
emit(i+c+t, o.replace(v, addtone(v, t), 1)+c)
|
emit(i+c+t, o.replace(v, addtone(v, t), 1)+c)
|
||||||
|
|
||||||
def onsets():
|
|
||||||
# Keep the qu-/gi- onset in the preedit so the tone lands on the rime
|
|
||||||
# nucleus, not on the onset glide (qua->quá not qúa, gia->giá not gía).
|
|
||||||
# transvi (onsetglide) knows to skip the glide; here we only need the
|
|
||||||
# composed clusters to exist so the preedit accumulates them.
|
|
||||||
vowels = set("aeiouy")
|
|
||||||
tones = set("sfrxj")
|
|
||||||
base = [(i, o) for (i, o) in list(entries)
|
|
||||||
if i and i[0] in vowels and not (set(i) & tones)]
|
|
||||||
for i, o in base:
|
|
||||||
if i[0] != 'u': # no qu+u syllable; u is the glide
|
|
||||||
emit("qu" + i, "qu" + o)
|
|
||||||
emit("gi" + i, "gi" + o)
|
|
||||||
# gi- with the i as nucleus (no following vowel): gì, gìn, gìm, ...
|
|
||||||
for c in ["", "c", "m", "n", "p", "t", "ch", "ng", "nh"]:
|
|
||||||
if c:
|
|
||||||
emit("gi" + c, "gi" + c)
|
|
||||||
|
|
||||||
vowel1()
|
vowel1()
|
||||||
vowel2()
|
vowel2()
|
||||||
vowel3()
|
vowel3()
|
||||||
@@ -176,5 +157,3 @@ tone1mod()
|
|||||||
tone2mod()
|
tone2mod()
|
||||||
escape()
|
escape()
|
||||||
final()
|
final()
|
||||||
emit("\\", "\\")
|
|
||||||
onsets()
|
|
||||||
|
|||||||
1012
map/mssymbol.src
1012
map/mssymbol.src
File diff suppressed because it is too large
Load Diff
28294
map/pinyin.dict
Normal file
28294
map/pinyin.dict
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,69 +1,2 @@
|
|||||||
#!/bin/bash
|
#!/bin/rc
|
||||||
|
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
|
|
||||||
}
|
|
||||||
/^;;/ {
|
|
||||||
if(!body) # the header, with its license notice, is kept
|
|
||||||
print
|
|
||||||
next
|
|
||||||
}
|
|
||||||
/^[[:space:]]*$/ {
|
|
||||||
next
|
|
||||||
}
|
|
||||||
{
|
|
||||||
body = 1
|
|
||||||
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]]
|
|
||||||
}
|
|
||||||
'
|
|
||||||
|
|||||||
103
map/symbol.src
103
map/symbol.src
@@ -1,103 +0,0 @@
|
|||||||
# Result first, followed by one or more TAB-separated aliases.
|
|
||||||
⚠ !!
|
|
||||||
★ **
|
|
||||||
± +-
|
|
||||||
→ ->
|
|
||||||
· ..
|
|
||||||
… ...
|
|
||||||
÷ ./
|
|
||||||
☹ :(
|
|
||||||
☺ :)
|
|
||||||
|
|
||||||
# Bare 1-9 choose from a prefix search; keep digit aliases in matching slots.
|
|
||||||
← <- <
|
|
||||||
≤ <= <
|
|
||||||
♥ <3 <
|
|
||||||
≠ <> != <
|
|
||||||
≡ ==
|
|
||||||
⇒ =>
|
|
||||||
≥ >=
|
|
||||||
¹ ^1 ^
|
|
||||||
² ^2 ^
|
|
||||||
³ ^3 ^
|
|
||||||
⁴ ^4 ^
|
|
||||||
⁵ ^5 ^
|
|
||||||
⁶ ^6 ^
|
|
||||||
⁷ ^7 ^
|
|
||||||
⁸ ^8 ^
|
|
||||||
⁹ ^9 ^
|
|
||||||
₁ _1 _
|
|
||||||
₂ _2 _
|
|
||||||
₃ _3 _
|
|
||||||
₄ _4 _
|
|
||||||
₅ _5 _
|
|
||||||
₆ _6 _
|
|
||||||
₇ _7 _
|
|
||||||
₈ _8 _
|
|
||||||
₉ _9 _
|
|
||||||
|
|
||||||
⁽ ^(
|
|
||||||
⁾ ^)
|
|
||||||
⁺ ^+
|
|
||||||
⁻ ^-
|
|
||||||
⁼ ^=
|
|
||||||
₍ _(
|
|
||||||
₎ _)
|
|
||||||
₊ _+
|
|
||||||
₋ _-
|
|
||||||
₌ _=
|
|
||||||
≈ ~=
|
|
||||||
⁰ ^0
|
|
||||||
₀ _0
|
|
||||||
ₐ _a
|
|
||||||
α alpha
|
|
||||||
β beta
|
|
||||||
χ chi
|
|
||||||
° deg degree
|
|
||||||
δ delta
|
|
||||||
Δ De Delta
|
|
||||||
↓ dn down
|
|
||||||
ₑ _e
|
|
||||||
ε eps
|
|
||||||
η eta
|
|
||||||
凸 fuck
|
|
||||||
γ gamma
|
|
||||||
Γ Ga Gamma
|
|
||||||
ⁱ ^i
|
|
||||||
∫ II integral
|
|
||||||
∞ inf infinity
|
|
||||||
ι iota
|
|
||||||
κ kappa
|
|
||||||
λ lambda
|
|
||||||
Λ La Lambda
|
|
||||||
× mul times
|
|
||||||
μ mu
|
|
||||||
ⁿ ^n
|
|
||||||
ν nu
|
|
||||||
ω omega
|
|
||||||
Ω Om Omega
|
|
||||||
● oo circle
|
|
||||||
ₒ _o
|
|
||||||
φ phi
|
|
||||||
Φ Ph Phi
|
|
||||||
π pi
|
|
||||||
Π Pi
|
|
||||||
∏ PP prod
|
|
||||||
ψ psi
|
|
||||||
Ψ Ps Psi
|
|
||||||
ρ rho
|
|
||||||
σ sigma
|
|
||||||
Σ Si Sigma
|
|
||||||
√ sq sqrt
|
|
||||||
∑ SS sum
|
|
||||||
τ tau
|
|
||||||
θ theta
|
|
||||||
Θ Th Theta
|
|
||||||
↑ up
|
|
||||||
υ ups
|
|
||||||
✓ vv check
|
|
||||||
ξ xi
|
|
||||||
Ξ Xi
|
|
||||||
✗ xx cross
|
|
||||||
ₓ _x
|
|
||||||
ζ zeta
|
|
||||||
5180
map/telex.map
5180
map/telex.map
File diff suppressed because it is too large
Load Diff
@@ -1,73 +0,0 @@
|
|||||||
#!/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
Normal file
83100
map/wubi.dict
Normal file
File diff suppressed because it is too large
Load Diff
257
popup_layout.c
257
popup_layout.c
@@ -1,257 +0,0 @@
|
|||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
|
|
||||||
enum {
|
|
||||||
Asciitofull = 0xFEE0,
|
|
||||||
};
|
|
||||||
|
|
||||||
int popupscale = 1;
|
|
||||||
|
|
||||||
static void
|
|
||||||
fill(u32int *buf, int n, u32int color)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for(i = 0; i < n; i++)
|
|
||||||
buf[i] = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
fillrect(u32int *buf, int w, int h, int x, int y, int rw, int rh,
|
|
||||||
u32int color)
|
|
||||||
{
|
|
||||||
int x0, x1, y0, y1;
|
|
||||||
|
|
||||||
x0 = min(max(x, 0), w);
|
|
||||||
x1 = min(max(x + max(rw, 0), 0), w);
|
|
||||||
y0 = min(max(y, 0), h);
|
|
||||||
y1 = min(max(y + max(rh, 0), 0), h);
|
|
||||||
for(; y0 < y1; y0++)
|
|
||||||
fill(buf + y0*w + x0, max(x1 - x0, 0), color);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
validarea(Area *a)
|
|
||||||
{
|
|
||||||
return a != nil && a->w > 0 && a->h > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static vlong
|
|
||||||
areadistance(Area *a, int x, int y)
|
|
||||||
{
|
|
||||||
vlong dx, dy, right, bottom;
|
|
||||||
|
|
||||||
right = (vlong)a->x + a->w;
|
|
||||||
bottom = (vlong)a->y + a->h;
|
|
||||||
dx = x < a->x ? (vlong)a->x - x : x >= right ? x - right : 0;
|
|
||||||
dy = y < a->y ? (vlong)a->y - y : y >= bottom ? y - bottom : 0;
|
|
||||||
return dx + dy;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
popuparea(Area *mon, int nmon, Area *work, int x, int y, Area *out)
|
|
||||||
{
|
|
||||||
vlong best, bottom, d, right, x0, x1, y0, y1;
|
|
||||||
int i, pick;
|
|
||||||
|
|
||||||
memset(out, 0, sizeof *out);
|
|
||||||
pick = -1;
|
|
||||||
best = (vlong)1 << 62;
|
|
||||||
for(i = 0; i < nmon; i++){
|
|
||||||
if(!validarea(&mon[i]))
|
|
||||||
continue;
|
|
||||||
right = (vlong)mon[i].x + mon[i].w;
|
|
||||||
bottom = (vlong)mon[i].y + mon[i].h;
|
|
||||||
if(x >= mon[i].x && x < right &&
|
|
||||||
y >= mon[i].y && y < bottom){
|
|
||||||
pick = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
d = areadistance(&mon[i], x, y);
|
|
||||||
if(d < best){
|
|
||||||
best = d;
|
|
||||||
pick = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(pick < 0)
|
|
||||||
return;
|
|
||||||
*out = mon[pick];
|
|
||||||
if(!validarea(work))
|
|
||||||
return;
|
|
||||||
x0 = max((vlong)out->x, work->x);
|
|
||||||
y0 = max((vlong)out->y, work->y);
|
|
||||||
x1 = min((vlong)out->x + out->w, (vlong)work->x + work->w);
|
|
||||||
y1 = min((vlong)out->y + out->h, (vlong)work->y + work->h);
|
|
||||||
if(x1 <= x0 || y1 <= y0)
|
|
||||||
return;
|
|
||||||
out->x = x0;
|
|
||||||
out->y = y0;
|
|
||||||
out->w = x1 - x0;
|
|
||||||
out->h = y1 - y0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
pagemarker(char *buf, int nbuf, int first, int shown, int total)
|
|
||||||
{
|
|
||||||
if(nbuf <= 0)
|
|
||||||
return 0;
|
|
||||||
buf[0] = 0;
|
|
||||||
if(shown <= 0 || (first == 0 && shown >= total))
|
|
||||||
return 0;
|
|
||||||
return snprint(buf, nbuf, "%d-%d/%d", first + 1,
|
|
||||||
first + shown, total);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
fitrows(int h, int pre, int mark, int n)
|
|
||||||
{
|
|
||||||
int fixed;
|
|
||||||
|
|
||||||
fixed = 2*PopupPad + mark*Fontsz;
|
|
||||||
if(pre)
|
|
||||||
fixed += Fontsz + (n != 0 ? PopupSep : 0);
|
|
||||||
if(h <= fixed)
|
|
||||||
return 0;
|
|
||||||
return min(n, (h - fixed) / Fontsz);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
popuplayout(Drawcmd *dc, int areaw, int areah, Popup *p)
|
|
||||||
{
|
|
||||||
char buf[32];
|
|
||||||
int first, i, markrow, nall, nmark, npre, total, width, y;
|
|
||||||
|
|
||||||
memset(p, 0, sizeof *p);
|
|
||||||
p->prey = -1;
|
|
||||||
p->sepy = -1;
|
|
||||||
p->rowsy = -1;
|
|
||||||
p->marky = -1;
|
|
||||||
p->sely = -1;
|
|
||||||
nall = min(max(dc->nkouho, 0), Maxdisp);
|
|
||||||
npre = dc->pre.n != 0;
|
|
||||||
if((nall == 0 && !npre) || areaw <= 0 || areah <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* As many rows as fit, and a page marker when they do not all. */
|
|
||||||
total = max(dc->total, dc->first + nall);
|
|
||||||
markrow = dc->first > 0 || nall < total;
|
|
||||||
p->n = fitrows(areah, npre, markrow, nall);
|
|
||||||
if(p->n < nall && !markrow){
|
|
||||||
markrow = 1;
|
|
||||||
p->n = fitrows(areah, npre, markrow, nall);
|
|
||||||
}
|
|
||||||
if(p->n == 0 && !npre)
|
|
||||||
return;
|
|
||||||
if(p->n != 0 && dc->sel >= p->n)
|
|
||||||
p->row0 = min(dc->sel - p->n + 1, nall - p->n);
|
|
||||||
first = dc->first + p->row0;
|
|
||||||
nmark = markrow ? pagemarker(buf, sizeof buf, first, p->n, total) : 0;
|
|
||||||
if(nmark == 0)
|
|
||||||
markrow = 0;
|
|
||||||
|
|
||||||
/* Rows share one steady width; a preedit alone hugs its text. */
|
|
||||||
width = p->n != 0 ? PopupBasew : 2*PopupPad;
|
|
||||||
if(npre)
|
|
||||||
width = max(width, textwidth(&dc->pre) + 2*PopupPad);
|
|
||||||
for(i = 0; i < p->n; i++)
|
|
||||||
width = max(width, textwidth(&dc->kouho[p->row0+i]) +
|
|
||||||
PopupNumw + 2*PopupPad);
|
|
||||||
if(markrow){
|
|
||||||
sinit(&p->mark, buf, nmark);
|
|
||||||
p->markw = textwidth(&p->mark);
|
|
||||||
width = max(width, p->markw + 2*PopupPad);
|
|
||||||
}
|
|
||||||
p->w = min(width, areaw);
|
|
||||||
|
|
||||||
y = PopupPad;
|
|
||||||
if(npre){
|
|
||||||
p->prey = y;
|
|
||||||
y += Fontsz;
|
|
||||||
}
|
|
||||||
if(npre && p->n != 0){
|
|
||||||
p->sepy = y;
|
|
||||||
y += PopupSep;
|
|
||||||
}
|
|
||||||
if(p->n != 0){
|
|
||||||
p->rowsy = y;
|
|
||||||
y += p->n*Fontsz;
|
|
||||||
}
|
|
||||||
if(markrow){
|
|
||||||
p->marky = y;
|
|
||||||
y += Fontsz;
|
|
||||||
}
|
|
||||||
p->h = min(y + PopupPad, areah);
|
|
||||||
p->textw = max(p->w - 2*PopupPad - PopupNumw, 0);
|
|
||||||
if(markrow){
|
|
||||||
p->markw = min(p->markw, max(p->w - 2*PopupPad, 0));
|
|
||||||
p->markx = p->w - PopupPad - p->markw;
|
|
||||||
}
|
|
||||||
p->selw = max(p->w - 2*PopupPad, 0);
|
|
||||||
if(dc->sel >= p->row0 && dc->sel < p->row0 + p->n)
|
|
||||||
p->sely = p->rowsy + (dc->sel - p->row0)*Fontsz;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
popupdraw(u32int *img, Drawcmd *dc, Popup *p)
|
|
||||||
{
|
|
||||||
Str num;
|
|
||||||
u32int color;
|
|
||||||
int i, j, y;
|
|
||||||
|
|
||||||
fill(img, p->w*p->h, Colbg);
|
|
||||||
if(p->prey >= 0)
|
|
||||||
textdraw(img, p->w, p->h, PopupPad, p->prey,
|
|
||||||
max(p->w - 2*PopupPad, 0), Colfg, &dc->pre);
|
|
||||||
if(p->sepy >= 0)
|
|
||||||
fillrect(img, p->w, p->h, PopupPad, p->sepy,
|
|
||||||
p->w - 2*PopupPad, PopupSep, Colsep);
|
|
||||||
if(p->sely >= 0)
|
|
||||||
fillrect(img, p->w, p->h, PopupPad, p->sely,
|
|
||||||
p->selw, Fontsz, Colsel);
|
|
||||||
for(i = 0, y = p->rowsy; i < p->n; i++, y += Fontsz){
|
|
||||||
j = p->row0 + i;
|
|
||||||
color = j == dc->sel ? Colselfg : Colfg;
|
|
||||||
sclear(&num);
|
|
||||||
sputr(&num, '1' + j + Asciitofull);
|
|
||||||
textdraw(img, p->w, p->h, PopupPad, y,
|
|
||||||
min(PopupNumw, max(p->w - 2*PopupPad, 0)), color, &num);
|
|
||||||
textdraw(img, p->w, p->h, PopupPad + PopupNumw, y, p->textw,
|
|
||||||
color, &dc->kouho[j]);
|
|
||||||
}
|
|
||||||
if(p->marky >= 0)
|
|
||||||
textdraw(img, p->w, p->h, p->markx, p->marky, p->markw,
|
|
||||||
Colfg, &p->mark);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
popupposition(Caret *caret, int pointerx, int pointery, Area *area,
|
|
||||||
int w, int h, int *x, int *y)
|
|
||||||
{
|
|
||||||
vlong below, bottom, px, py, right, xmax, ymax;
|
|
||||||
|
|
||||||
if(!validarea(area)){
|
|
||||||
*x = 0;
|
|
||||||
*y = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
right = (vlong)area->x + area->w;
|
|
||||||
bottom = (vlong)area->y + area->h;
|
|
||||||
|
|
||||||
if(caret->valid){
|
|
||||||
px = caret->x;
|
|
||||||
below = (vlong)caret->y + max(caret->h, 0);
|
|
||||||
if(below >= area->y && below + h <= bottom)
|
|
||||||
py = below;
|
|
||||||
else
|
|
||||||
py = (vlong)caret->y - h;
|
|
||||||
}else{
|
|
||||||
px = (vlong)pointerx + 10;
|
|
||||||
py = (vlong)pointery + 10;
|
|
||||||
if(py + h > bottom)
|
|
||||||
py = (vlong)pointery - 10 - h;
|
|
||||||
}
|
|
||||||
xmax = max(right - w, area->x);
|
|
||||||
ymax = max(bottom - h, area->y);
|
|
||||||
*x = max((vlong)area->x, min(px, xmax));
|
|
||||||
*y = max((vlong)area->y, min(py, ymax));
|
|
||||||
}
|
|
||||||
@@ -1,494 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<protocol name="input_method_unstable_v2">
|
|
||||||
|
|
||||||
<copyright>
|
|
||||||
Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
Copyright © 2010-2011 Intel Corporation
|
|
||||||
Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
Copyright © 2012, 2013 Intel Corporation
|
|
||||||
Copyright © 2015, 2016 Jan Arne Petersen
|
|
||||||
Copyright © 2017, 2018 Red Hat, Inc.
|
|
||||||
Copyright © 2018 Purism SPC
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
copy of this software and associated documentation files (the "Software"),
|
|
||||||
to deal in the Software without restriction, including without limitation
|
|
||||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice (including the next
|
|
||||||
paragraph) shall be included in all copies or substantial portions of the
|
|
||||||
Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
DEALINGS IN THE SOFTWARE.
|
|
||||||
</copyright>
|
|
||||||
|
|
||||||
<description summary="Protocol for creating input methods">
|
|
||||||
This protocol allows applications to act as input methods for compositors.
|
|
||||||
|
|
||||||
An input method context is used to manage the state of the input method.
|
|
||||||
|
|
||||||
Text strings are UTF-8 encoded, their indices and lengths are in bytes.
|
|
||||||
|
|
||||||
This document adheres to the RFC 2119 when using words like "must",
|
|
||||||
"should", "may", etc.
|
|
||||||
|
|
||||||
Warning! The protocol described in this file is experimental and
|
|
||||||
backward incompatible changes may be made. Backward compatible changes
|
|
||||||
may be added together with the corresponding interface version bump.
|
|
||||||
Backward incompatible changes are done by bumping the version number in
|
|
||||||
the protocol and interface names and resetting the interface version.
|
|
||||||
Once the protocol is to be declared stable, the 'z' prefix and the
|
|
||||||
version number in the protocol and interface names are removed and the
|
|
||||||
interface version number is reset.
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<interface name="zwp_input_method_v2" version="1">
|
|
||||||
<description summary="input method">
|
|
||||||
An input method object allows for clients to compose text.
|
|
||||||
|
|
||||||
The objects connects the client to a text input in an application, and
|
|
||||||
lets the client to serve as an input method for a seat.
|
|
||||||
|
|
||||||
The zwp_input_method_v2 object can occupy two distinct states: active and
|
|
||||||
inactive. In the active state, the object is associated to and
|
|
||||||
communicates with a text input. In the inactive state, there is no
|
|
||||||
associated text input, and the only communication is with the compositor.
|
|
||||||
Initially, the input method is in the inactive state.
|
|
||||||
|
|
||||||
Requests issued in the inactive state must be accepted by the compositor.
|
|
||||||
Because of the serial mechanism, and the state reset on activate event,
|
|
||||||
they will not have any effect on the state of the next text input.
|
|
||||||
|
|
||||||
There must be no more than one input method object per seat.
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<enum name="error">
|
|
||||||
<entry name="role" value="0" summary="wl_surface has another role"/>
|
|
||||||
</enum>
|
|
||||||
|
|
||||||
<event name="activate">
|
|
||||||
<description summary="input method has been requested">
|
|
||||||
Notification that a text input focused on this seat requested the input
|
|
||||||
method to be activated.
|
|
||||||
|
|
||||||
This event serves the purpose of providing the compositor with an
|
|
||||||
active input method.
|
|
||||||
|
|
||||||
This event resets all state associated with previous
|
|
||||||
surrounding_text, text_change_cause, and content_type events, as well
|
|
||||||
as the state associated with set_preedit_string, commit_string, and
|
|
||||||
delete_surrounding_text requests. In addition, it marks the
|
|
||||||
zwp_input_method_v2 object as active, and makes any existing
|
|
||||||
zwp_input_popup_surface_v2 objects visible.
|
|
||||||
|
|
||||||
The surrounding_text, and content_type events must follow before the
|
|
||||||
next done event if the text input supports the respective
|
|
||||||
functionality.
|
|
||||||
|
|
||||||
State set with this event is double-buffered. It will get applied on
|
|
||||||
the next zwp_input_method_v2.done event, and stay valid until changed.
|
|
||||||
</description>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="deactivate">
|
|
||||||
<description summary="deactivate event">
|
|
||||||
Notification that no focused text input currently needs an active
|
|
||||||
input method on this seat.
|
|
||||||
|
|
||||||
This event marks the zwp_input_method_v2 object as inactive. The
|
|
||||||
compositor must make all existing zwp_input_popup_surface_v2 objects
|
|
||||||
invisible until the next activate event.
|
|
||||||
|
|
||||||
State set with this event is double-buffered. It will get applied on
|
|
||||||
the next zwp_input_method_v2.done event, and stay valid until changed.
|
|
||||||
</description>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="surrounding_text">
|
|
||||||
<description summary="surrounding text event">
|
|
||||||
Updates the surrounding plain text around the cursor, excluding the
|
|
||||||
preedit text.
|
|
||||||
|
|
||||||
If any preedit text is present, it is replaced with the cursor for the
|
|
||||||
purpose of this event.
|
|
||||||
|
|
||||||
The argument text is a buffer containing the surrounding text, and must
|
|
||||||
include the cursor position, and the complete selection. It should
|
|
||||||
contain additional characters before and after these. There is a
|
|
||||||
maximum length of wayland messages, so text can not be longer than 4000
|
|
||||||
bytes.
|
|
||||||
|
|
||||||
cursor is the byte offset of the cursor within the text buffer.
|
|
||||||
|
|
||||||
anchor is the byte offset of the selection anchor within the text
|
|
||||||
buffer. If there is no selected text, anchor must be the same as
|
|
||||||
cursor.
|
|
||||||
|
|
||||||
If this event does not arrive before the first done event, the input
|
|
||||||
method may assume that the text input does not support this
|
|
||||||
functionality and ignore following surrounding_text events.
|
|
||||||
|
|
||||||
Values set with this event are double-buffered. They will get applied
|
|
||||||
and set to initial values on the next zwp_input_method_v2.done
|
|
||||||
event.
|
|
||||||
|
|
||||||
The initial state for affected fields is empty, meaning that the text
|
|
||||||
input does not support sending surrounding text. If the empty values
|
|
||||||
get applied, subsequent attempts to change them may have no effect.
|
|
||||||
</description>
|
|
||||||
<arg name="text" type="string"/>
|
|
||||||
<arg name="cursor" type="uint"/>
|
|
||||||
<arg name="anchor" type="uint"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="text_change_cause">
|
|
||||||
<description summary="indicates the cause of surrounding text change">
|
|
||||||
Tells the input method why the text surrounding the cursor changed.
|
|
||||||
|
|
||||||
Whenever the client detects an external change in text, cursor, or
|
|
||||||
anchor position, it must issue this request to the compositor. This
|
|
||||||
request is intended to give the input method a chance to update the
|
|
||||||
preedit text in an appropriate way, e.g. by removing it when the user
|
|
||||||
starts typing with a keyboard.
|
|
||||||
|
|
||||||
cause describes the source of the change.
|
|
||||||
|
|
||||||
The value set with this event is double-buffered. It will get applied
|
|
||||||
and set to its initial value on the next zwp_input_method_v2.done
|
|
||||||
event.
|
|
||||||
|
|
||||||
The initial value of cause is input_method.
|
|
||||||
</description>
|
|
||||||
<arg name="cause" type="uint" enum="zwp_text_input_v3.change_cause"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="content_type">
|
|
||||||
<description summary="content purpose and hint">
|
|
||||||
Indicates the content type and hint for the current
|
|
||||||
zwp_input_method_v2 instance.
|
|
||||||
|
|
||||||
Values set with this event are double-buffered. They will get applied
|
|
||||||
on the next zwp_input_method_v2.done event.
|
|
||||||
|
|
||||||
The initial value for hint is none, and the initial value for purpose
|
|
||||||
is normal.
|
|
||||||
</description>
|
|
||||||
<arg name="hint" type="uint" enum="zwp_text_input_v3.content_hint"/>
|
|
||||||
<arg name="purpose" type="uint" enum="zwp_text_input_v3.content_purpose"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="done">
|
|
||||||
<description summary="apply state">
|
|
||||||
Atomically applies state changes recently sent to the client.
|
|
||||||
|
|
||||||
The done event establishes and updates the state of the client, and
|
|
||||||
must be issued after any changes to apply them.
|
|
||||||
|
|
||||||
Text input state (content purpose, content hint, surrounding text, and
|
|
||||||
change cause) is conceptually double-buffered within an input method
|
|
||||||
context.
|
|
||||||
|
|
||||||
Events modify the pending state, as opposed to the current state in use
|
|
||||||
by the input method. A done event atomically applies all pending state,
|
|
||||||
replacing the current state. After done, the new pending state is as
|
|
||||||
documented for each related request.
|
|
||||||
|
|
||||||
Events must be applied in the order of arrival.
|
|
||||||
|
|
||||||
Neither current nor pending state are modified unless noted otherwise.
|
|
||||||
</description>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<request name="commit_string">
|
|
||||||
<description summary="commit string">
|
|
||||||
Send the commit string text for insertion to the application.
|
|
||||||
|
|
||||||
Inserts a string at current cursor position (see commit event
|
|
||||||
sequence). The string to commit could be either just a single character
|
|
||||||
after a key press or the result of some composing.
|
|
||||||
|
|
||||||
The argument text is a buffer containing the string to insert. There is
|
|
||||||
a maximum length of wayland messages, so text can not be longer than
|
|
||||||
4000 bytes.
|
|
||||||
|
|
||||||
Values set with this event are double-buffered. They must be applied
|
|
||||||
and reset to initial on the next zwp_text_input_v3.commit request.
|
|
||||||
|
|
||||||
The initial value of text is an empty string.
|
|
||||||
</description>
|
|
||||||
<arg name="text" type="string"/>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<request name="set_preedit_string">
|
|
||||||
<description summary="pre-edit string">
|
|
||||||
Send the pre-edit string text to the application text input.
|
|
||||||
|
|
||||||
Place a new composing text (pre-edit) at the current cursor position.
|
|
||||||
Any previously set composing text must be removed. Any previously
|
|
||||||
existing selected text must be removed. The cursor is moved to a new
|
|
||||||
position within the preedit string.
|
|
||||||
|
|
||||||
The argument text is a buffer containing the preedit string. There is
|
|
||||||
a maximum length of wayland messages, so text can not be longer than
|
|
||||||
4000 bytes.
|
|
||||||
|
|
||||||
The arguments cursor_begin and cursor_end are counted in bytes relative
|
|
||||||
to the beginning of the submitted string buffer. Cursor should be
|
|
||||||
hidden by the text input when both are equal to -1.
|
|
||||||
|
|
||||||
cursor_begin indicates the beginning of the cursor. cursor_end
|
|
||||||
indicates the end of the cursor. It may be equal or different than
|
|
||||||
cursor_begin.
|
|
||||||
|
|
||||||
Values set with this event are double-buffered. They must be applied on
|
|
||||||
the next zwp_input_method_v2.commit event.
|
|
||||||
|
|
||||||
The initial value of text is an empty string. The initial value of
|
|
||||||
cursor_begin, and cursor_end are both 0.
|
|
||||||
</description>
|
|
||||||
<arg name="text" type="string"/>
|
|
||||||
<arg name="cursor_begin" type="int"/>
|
|
||||||
<arg name="cursor_end" type="int"/>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<request name="delete_surrounding_text">
|
|
||||||
<description summary="delete text">
|
|
||||||
Remove the surrounding text.
|
|
||||||
|
|
||||||
before_length and after_length are the number of bytes before and after
|
|
||||||
the current cursor index (excluding the preedit text) to delete.
|
|
||||||
|
|
||||||
If any preedit text is present, it is replaced with the cursor for the
|
|
||||||
purpose of this event. In effect before_length is counted from the
|
|
||||||
beginning of preedit text, and after_length from its end (see commit
|
|
||||||
event sequence).
|
|
||||||
|
|
||||||
Values set with this event are double-buffered. They must be applied
|
|
||||||
and reset to initial on the next zwp_input_method_v2.commit request.
|
|
||||||
|
|
||||||
The initial values of both before_length and after_length are 0.
|
|
||||||
</description>
|
|
||||||
<arg name="before_length" type="uint"/>
|
|
||||||
<arg name="after_length" type="uint"/>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<request name="commit">
|
|
||||||
<description summary="apply state">
|
|
||||||
Apply state changes from commit_string, set_preedit_string and
|
|
||||||
delete_surrounding_text requests.
|
|
||||||
|
|
||||||
The state relating to these events is double-buffered, and each one
|
|
||||||
modifies the pending state. This request replaces the current state
|
|
||||||
with the pending state.
|
|
||||||
|
|
||||||
The connected text input is expected to proceed by evaluating the
|
|
||||||
changes in the following order:
|
|
||||||
|
|
||||||
1. Replace existing preedit string with the cursor.
|
|
||||||
2. Delete requested surrounding text.
|
|
||||||
3. Insert commit string with the cursor at its end.
|
|
||||||
4. Calculate surrounding text to send.
|
|
||||||
5. Insert new preedit text in cursor position.
|
|
||||||
6. Place cursor inside preedit text.
|
|
||||||
|
|
||||||
The serial number reflects the last state of the zwp_input_method_v2
|
|
||||||
object known to the client. The value of the serial argument must be
|
|
||||||
equal to the number of done events already issued by that object. When
|
|
||||||
the compositor receives a commit request with a serial different than
|
|
||||||
the number of past done events, it must proceed as normal, except it
|
|
||||||
should not change the current state of the zwp_input_method_v2 object.
|
|
||||||
</description>
|
|
||||||
<arg name="serial" type="uint"/>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<request name="get_input_popup_surface">
|
|
||||||
<description summary="create popup surface">
|
|
||||||
Creates a new zwp_input_popup_surface_v2 object wrapping a given
|
|
||||||
surface.
|
|
||||||
|
|
||||||
The surface gets assigned the "input_popup" role. If the surface
|
|
||||||
already has an assigned role, the compositor must issue a protocol
|
|
||||||
error.
|
|
||||||
</description>
|
|
||||||
<arg name="id" type="new_id" interface="zwp_input_popup_surface_v2"/>
|
|
||||||
<arg name="surface" type="object" interface="wl_surface"/>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<request name="grab_keyboard">
|
|
||||||
<description summary="grab hardware keyboard">
|
|
||||||
Allow an input method to receive hardware keyboard input and process
|
|
||||||
key events to generate text events (with pre-edit) over the wire. This
|
|
||||||
allows input methods which compose multiple key events for inputting
|
|
||||||
text like it is done for CJK languages.
|
|
||||||
|
|
||||||
The compositor should send all keyboard events on the seat to the grab
|
|
||||||
holder via the returned wl_keyboard object. Nevertheless, the
|
|
||||||
compositor may decide not to forward any particular event. The
|
|
||||||
compositor must not further process any event after it has been
|
|
||||||
forwarded to the grab holder.
|
|
||||||
|
|
||||||
Releasing the resulting wl_keyboard object releases the grab.
|
|
||||||
</description>
|
|
||||||
<arg name="keyboard" type="new_id"
|
|
||||||
interface="zwp_input_method_keyboard_grab_v2"/>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<event name="unavailable">
|
|
||||||
<description summary="input method unavailable">
|
|
||||||
The input method ceased to be available.
|
|
||||||
|
|
||||||
The compositor must issue this event as the only event on the object if
|
|
||||||
there was another input_method object associated with the same seat at
|
|
||||||
the time of its creation.
|
|
||||||
|
|
||||||
The compositor must issue this request when the object is no longer
|
|
||||||
usable, e.g. due to seat removal.
|
|
||||||
|
|
||||||
The input method context becomes inert and should be destroyed after
|
|
||||||
deactivation is handled. Any further requests and events except for the
|
|
||||||
destroy request must be ignored.
|
|
||||||
</description>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<request name="destroy" type="destructor">
|
|
||||||
<description summary="destroy the text input">
|
|
||||||
Destroys the zwp_text_input_v2 object and any associated child
|
|
||||||
objects, i.e. zwp_input_popup_surface_v2 and
|
|
||||||
zwp_input_method_keyboard_grab_v2.
|
|
||||||
</description>
|
|
||||||
</request>
|
|
||||||
</interface>
|
|
||||||
|
|
||||||
<interface name="zwp_input_popup_surface_v2" version="1">
|
|
||||||
<description summary="popup surface">
|
|
||||||
This interface marks a surface as a popup for interacting with an input
|
|
||||||
method.
|
|
||||||
|
|
||||||
The compositor should place it near the active text input area. It must
|
|
||||||
be visible if and only if the input method is in the active state.
|
|
||||||
|
|
||||||
The client must not destroy the underlying wl_surface while the
|
|
||||||
zwp_input_popup_surface_v2 object exists.
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<event name="text_input_rectangle">
|
|
||||||
<description summary="set text input area position">
|
|
||||||
Notify about the position of the area of the text input expressed as a
|
|
||||||
rectangle in surface local coordinates.
|
|
||||||
|
|
||||||
This is a hint to the input method telling it the relative position of
|
|
||||||
the text being entered.
|
|
||||||
</description>
|
|
||||||
<arg name="x" type="int"/>
|
|
||||||
<arg name="y" type="int"/>
|
|
||||||
<arg name="width" type="int"/>
|
|
||||||
<arg name="height" type="int"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<request name="destroy" type="destructor"/>
|
|
||||||
</interface>
|
|
||||||
|
|
||||||
<interface name="zwp_input_method_keyboard_grab_v2" version="1">
|
|
||||||
<!-- Closely follows wl_keyboard version 6 -->
|
|
||||||
<description summary="keyboard grab">
|
|
||||||
The zwp_input_method_keyboard_grab_v2 interface represents an exclusive
|
|
||||||
grab of the wl_keyboard interface associated with the seat.
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<event name="keymap">
|
|
||||||
<description summary="keyboard mapping">
|
|
||||||
This event provides a file descriptor to the client which can be
|
|
||||||
memory-mapped to provide a keyboard mapping description.
|
|
||||||
</description>
|
|
||||||
<arg name="format" type="uint" enum="wl_keyboard.keymap_format"
|
|
||||||
summary="keymap format"/>
|
|
||||||
<arg name="fd" type="fd" summary="keymap file descriptor"/>
|
|
||||||
<arg name="size" type="uint" summary="keymap size, in bytes"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="key">
|
|
||||||
<description summary="key event">
|
|
||||||
A key was pressed or released.
|
|
||||||
The time argument is a timestamp with millisecond granularity, with an
|
|
||||||
undefined base.
|
|
||||||
</description>
|
|
||||||
<arg name="serial" type="uint" summary="serial number of the key event"/>
|
|
||||||
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
|
||||||
<arg name="key" type="uint" summary="key that produced the event"/>
|
|
||||||
<arg name="state" type="uint" enum="wl_keyboard.key_state"
|
|
||||||
summary="physical state of the key"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="modifiers">
|
|
||||||
<description summary="modifier and group state">
|
|
||||||
Notifies clients that the modifier and/or group state has changed, and
|
|
||||||
it should update its local state.
|
|
||||||
</description>
|
|
||||||
<arg name="serial" type="uint" summary="serial number of the modifiers event"/>
|
|
||||||
<arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
|
|
||||||
<arg name="mods_latched" type="uint" summary="latched modifiers"/>
|
|
||||||
<arg name="mods_locked" type="uint" summary="locked modifiers"/>
|
|
||||||
<arg name="group" type="uint" summary="keyboard layout"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<request name="release" type="destructor">
|
|
||||||
<description summary="release the grab object"/>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<event name="repeat_info">
|
|
||||||
<description summary="repeat rate and delay">
|
|
||||||
Informs the client about the keyboard's repeat rate and delay.
|
|
||||||
|
|
||||||
This event is sent as soon as the zwp_input_method_keyboard_grab_v2
|
|
||||||
object has been created, and is guaranteed to be received by the
|
|
||||||
client before any key press event.
|
|
||||||
|
|
||||||
Negative values for either rate or delay are illegal. A rate of zero
|
|
||||||
will disable any repeating (regardless of the value of delay).
|
|
||||||
|
|
||||||
This event can be sent later on as well with a new value if necessary,
|
|
||||||
so clients should continue listening for the event past the creation
|
|
||||||
of zwp_input_method_keyboard_grab_v2.
|
|
||||||
</description>
|
|
||||||
<arg name="rate" type="int"
|
|
||||||
summary="the rate of repeating keys in characters per second"/>
|
|
||||||
<arg name="delay" type="int"
|
|
||||||
summary="delay in milliseconds since key down until repeating starts"/>
|
|
||||||
</event>
|
|
||||||
</interface>
|
|
||||||
|
|
||||||
<interface name="zwp_input_method_manager_v2" version="1">
|
|
||||||
<description summary="input method manager">
|
|
||||||
The input method manager allows the client to become the input method on
|
|
||||||
a chosen seat.
|
|
||||||
|
|
||||||
No more than one input method must be associated with any seat at any
|
|
||||||
given time.
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<request name="get_input_method">
|
|
||||||
<description summary="request an input method object">
|
|
||||||
Request a new input zwp_input_method_v2 object associated with a given
|
|
||||||
seat.
|
|
||||||
</description>
|
|
||||||
<arg name="seat" type="object" interface="wl_seat"/>
|
|
||||||
<arg name="input_method" type="new_id" interface="zwp_input_method_v2"/>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<request name="destroy" type="destructor">
|
|
||||||
<description summary="destroy the input method manager">
|
|
||||||
Destroys the zwp_input_method_manager_v2 object.
|
|
||||||
|
|
||||||
The zwp_input_method_v2 objects originating from it remain valid.
|
|
||||||
</description>
|
|
||||||
</request>
|
|
||||||
</interface>
|
|
||||||
</protocol>
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<protocol name="virtual_keyboard_unstable_v1">
|
|
||||||
<copyright>
|
|
||||||
Copyright © 2008-2011 Kristian Høgsberg
|
|
||||||
Copyright © 2010-2013 Intel Corporation
|
|
||||||
Copyright © 2012-2013 Collabora, Ltd.
|
|
||||||
Copyright © 2018 Purism SPC
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
copy of this software and associated documentation files (the "Software"),
|
|
||||||
to deal in the Software without restriction, including without limitation
|
|
||||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice (including the next
|
|
||||||
paragraph) shall be included in all copies or substantial portions of the
|
|
||||||
Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
DEALINGS IN THE SOFTWARE.
|
|
||||||
</copyright>
|
|
||||||
|
|
||||||
<interface name="zwp_virtual_keyboard_v1" version="1">
|
|
||||||
<description summary="virtual keyboard">
|
|
||||||
The virtual keyboard provides an application with requests which emulate
|
|
||||||
the behaviour of a physical keyboard.
|
|
||||||
|
|
||||||
This interface can be used by clients on its own to provide raw input
|
|
||||||
events, or it can accompany the input method protocol.
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<request name="keymap">
|
|
||||||
<description summary="keyboard mapping">
|
|
||||||
Provide a file descriptor to the compositor which can be
|
|
||||||
memory-mapped to provide a keyboard mapping description.
|
|
||||||
</description>
|
|
||||||
<arg name="format" type="uint" enum="wl_keyboard.keymap_format" summary="keymap format"/>
|
|
||||||
<arg name="fd" type="fd" summary="keymap file descriptor"/>
|
|
||||||
<arg name="size" type="uint" summary="keymap size, in bytes"/>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<enum name="error">
|
|
||||||
<entry name="no_keymap" value="0" summary="No keymap was set"/>
|
|
||||||
<entry name="invalid_keymap_format" value="1" summary="Invalid keymap format"/>
|
|
||||||
</enum>
|
|
||||||
|
|
||||||
<request name="key">
|
|
||||||
<description summary="key event">
|
|
||||||
A key was pressed or released.
|
|
||||||
The time argument is a timestamp with millisecond granularity, with an
|
|
||||||
undefined base. All requests regarding a single object must share the
|
|
||||||
same clock.
|
|
||||||
|
|
||||||
Keymap must be set before issuing this request.
|
|
||||||
|
|
||||||
State carries a value from the key_state enumeration.
|
|
||||||
</description>
|
|
||||||
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
|
||||||
<arg name="key" type="uint" summary="key that produced the event"/>
|
|
||||||
<arg name="state" type="uint" summary="physical state of the key"/>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<request name="modifiers">
|
|
||||||
<description summary="modifier and group state">
|
|
||||||
Notifies the compositor that the modifier and/or group state has
|
|
||||||
changed, and it should update state.
|
|
||||||
|
|
||||||
The client should use wl_keyboard.modifiers event to synchronize its
|
|
||||||
internal state with seat state.
|
|
||||||
|
|
||||||
Keymap must be set before issuing this request.
|
|
||||||
</description>
|
|
||||||
<arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
|
|
||||||
<arg name="mods_latched" type="uint" summary="latched modifiers"/>
|
|
||||||
<arg name="mods_locked" type="uint" summary="locked modifiers"/>
|
|
||||||
<arg name="group" type="uint" summary="keyboard layout"/>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<request name="destroy" type="destructor" since="1">
|
|
||||||
<description summary="destroy the virtual keyboard keyboard object"/>
|
|
||||||
</request>
|
|
||||||
</interface>
|
|
||||||
|
|
||||||
<interface name="zwp_virtual_keyboard_manager_v1" version="1">
|
|
||||||
<description summary="virtual keyboard manager">
|
|
||||||
A virtual keyboard manager allows an application to provide keyboard
|
|
||||||
input events as if they came from a physical keyboard.
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<enum name="error">
|
|
||||||
<entry name="unauthorized" value="0" summary="client not authorized to use the interface"/>
|
|
||||||
</enum>
|
|
||||||
|
|
||||||
<request name="create_virtual_keyboard">
|
|
||||||
<description summary="Create a new virtual keyboard">
|
|
||||||
Creates a new virtual keyboard associated to a seat.
|
|
||||||
|
|
||||||
If the compositor enables a keyboard to perform arbitrary actions, it
|
|
||||||
should present an error when an untrusted client requests a new
|
|
||||||
keyboard.
|
|
||||||
</description>
|
|
||||||
<arg name="seat" type="object" interface="wl_seat"/>
|
|
||||||
<arg name="id" type="new_id" interface="zwp_virtual_keyboard_v1"/>
|
|
||||||
</request>
|
|
||||||
</interface>
|
|
||||||
</protocol>
|
|
||||||
27
run.sh
27
run.sh
@@ -1,24 +1,9 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
cd "$(dirname "$0")" || exit 1
|
pkill strans
|
||||||
|
pkill strans-xim
|
||||||
|
|
||||||
if test "$#" -ne 0; then
|
sleep 1
|
||||||
echo "usage: run.sh" >&2
|
./strans map font &
|
||||||
exit 1
|
sleep 1
|
||||||
fi
|
xim/strans-xim &
|
||||||
|
|
||||||
if ! test -x ./strans; then
|
|
||||||
echo "run.sh: ./strans is not executable; run make first" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if ! command -v pkill >/dev/null 2>&1; then
|
|
||||||
echo "run.sh: pkill is required" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
uid=$(id -u) || exit 1
|
|
||||||
pkill -TERM -u "$uid" -x strans 2>/dev/null || :
|
|
||||||
sleep 0.1
|
|
||||||
pkill -KILL -u "$uid" -x strans 2>/dev/null || :
|
|
||||||
|
|
||||||
./strans map </dev/null &
|
|
||||||
|
|||||||
149
srv.c
149
srv.c
@@ -1,150 +1,41 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
#include <sys/stat.h>
|
static char adir[40];
|
||||||
#include <sys/un.h>
|
|
||||||
|
|
||||||
static char adir[256];
|
|
||||||
static char sockpath[sizeof(((struct sockaddr_un*)0)->sun_path)];
|
|
||||||
static dev_t sockdev;
|
|
||||||
static ino_t sockino;
|
|
||||||
static Channel *clientc;
|
|
||||||
|
|
||||||
/* Removes the socket only while it is still the one we announced. */
|
|
||||||
static void
|
static void
|
||||||
srvunlink(void)
|
sendkey(int fd, u32int ks, u32int mod)
|
||||||
{
|
{
|
||||||
struct stat st;
|
Keyreq kr;
|
||||||
|
|
||||||
if(sockpath[0] != '\0' && lstat(sockpath, &st) == 0 &&
|
kr.fd = fd;
|
||||||
st.st_dev == sockdev && st.st_ino == sockino)
|
kr.ks = ks;
|
||||||
unlink(sockpath);
|
kr.mod = mod;
|
||||||
sockpath[0] = '\0';
|
chansend(keyc, &kr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
srvnote(void *v, char *note)
|
|
||||||
{
|
|
||||||
USED(v);
|
|
||||||
if(notefatal(note))
|
|
||||||
srvunlink();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 0 for a request to make, 1 for a frame that only changes what rides
|
|
||||||
* along with the requests after it, -1 for anything else.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
srvreadreq(int fd, Keyreq *kr, int *want)
|
|
||||||
{
|
|
||||||
uchar req[Ipccaretsz];
|
|
||||||
char text[Ipcfieldmax];
|
|
||||||
int valid;
|
|
||||||
size_t n;
|
|
||||||
int32_t x, y, h;
|
|
||||||
|
|
||||||
if(ipcreadn(fd, req, Ipcreqsz) < 0)
|
|
||||||
return -1;
|
|
||||||
*want = 0;
|
|
||||||
kr->ks = 0;
|
|
||||||
kr->mod = 0;
|
|
||||||
switch(ipcreqtype(req)){
|
|
||||||
case Ipckey:
|
|
||||||
ipcunpackreq(req, want, &kr->mod, &kr->ks);
|
|
||||||
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
|
|
||||||
return 0;
|
|
||||||
case Ipccap:
|
|
||||||
*want = (req[0] & Ipcreqwant) != 0;
|
|
||||||
kr->op = Keycap;
|
|
||||||
return 0;
|
|
||||||
case Ipccaret:
|
|
||||||
if(ipcreadn(fd, req + Ipcreqsz, Ipccaretsz - Ipcreqsz) < 0 ||
|
|
||||||
ipcunpackcaret(req, &valid, &x, &y, &h) < 0)
|
|
||||||
return -1;
|
|
||||||
kr->op = Keycaret;
|
|
||||||
kr->caret.valid = valid;
|
|
||||||
kr->caret.x = x;
|
|
||||||
kr->caret.y = y;
|
|
||||||
kr->caret.h = h;
|
|
||||||
return 0;
|
|
||||||
case Ipcsurround:
|
|
||||||
n = ipcsurroundlen(req);
|
|
||||||
if(n > 0 && ipcreadn(fd, text, n) < 0)
|
|
||||||
return -1;
|
|
||||||
stail(&kr->surround, text, n);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* One Keyreq persists for the connection: the negotiated preedit and
|
|
||||||
* the last caret ride along with every request, and the socket closing
|
|
||||||
* releases the engine.
|
|
||||||
*/
|
|
||||||
static void
|
static void
|
||||||
clientthread(void *arg)
|
clientthread(void *arg)
|
||||||
{
|
{
|
||||||
Keyreq kr;
|
int fd;
|
||||||
Keyres res;
|
uchar req[4];
|
||||||
uchar out[Ipcmaxresp], token;
|
|
||||||
char commit[Maxutf], preedit[Maxutf];
|
|
||||||
int fd, n, ncommit, npreedit, rv, want;
|
|
||||||
|
|
||||||
fd = (int)(uintptr)arg;
|
fd = (int)(uintptr)arg;
|
||||||
threadsetname("client %d", fd);
|
threadsetname("client %d", fd);
|
||||||
memset(&kr, 0, sizeof kr);
|
while(read(fd, req, 4) == 4)
|
||||||
kr.reply = chancreate(sizeof(Keyres), 0);
|
sendkey(fd, req[2] | (req[3] << 8), req[1]);
|
||||||
kr.owner = &fd;
|
|
||||||
kr.clientpre = 1;
|
|
||||||
while((rv = srvreadreq(fd, &kr, &want)) >= 0){
|
|
||||||
if(rv != 0)
|
|
||||||
continue;
|
|
||||||
if(kr.op != Keycaret)
|
|
||||||
kr.clientpre = want;
|
|
||||||
chansend(keyc, &kr);
|
|
||||||
chanrecv(kr.reply, &res);
|
|
||||||
if(kr.op == Keycaret)
|
|
||||||
continue;
|
|
||||||
ncommit = stoutf(&res.commit, commit, sizeof commit);
|
|
||||||
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
|
|
||||||
/* A capability reply is always eaten: it marks the extension. */
|
|
||||||
n = ipcpackresp(out, sizeof out, kr.op == Keycap || res.eaten,
|
|
||||||
res.del, commit, ncommit, preedit, npreedit, want);
|
|
||||||
if(n < 0 || ipcsend(fd, out, n) < 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
kr.op = Keyrelease;
|
|
||||||
kr.ks = 0;
|
|
||||||
kr.mod = 0;
|
|
||||||
chansend(keyc, &kr);
|
|
||||||
chanrecv(kr.reply, &res);
|
|
||||||
chanfree(kr.reply);
|
|
||||||
close(fd);
|
close(fd);
|
||||||
chanrecv(clientc, &token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
srvinit(void)
|
srvinit(void)
|
||||||
{
|
{
|
||||||
struct stat st;
|
char addr[64];
|
||||||
char *addr;
|
|
||||||
|
|
||||||
if(ipcpath(sockpath, sizeof sockpath) < 0)
|
snprint(addr, sizeof(addr), "unix!/tmp/strans.%d", getuid());
|
||||||
die("IPC path is too long");
|
remove(addr + 5);
|
||||||
addr = smprint("unix!%s", sockpath);
|
|
||||||
if(addr == nil)
|
|
||||||
die("out of memory");
|
|
||||||
if(announce(addr, adir) < 0)
|
if(announce(addr, adir) < 0)
|
||||||
die("IPC endpoint is already in use: %r");
|
die("announce: %r");
|
||||||
free(addr);
|
|
||||||
if(chmod(sockpath, 0600) < 0 || lstat(sockpath, &st) < 0)
|
|
||||||
die("can't protect IPC endpoint: %s", sockpath);
|
|
||||||
sockdev = st.st_dev;
|
|
||||||
sockino = st.st_ino;
|
|
||||||
atexit(srvunlink);
|
|
||||||
threadnotify(srvnote, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -152,19 +43,13 @@ srvthread(void*)
|
|||||||
{
|
{
|
||||||
char ldir[40];
|
char ldir[40];
|
||||||
int fd;
|
int fd;
|
||||||
uchar token;
|
|
||||||
|
|
||||||
threadsetname("srv");
|
threadsetname("srv");
|
||||||
token = 0;
|
srvinit();
|
||||||
clientc = chancreate(sizeof token, Maxclients);
|
|
||||||
for(;;){
|
for(;;){
|
||||||
fd = listen(adir, ldir);
|
fd = listen(adir, ldir);
|
||||||
if(fd < 0)
|
if(fd < 0)
|
||||||
continue;
|
continue;
|
||||||
if(channbsend(clientc, &token) <= 0){
|
|
||||||
close(fd);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
proccreate(clientthread, (void*)(uintptr)fd, 8192);
|
proccreate(clientthread, (void*)(uintptr)fd, 8192);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5079
stb_truetype.h
Normal file
5079
stb_truetype.h
Normal file
File diff suppressed because it is too large
Load Diff
54
str.c
54
str.c
@@ -1,42 +1,23 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
/* Fills s from n bytes of UTF-8; whole, valid, and at most Maxrunes.
|
enum
|
||||||
* Anything else leaves s empty. */
|
{
|
||||||
int
|
Maxrunes = nelem(((Str*)0)->r),
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
sinit(Str *s, char *src, int n)
|
sinit(Str *s, char *src, int n)
|
||||||
{
|
{
|
||||||
Str tmp = {0};
|
|
||||||
Rune r;
|
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
s->n = 0;
|
s->n = 0;
|
||||||
while(n > 0){
|
while(n > 0 && s->n < Maxrunes){
|
||||||
if(tmp.n >= Maxrunes || !fullrune(src, n))
|
len = chartorune(&s->r[s->n], src);
|
||||||
return 0;
|
s->n++;
|
||||||
len = chartorune(&r, src);
|
|
||||||
if((r == Runeerror && len == 1) || (r >= 0xd800 && r <= 0xdfff))
|
|
||||||
return 0;
|
|
||||||
tmp.r[tmp.n++] = r;
|
|
||||||
src += len;
|
src += len;
|
||||||
n -= len;
|
n -= len;
|
||||||
}
|
}
|
||||||
*s = tmp;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The last runes of n bytes of UTF-8, as many as a Str holds. */
|
|
||||||
void
|
|
||||||
stail(Str *s, char *src, int n)
|
|
||||||
{
|
|
||||||
Rune r;
|
|
||||||
char *p;
|
|
||||||
int nr;
|
|
||||||
|
|
||||||
nr = utfnlen(src, n);
|
|
||||||
for(p = src; nr > Maxrunes; nr--)
|
|
||||||
p += chartorune(&r, p);
|
|
||||||
sinit(s, p, n - (p - src));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -48,9 +29,8 @@ 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)
|
||||||
return;
|
die("sputr overflow");
|
||||||
s->r[s->n++] = r;
|
s->r[s->n++] = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,10 +44,9 @@ spopr(Str *s)
|
|||||||
void
|
void
|
||||||
sappend(Str *dst, Str *src)
|
sappend(Str *dst, Str *src)
|
||||||
{
|
{
|
||||||
int i, n;
|
int i;
|
||||||
|
|
||||||
n = src->n;
|
for(i = 0; i < src->n && dst->n < Maxrunes; i++)
|
||||||
for(i = 0; i < n && dst->n < Maxrunes; i++)
|
|
||||||
dst->r[dst->n++] = src->r[i];
|
dst->r[dst->n++] = src->r[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,19 +63,14 @@ scmp(Str *a, Str *b)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* UTF-8 of s into buf[sz], NUL-terminated, whole runes only. */
|
|
||||||
int
|
int
|
||||||
stoutf(Str *s, char *buf, int sz)
|
stoutf(Str *s, char *buf, int sz)
|
||||||
{
|
{
|
||||||
char tmp[UTFmax];
|
|
||||||
int i, n, len;
|
int i, n, len;
|
||||||
|
|
||||||
n = 0;
|
n = 0;
|
||||||
for(i = 0; i < s->n; i++){
|
for(i = 0; i < s->n && n < sz - UTFmax; i++){
|
||||||
len = runetochar(tmp, &s->r[i]);
|
len = runetochar(buf + n, &s->r[i]);
|
||||||
if(len > sz - n - 1)
|
|
||||||
break;
|
|
||||||
memmove(buf + n, tmp, len);
|
|
||||||
n += len;
|
n += len;
|
||||||
}
|
}
|
||||||
buf[n] = '\0';
|
buf[n] = '\0';
|
||||||
|
|||||||
150
tests/Makefile
150
tests/Makefile
@@ -1,150 +0,0 @@
|
|||||||
CC = 9c
|
|
||||||
LD = 9l
|
|
||||||
HOSTCC = cc
|
|
||||||
PKG_CONFIG ?= pkg-config
|
|
||||||
CFLAGS ?= -O2 -g
|
|
||||||
UNIT_CPPFLAGS = -I..
|
|
||||||
UNIT_CFLAGS = -Wall -Wextra
|
|
||||||
HOST_CFLAGS = -std=c99 -Wall -Wextra
|
|
||||||
TEXT_CFLAGS = $(shell $(PKG_CONFIG) --cflags pangocairo cairo fontconfig)
|
|
||||||
TEXT_LIBS = $(shell $(PKG_CONFIG) --libs pangocairo cairo fontconfig)
|
|
||||||
DBUS_CFLAGS = $(shell $(PKG_CONFIG) --cflags dbus-1)
|
|
||||||
DBUS_LIBS = $(shell $(PKG_CONFIG) --libs dbus-1)
|
|
||||||
GTK_CFLAGS = $(shell $(PKG_CONFIG) --cflags gtk+-3.0)
|
|
||||||
GTK_LIBS = $(shell $(PKG_CONFIG) --libs gtk+-3.0)
|
|
||||||
IBUS_CFLAGS = $(shell $(PKG_CONFIG) --cflags dbus-1 xkbcommon)
|
|
||||||
IBUS_LIBS = $(shell $(PKG_CONFIG) --libs dbus-1 xkbcommon)
|
|
||||||
IBUS_CLIENT_CFLAGS = $(shell $(PKG_CONFIG) --cflags ibus-1.0)
|
|
||||||
IBUS_CLIENT_LIBS = $(shell $(PKG_CONFIG) --libs ibus-1.0)
|
|
||||||
X11_CFLAGS = $(shell $(PKG_CONFIG) --cflags x11)
|
|
||||||
X11_LIBS = $(shell $(PKG_CONFIG) --libs x11)
|
|
||||||
XIM_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
|
||||||
XIM_LIBS = $(shell $(PKG_CONFIG) --libs xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
|
||||||
WL_CFLAGS = $(shell $(PKG_CONFIG) --cflags wayland-client xkbcommon)
|
|
||||||
WL_LIBS = $(shell $(PKG_CONFIG) --libs wayland-client xkbcommon)
|
|
||||||
UNIT_LDLIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS) $(WL_LIBS)
|
|
||||||
|
|
||||||
PROG = unit_test
|
|
||||||
STRESS = stress_test
|
|
||||||
LIVESRC = live.c live.h
|
|
||||||
LIVEBUSSRC = livebus.c livebus.h
|
|
||||||
SMOKE = ibus_live_test ibus_client_smoke gtk_live_test xim_live_test \
|
|
||||||
ipc_live_test
|
|
||||||
FAULT = daemon_collision_test daemon_failure_test daemon_restart_test
|
|
||||||
LIVE = $(SMOKE) $(FAULT)
|
|
||||||
TESTSRC = test_util.c str_test.c trie_test.c \
|
|
||||||
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
|
||||||
popup_test.c font_test.c ibus_test.c server_test.c compose_test.c \
|
|
||||||
xim_adapter_test.c wl_adapter_test.c
|
|
||||||
TESTOBJ = $(TESTSRC:.c=.o)
|
|
||||||
# imv2.c and vkv1.c are wayland-scanner's; the parent Makefile writes them.
|
|
||||||
PARENTSRC = str.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
|
|
||||||
font.c compose.c imv2.c vkv1.c
|
|
||||||
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
|
||||||
COMMONOBJ = $(TESTOBJ) $(PARENTOBJ)
|
|
||||||
OBJS = unit_test.o stress_test.o $(COMMONOBJ)
|
|
||||||
|
|
||||||
all: $(PROG) $(STRESS) $(LIVE)
|
|
||||||
|
|
||||||
check test: $(PROG)
|
|
||||||
./$(PROG) $(UNITARGS)
|
|
||||||
|
|
||||||
check-live: $(SMOKE) ../strans ../gtk/im-strans.so
|
|
||||||
./ibus_live_test ../strans ../map ./ibus_client_smoke
|
|
||||||
./gtk_live_test ../gtk/im-strans.so
|
|
||||||
./xim_live_test ../strans ../map
|
|
||||||
./ipc_live_test ../strans ../map
|
|
||||||
|
|
||||||
check-stress: $(STRESS) ibus_live_test ipc_live_test $(FAULT) ../strans
|
|
||||||
./$(STRESS)
|
|
||||||
./ibus_live_test --capacity ../strans ../map
|
|
||||||
./ipc_live_test --capacity ../strans ../map
|
|
||||||
./daemon_collision_test ../strans ../map
|
|
||||||
./daemon_failure_test ../strans
|
|
||||||
./daemon_restart_test ../strans ../map
|
|
||||||
|
|
||||||
$(PROG): unit_test.o $(COMMONOBJ)
|
|
||||||
$(LD) $(LDFLAGS) -o $@ unit_test.o $(COMMONOBJ) $(UNIT_LDLIBS) \
|
|
||||||
$(LDLIBS)
|
|
||||||
|
|
||||||
$(STRESS): stress_test.o $(COMMONOBJ)
|
|
||||||
$(LD) $(LDFLAGS) -o $@ stress_test.o $(COMMONOBJ) $(UNIT_LDLIBS) \
|
|
||||||
$(LDLIBS)
|
|
||||||
|
|
||||||
stress_test.o: unit_test.c
|
|
||||||
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -DSTRESS \
|
|
||||||
-c -o $@ $<
|
|
||||||
|
|
||||||
ibus_live_test: ibus_live_test.c $(LIVESRC) $(LIVEBUSSRC) ../ipc.c ../ipc.h
|
|
||||||
$(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
|
||||||
$(LDFLAGS) -o $@ ibus_live_test.c live.c livebus.c ../ipc.c \
|
|
||||||
$(DBUS_LIBS) $(LDLIBS)
|
|
||||||
|
|
||||||
ibus_client_smoke: ibus_client_smoke.c
|
|
||||||
$(HOSTCC) $(CPPFLAGS) $(IBUS_CLIENT_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
|
||||||
$(LDFLAGS) -o $@ $< $(IBUS_CLIENT_LIBS) $(LDLIBS)
|
|
||||||
|
|
||||||
gtk_live_test: gtk_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
|
||||||
$(HOSTCC) $(CPPFLAGS) -I.. $(GTK_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
|
||||||
$(LDFLAGS) -o $@ gtk_live_test.c live.c ../ipc.c $(GTK_LIBS) \
|
|
||||||
-pthread $(LDLIBS)
|
|
||||||
|
|
||||||
# The parent make knows what these are built from; always ask it.
|
|
||||||
../gtk/im-strans.so:
|
|
||||||
$(MAKE) -C ../gtk
|
|
||||||
|
|
||||||
../strans:
|
|
||||||
$(MAKE) -C .. strans
|
|
||||||
|
|
||||||
../imv2.c ../imv2.h ../vkv1.c ../vkv1.h:
|
|
||||||
$(MAKE) -C .. $(@F)
|
|
||||||
|
|
||||||
xim_live_test: xim_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
|
||||||
$(HOSTCC) $(CPPFLAGS) -I.. $(X11_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
|
||||||
$(LDFLAGS) -o $@ xim_live_test.c live.c ../ipc.c $(X11_LIBS) \
|
|
||||||
$(LDLIBS)
|
|
||||||
|
|
||||||
ipc_live_test: ipc_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
|
||||||
$(HOSTCC) $(CPPFLAGS) -I.. $(HOST_CFLAGS) $(CFLAGS) $(LDFLAGS) \
|
|
||||||
-o $@ ipc_live_test.c live.c ../ipc.c $(LDLIBS)
|
|
||||||
|
|
||||||
daemon_collision_test: daemon_collision_test.c $(LIVESRC) $(LIVEBUSSRC) \
|
|
||||||
../ipc.c ../ipc.h
|
|
||||||
$(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
|
||||||
$(LDFLAGS) -o $@ daemon_collision_test.c live.c livebus.c \
|
|
||||||
../ipc.c $(DBUS_LIBS) $(LDLIBS)
|
|
||||||
|
|
||||||
daemon_failure_test: daemon_failure_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
|
||||||
$(HOSTCC) $(CPPFLAGS) -I.. $(HOST_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ \
|
|
||||||
daemon_failure_test.c live.c ../ipc.c $(LDLIBS)
|
|
||||||
|
|
||||||
daemon_restart_test: daemon_restart_test.c $(LIVESRC) $(LIVEBUSSRC) \
|
|
||||||
../ipc.c ../ipc.h
|
|
||||||
$(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
|
||||||
$(LDFLAGS) -o $@ daemon_restart_test.c live.c livebus.c ../ipc.c \
|
|
||||||
$(DBUS_LIBS) $(LDLIBS)
|
|
||||||
|
|
||||||
unit_test.o stress_test.o $(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h \
|
|
||||||
../cutest/cutest.h
|
|
||||||
engine_test.o: ../strans.c
|
|
||||||
ibus_test.o: UNIT_CPPFLAGS += $(IBUS_CFLAGS)
|
|
||||||
ibus_test.o: ../ibus.c
|
|
||||||
xim_adapter_test.o: UNIT_CPPFLAGS += $(XIM_CFLAGS)
|
|
||||||
xim_adapter_test.o: ../xim.c
|
|
||||||
wl_adapter_test.o unit_imv2.o unit_vkv1.o: UNIT_CPPFLAGS += $(WL_CFLAGS)
|
|
||||||
wl_adapter_test.o: ../wl.c ../imv2.h ../vkv1.h
|
|
||||||
server_test.o: ../srv.c
|
|
||||||
unit_font.o: UNIT_CPPFLAGS += $(TEXT_CFLAGS)
|
|
||||||
unit_compose.o compose_test.o: UNIT_CPPFLAGS += $(IBUS_CFLAGS)
|
|
||||||
|
|
||||||
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
|
||||||
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -c -o $@ $<
|
|
||||||
|
|
||||||
%.o: %.c
|
|
||||||
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -c -o $@ $<
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(OBJS) $(PROG) $(STRESS) $(LIVE)
|
|
||||||
|
|
||||||
.PHONY: all check check-live check-stress test clean \
|
|
||||||
../strans ../gtk/im-strans.so
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
#include "test.h"
|
|
||||||
#include <xkbcommon/xkbcommon-keysyms.h>
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A sequence swallows its keys and hands its text back at the end, and
|
|
||||||
* belongs to the one context, in the one frontend, that started it.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
compose_sequences(struct ct *t)
|
|
||||||
{
|
|
||||||
static int a, b; /* two input contexts, told apart by address */
|
|
||||||
static const struct {
|
|
||||||
int who;
|
|
||||||
void *owner;
|
|
||||||
u32int sym;
|
|
||||||
int composing;
|
|
||||||
char *text;
|
|
||||||
} keys[] = {
|
|
||||||
{ Composexim, &a, XKB_KEY_a, 0, "" },
|
|
||||||
{ Composexim, &a, XKB_KEY_dead_acute, 1, "" },
|
|
||||||
{ Composexim, &a, XKB_KEY_e, 0, "é" },
|
|
||||||
{ Composexim, &a, XKB_KEY_Multi_key, 1, "" },
|
|
||||||
{ Composexim, &a, XKB_KEY_o, 1, "" },
|
|
||||||
{ Composexim, &a, XKB_KEY_c, 0, "©" },
|
|
||||||
{ Composexim, &a, XKB_KEY_dead_acute, 1, "" },
|
|
||||||
{ Composexim, &a, XKB_KEY_Escape, 1, "" },
|
|
||||||
{ Composexim, &a, XKB_KEY_a, 0, "" },
|
|
||||||
/* another context of the same frontend starts over */
|
|
||||||
{ Composexim, &a, XKB_KEY_dead_acute, 1, "" },
|
|
||||||
{ Composexim, &b, XKB_KEY_e, 0, "" },
|
|
||||||
/* another frontend does not touch this one's sequence */
|
|
||||||
{ Composexim, &a, XKB_KEY_dead_acute, 1, "" },
|
|
||||||
{ Composewl, &a, XKB_KEY_o, 0, "" },
|
|
||||||
{ Composexim, &a, XKB_KEY_e, 0, "é" },
|
|
||||||
};
|
|
||||||
char buf[Maxutf];
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if(!CT_CHECK(t, setenv("XCOMPOSEFILE", "data/compose", 1) == 0))
|
|
||||||
return;
|
|
||||||
composeinit();
|
|
||||||
for(i = 0; i < nelem(keys); i++){
|
|
||||||
CT_EQ_INT(t, keys[i].composing, composekey(keys[i].who,
|
|
||||||
keys[i].owner, keys[i].sym, buf, sizeof buf));
|
|
||||||
CT_EQ_STR(t, keys[i].text, buf);
|
|
||||||
}
|
|
||||||
/* a context that goes takes its half-typed sequence with it */
|
|
||||||
CT_EQ_INT(t, 1,
|
|
||||||
composekey(Composewl, &a, XKB_KEY_dead_acute, buf, sizeof buf));
|
|
||||||
composedrop(Composewl, &a);
|
|
||||||
CT_EQ_INT(t, 0, composekey(Composewl, &a, XKB_KEY_e, buf, sizeof buf));
|
|
||||||
CT_EQ_STR(t, "", buf);
|
|
||||||
unsetenv("XCOMPOSEFILE");
|
|
||||||
}
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
#define _GNU_SOURCE
|
|
||||||
#include <errno.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "live.h"
|
|
||||||
#include "livebus.h"
|
|
||||||
|
|
||||||
typedef struct Test Test;
|
|
||||||
|
|
||||||
struct Test
|
|
||||||
{
|
|
||||||
Live l;
|
|
||||||
Daemon first;
|
|
||||||
Daemon second;
|
|
||||||
int ipcfirst;
|
|
||||||
int ipcnew;
|
|
||||||
DBusConnection *busfirst;
|
|
||||||
DBusConnection *busnew;
|
|
||||||
char address[512];
|
|
||||||
};
|
|
||||||
|
|
||||||
static int
|
|
||||||
setup(Test *t)
|
|
||||||
{
|
|
||||||
memset(t, 0, sizeof *t);
|
|
||||||
daemoninit(&t->first, "first daemon");
|
|
||||||
daemoninit(&t->second, "second daemon");
|
|
||||||
t->ipcfirst = -1;
|
|
||||||
t->ipcnew = -1;
|
|
||||||
return livesetup(&t->l, "collision");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
addressunchanged(Test *t)
|
|
||||||
{
|
|
||||||
char contents[2048], address[512];
|
|
||||||
size_t ncontents;
|
|
||||||
pid_t declared;
|
|
||||||
|
|
||||||
if(!readfile(t->l.addrfile, contents, sizeof contents, &ncontents) ||
|
|
||||||
!parseaddress(contents, ncontents, address, sizeof address, &declared))
|
|
||||||
return 0;
|
|
||||||
if(declared != t->first.pid)
|
|
||||||
return fail("IBus address PID %ld, expected %ld", (long)declared,
|
|
||||||
(long)t->first.pid);
|
|
||||||
return strcmp(address, t->address) == 0 ||
|
|
||||||
fail("first private IBus address changed after collision");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
waitsecond(Test *t)
|
|
||||||
{
|
|
||||||
struct pollfd pfd;
|
|
||||||
int n, status, timeout;
|
|
||||||
int64_t deadline;
|
|
||||||
|
|
||||||
deadline = nowms() + Starttimeout;
|
|
||||||
for(;;){
|
|
||||||
do
|
|
||||||
n = waitpid(t->second.pid, &status, WNOHANG);
|
|
||||||
while(n < 0 && errno == EINTR);
|
|
||||||
if(n == t->second.pid){
|
|
||||||
t->second.pid = -1;
|
|
||||||
readerrors(&t->second);
|
|
||||||
if(!WIFEXITED(status) || WEXITSTATUS(status) == 0)
|
|
||||||
return fail("second daemon did not exit unsuccessfully: %#x",
|
|
||||||
status);
|
|
||||||
if(strstr(t->second.err, "IPC endpoint is already in use") == NULL)
|
|
||||||
return fail("second daemon did not report the IPC collision");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(n < 0){
|
|
||||||
fail("waitpid second daemon %ld: %s", (long)t->second.pid,
|
|
||||||
strerror(errno));
|
|
||||||
if(errno == ECHILD)
|
|
||||||
t->second.pid = -1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if(!daemonalive(&t->first))
|
|
||||||
return 0;
|
|
||||||
timeout = leftms(deadline);
|
|
||||||
if(timeout == 0){
|
|
||||||
fail("second daemon did not exit after the endpoint collision");
|
|
||||||
killdaemon(&t->second, NULL);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
pfd.fd = t->second.errfd;
|
|
||||||
pfd.events = POLLIN|POLLHUP;
|
|
||||||
pfd.revents = 0;
|
|
||||||
n = poll(&pfd, 1, timeout > 20 ? 20 : timeout);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n < 0)
|
|
||||||
return fail("poll second daemon: %s", strerror(errno));
|
|
||||||
if(pfd.revents != 0)
|
|
||||||
readerrors(&t->second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
runcollision(Test *t, char *program, char *mapdir)
|
|
||||||
{
|
|
||||||
if(!startdaemon(&t->l, &t->first, program, mapdir) ||
|
|
||||||
!waitready(&t->l, &t->first, t->address, sizeof t->address))
|
|
||||||
return 0;
|
|
||||||
t->ipcfirst = connectsocket(t->l.socket, nowms() + Calltimeout);
|
|
||||||
if(t->ipcfirst < 0)
|
|
||||||
return fail("connect persistent IPC client: %s", strerror(errno));
|
|
||||||
if(!ipcprobe(t->ipcfirst, "persistent pre-collision"))
|
|
||||||
return 0;
|
|
||||||
t->busfirst = openbus(t->address, "persistent pre-collision");
|
|
||||||
if(t->busfirst == NULL ||
|
|
||||||
!hello(t->busfirst, NULL, 0, "persistent pre-collision Hello"))
|
|
||||||
return 0;
|
|
||||||
if(!startdaemon(&t->l, &t->second, program, mapdir) ||
|
|
||||||
!waitsecond(t) || !daemonalive(&t->first) || !addressunchanged(t))
|
|
||||||
return 0;
|
|
||||||
if(!ipcprobe(t->ipcfirst, "persistent post-collision"))
|
|
||||||
return 0;
|
|
||||||
if(!createcontext(t->busfirst, NULL, 0,
|
|
||||||
"persistent post-collision context"))
|
|
||||||
return 0;
|
|
||||||
t->ipcnew = connectsocket(t->l.socket, nowms() + Calltimeout);
|
|
||||||
if(t->ipcnew < 0)
|
|
||||||
return fail("connect new IPC client after collision: %s",
|
|
||||||
strerror(errno));
|
|
||||||
if(!ipcprobe(t->ipcnew, "new post-collision"))
|
|
||||||
return 0;
|
|
||||||
t->busnew = openbus(t->address, "new post-collision");
|
|
||||||
if(t->busnew == NULL ||
|
|
||||||
!hello(t->busnew, NULL, 0, "new post-collision Hello") ||
|
|
||||||
!createcontext(t->busnew, NULL, 0, "new post-collision context"))
|
|
||||||
return 0;
|
|
||||||
return daemonalive(&t->first);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
cleanup(Test *t)
|
|
||||||
{
|
|
||||||
int n, ok, status;
|
|
||||||
|
|
||||||
ok = 1;
|
|
||||||
closebus(&t->busnew);
|
|
||||||
closebus(&t->busfirst);
|
|
||||||
if(t->ipcnew >= 0){
|
|
||||||
if(close(t->ipcnew) < 0)
|
|
||||||
ok = fail("close new IPC client: %s", strerror(errno));
|
|
||||||
t->ipcnew = -1;
|
|
||||||
}
|
|
||||||
if(t->ipcfirst >= 0){
|
|
||||||
if(close(t->ipcfirst) < 0)
|
|
||||||
ok = fail("close persistent IPC client: %s", strerror(errno));
|
|
||||||
t->ipcfirst = -1;
|
|
||||||
}
|
|
||||||
if(t->second.pid > 0){
|
|
||||||
do
|
|
||||||
n = waitpid(t->second.pid, &status, WNOHANG);
|
|
||||||
while(n < 0 && errno == EINTR);
|
|
||||||
if(n == t->second.pid)
|
|
||||||
t->second.pid = -1;
|
|
||||||
else if(n == 0){
|
|
||||||
ok = fail("second daemon still running during cleanup");
|
|
||||||
if(!killdaemon(&t->second, NULL))
|
|
||||||
ok = 0;
|
|
||||||
}else{
|
|
||||||
fail("check second daemon during cleanup: %s", strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
if(errno == ECHILD)
|
|
||||||
t->second.pid = -1;
|
|
||||||
else if(!killdaemon(&t->second, NULL))
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!stopdaemon(&t->first))
|
|
||||||
ok = 0;
|
|
||||||
if(!closeerrors(&t->first))
|
|
||||||
ok = 0;
|
|
||||||
if(!closeerrors(&t->second))
|
|
||||||
ok = 0;
|
|
||||||
if(!liveclean(&t->l))
|
|
||||||
ok = 0;
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
Test test;
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
testname = "daemon_collision_test";
|
|
||||||
if(argc != 3){
|
|
||||||
fprintf(stderr, "usage: daemon_collision_test strans mapdir\n");
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
ok = setup(&test);
|
|
||||||
if(ok)
|
|
||||||
ok = runcollision(&test, argv[1], argv[2]);
|
|
||||||
if(!cleanup(&test))
|
|
||||||
ok = 0;
|
|
||||||
if(!ok){
|
|
||||||
showerrors(&test.first);
|
|
||||||
showerrors(&test.second);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("daemon endpoint collision ownership: ok\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
#define _GNU_SOURCE
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "live.h"
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Failtimeout = 4000,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int
|
|
||||||
waitfailed(Daemon *d, char *badmap)
|
|
||||||
{
|
|
||||||
struct pollfd pfd;
|
|
||||||
int n, status;
|
|
||||||
int64_t deadline;
|
|
||||||
|
|
||||||
status = 0;
|
|
||||||
deadline = nowms() + Failtimeout;
|
|
||||||
for(;;){
|
|
||||||
n = waitpid(d->pid, &status, WNOHANG);
|
|
||||||
if(n == d->pid){
|
|
||||||
d->pid = -1;
|
|
||||||
readerrors(d);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n < 0){
|
|
||||||
if(errno == ECHILD)
|
|
||||||
d->pid = -1;
|
|
||||||
return fail("waitpid: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
n = leftms(deadline);
|
|
||||||
if(n == 0)
|
|
||||||
return fail("daemon did not exit after map initialization failure");
|
|
||||||
pfd.fd = d->errfd;
|
|
||||||
pfd.events = POLLIN|POLLHUP;
|
|
||||||
pfd.revents = 0;
|
|
||||||
n = poll(&pfd, 1, n);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n < 0)
|
|
||||||
return fail("poll daemon: %s", strerror(errno));
|
|
||||||
if(n > 0)
|
|
||||||
readerrors(d);
|
|
||||||
}
|
|
||||||
if(!WIFEXITED(status) || WEXITSTATUS(status) == 0)
|
|
||||||
return fail("daemon map failure had wait status %#x", status);
|
|
||||||
if(strstr(d->err, "can't open") == NULL || strstr(d->err, badmap) == NULL)
|
|
||||||
return fail("daemon did not report the missing map directory");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
emptydir(char *path)
|
|
||||||
{
|
|
||||||
DIR *dir;
|
|
||||||
struct dirent *de;
|
|
||||||
int empty;
|
|
||||||
|
|
||||||
dir = opendir(path);
|
|
||||||
if(dir == NULL)
|
|
||||||
return fail("open %s: %s", path, strerror(errno));
|
|
||||||
empty = 1;
|
|
||||||
errno = 0;
|
|
||||||
while((de = readdir(dir)) != NULL)
|
|
||||||
if(strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0){
|
|
||||||
fail("unexpected endpoint %s/%s", path, de->d_name);
|
|
||||||
empty = 0;
|
|
||||||
}
|
|
||||||
if(errno != 0){
|
|
||||||
fail("read %s: %s", path, strerror(errno));
|
|
||||||
empty = 0;
|
|
||||||
}
|
|
||||||
if(closedir(dir) < 0){
|
|
||||||
fail("close %s: %s", path, strerror(errno));
|
|
||||||
empty = 0;
|
|
||||||
}
|
|
||||||
return empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
checkendpoints(Live *l)
|
|
||||||
{
|
|
||||||
struct stat st;
|
|
||||||
|
|
||||||
if(lstat(l->socket, &st) == 0)
|
|
||||||
return fail("IPC endpoint exists after failed startup: %s", l->socket);
|
|
||||||
if(errno != ENOENT)
|
|
||||||
return fail("lstat IPC endpoint: %s", strerror(errno));
|
|
||||||
return emptydir(l->runtime) && emptydir(l->bus);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
Daemon daemon;
|
|
||||||
Live live;
|
|
||||||
char badmap[320];
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
testname = "daemon_failure_test";
|
|
||||||
if(argc != 2){
|
|
||||||
fprintf(stderr, "usage: daemon_failure_test strans\n");
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
daemoninit(&daemon, "daemon");
|
|
||||||
ok = livesetup(&live, "failure");
|
|
||||||
if(ok && snprintf(badmap, sizeof badmap, "%s/missing-map", live.root)
|
|
||||||
>= (int)sizeof badmap)
|
|
||||||
ok = fail("temporary path is too long");
|
|
||||||
if(ok)
|
|
||||||
ok = startdaemon(&live, &daemon, argv[1], badmap);
|
|
||||||
if(ok)
|
|
||||||
ok = waitfailed(&daemon, badmap) && checkendpoints(&live);
|
|
||||||
if(!killdaemon(&daemon, NULL))
|
|
||||||
ok = 0;
|
|
||||||
if(!closeerrors(&daemon))
|
|
||||||
ok = 0;
|
|
||||||
if(!liveclean(&live))
|
|
||||||
ok = 0;
|
|
||||||
if(!ok){
|
|
||||||
showerrors(&daemon);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("failed daemon startup leaves no endpoints: ok\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,406 +0,0 @@
|
|||||||
#define _GNU_SOURCE
|
|
||||||
#include <errno.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "ipc.h"
|
|
||||||
#include "live.h"
|
|
||||||
#include "livebus.h"
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Ctrlmask = 1<<2,
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct Test Test;
|
|
||||||
|
|
||||||
struct Test
|
|
||||||
{
|
|
||||||
Live l;
|
|
||||||
Daemon first;
|
|
||||||
Daemon second;
|
|
||||||
pid_t firstpid;
|
|
||||||
int ipcfirst;
|
|
||||||
int ipcnew;
|
|
||||||
int busfirstfd;
|
|
||||||
DBusConnection *busfirst;
|
|
||||||
DBusConnection *busnew;
|
|
||||||
char firstaddress[512];
|
|
||||||
char secondaddress[512];
|
|
||||||
};
|
|
||||||
|
|
||||||
static int
|
|
||||||
setup(Test *t)
|
|
||||||
{
|
|
||||||
struct sigaction sa;
|
|
||||||
|
|
||||||
memset(t, 0, sizeof *t);
|
|
||||||
daemoninit(&t->first, "first daemon");
|
|
||||||
daemoninit(&t->second, "second daemon");
|
|
||||||
t->firstpid = -1;
|
|
||||||
t->ipcfirst = -1;
|
|
||||||
t->ipcnew = -1;
|
|
||||||
t->busfirstfd = -1;
|
|
||||||
memset(&sa, 0, sizeof sa);
|
|
||||||
sa.sa_handler = SIG_DFL;
|
|
||||||
if(sigemptyset(&sa.sa_mask) < 0 || sigaction(SIGCHLD, &sa, NULL) < 0)
|
|
||||||
return fail("establish exact child ownership: %s", strerror(errno));
|
|
||||||
return livesetup(&t->l, "restart");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
privateaddress(char *address, pid_t pid)
|
|
||||||
{
|
|
||||||
char prefix[96];
|
|
||||||
size_t n;
|
|
||||||
|
|
||||||
if(snprintf(prefix, sizeof prefix, "unix:abstract=strans-%ld",
|
|
||||||
(long)pid) >= (int)sizeof prefix)
|
|
||||||
return fail("private IBus prefix is too long");
|
|
||||||
n = strlen(prefix);
|
|
||||||
return (strncmp(address, prefix, n) == 0 && address[n] == ',') ||
|
|
||||||
fail("IBus address is not the daemon's private abstract address");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
focusin(DBusConnection *conn, char *path)
|
|
||||||
{
|
|
||||||
DBusMessage *reply;
|
|
||||||
|
|
||||||
reply = callret(conn, contextcall(path, "FocusIn"),
|
|
||||||
"replacement FocusIn");
|
|
||||||
if(reply == NULL)
|
|
||||||
return 0;
|
|
||||||
if(!dbus_message_has_signature(reply, "")){
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
return fail("replacement FocusIn returned a nonempty reply");
|
|
||||||
}
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
keycall(DBusConnection *conn, char *path, dbus_uint32_t sym,
|
|
||||||
dbus_uint32_t state, int expected, char *where)
|
|
||||||
{
|
|
||||||
DBusMessage *m, *reply;
|
|
||||||
DBusError err;
|
|
||||||
dbus_uint32_t code;
|
|
||||||
dbus_bool_t eaten;
|
|
||||||
|
|
||||||
code = 0;
|
|
||||||
m = contextcall(path, "ProcessKeyEvent");
|
|
||||||
if(m == NULL || !dbus_message_append_args(m,
|
|
||||||
DBUS_TYPE_UINT32, &sym, DBUS_TYPE_UINT32, &code,
|
|
||||||
DBUS_TYPE_UINT32, &state, DBUS_TYPE_INVALID)){
|
|
||||||
if(m != NULL)
|
|
||||||
dbus_message_unref(m);
|
|
||||||
return fail("build %s ProcessKeyEvent call", where);
|
|
||||||
}
|
|
||||||
reply = callret(conn, m, where);
|
|
||||||
if(reply == NULL)
|
|
||||||
return 0;
|
|
||||||
dbus_error_init(&err);
|
|
||||||
if(!dbus_message_has_signature(reply, "b") ||
|
|
||||||
!dbus_message_get_args(reply, &err, DBUS_TYPE_BOOLEAN, &eaten,
|
|
||||||
DBUS_TYPE_INVALID)){
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
dbus_error_free(&err);
|
|
||||||
return fail("%s returned an invalid ProcessKeyEvent reply", where);
|
|
||||||
}
|
|
||||||
dbus_error_free(&err);
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
return (eaten != FALSE) == (expected != 0) ||
|
|
||||||
fail("%s eaten=%d, expected %d", where, eaten != FALSE, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
openpersistent(Test *t)
|
|
||||||
{
|
|
||||||
t->ipcfirst = connectsocket(t->l.socket, nowms() + Calltimeout);
|
|
||||||
if(t->ipcfirst < 0)
|
|
||||||
return fail("connect persistent IPC client: %s", strerror(errno));
|
|
||||||
if(!ipcprobe(t->ipcfirst, "persistent pre-crash"))
|
|
||||||
return 0;
|
|
||||||
t->busfirst = openbus(t->firstaddress, "persistent pre-crash");
|
|
||||||
if(t->busfirst == NULL ||
|
|
||||||
!hello(t->busfirst, NULL, 0, "persistent pre-crash Hello") ||
|
|
||||||
!createcontext(t->busfirst, NULL, 0, "persistent pre-crash context"))
|
|
||||||
return 0;
|
|
||||||
if(!dbus_connection_get_unix_fd(t->busfirst, &t->busfirstfd))
|
|
||||||
return fail("persistent IBus connection has no Unix descriptor");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* plan9port ignores a broken pipe: the note handlers run and the daemon
|
|
||||||
* goes on serving, so the endpoints it announced must still be there.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
survivenote(Test *t)
|
|
||||||
{
|
|
||||||
struct stat st;
|
|
||||||
int fd, ok;
|
|
||||||
|
|
||||||
if(kill(t->firstpid, SIGPIPE) < 0)
|
|
||||||
return fail("send a broken pipe to the first daemon: %s",
|
|
||||||
strerror(errno));
|
|
||||||
pausems(200);
|
|
||||||
if(!daemonalive(&t->first))
|
|
||||||
return fail("first daemon died of a broken pipe");
|
|
||||||
if(lstat(t->l.socket, &st) < 0)
|
|
||||||
return fail("IPC socket taken by a note the daemon survived: %s",
|
|
||||||
strerror(errno));
|
|
||||||
if(lstat(t->l.addrfile, &st) < 0)
|
|
||||||
return fail("IBus address taken by a note the daemon survived: %s",
|
|
||||||
strerror(errno));
|
|
||||||
fd = connectsocket(t->l.socket, nowms() + Calltimeout);
|
|
||||||
if(fd < 0)
|
|
||||||
return fail("connect after a broken pipe: %s", strerror(errno));
|
|
||||||
ok = ipcprobe(fd, "after a broken pipe");
|
|
||||||
if(close(fd) < 0)
|
|
||||||
return fail("close the client opened after a broken pipe: %s",
|
|
||||||
strerror(errno));
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
hardcrash(Test *t)
|
|
||||||
{
|
|
||||||
int status;
|
|
||||||
|
|
||||||
status = 0;
|
|
||||||
if(!daemonalive(&t->first) || !killdaemon(&t->first, &status))
|
|
||||||
return 0;
|
|
||||||
readerrors(&t->first);
|
|
||||||
if(!WIFSIGNALED(status) || WTERMSIG(status) != SIGKILL)
|
|
||||||
return fail("first daemon hard crash had wait status %#x", status);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
waitipcclosed(Test *t)
|
|
||||||
{
|
|
||||||
struct pollfd pfd;
|
|
||||||
unsigned char byte;
|
|
||||||
ssize_t n;
|
|
||||||
int timeout;
|
|
||||||
int64_t deadline;
|
|
||||||
|
|
||||||
deadline = nowms() + Calltimeout;
|
|
||||||
for(;;){
|
|
||||||
timeout = leftms(deadline);
|
|
||||||
if(timeout == 0)
|
|
||||||
return fail("persistent IPC connection did not disconnect promptly");
|
|
||||||
pfd.fd = t->ipcfirst;
|
|
||||||
pfd.events = POLLIN|POLLHUP|POLLERR;
|
|
||||||
pfd.revents = 0;
|
|
||||||
n = poll(&pfd, 1, timeout);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n <= 0)
|
|
||||||
return fail("poll persistent IPC disconnection: %s",
|
|
||||||
n == 0 ? "timed out" : strerror(errno));
|
|
||||||
n = recv(t->ipcfirst, &byte, 1, MSG_PEEK);
|
|
||||||
if(n == 0 || (n < 0 && (errno == ECONNRESET || errno == ENOTCONN ||
|
|
||||||
errno == EPIPE)))
|
|
||||||
return 1;
|
|
||||||
if(n < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK))
|
|
||||||
continue;
|
|
||||||
if(n < 0)
|
|
||||||
return fail("read persistent IPC disconnection: %s",
|
|
||||||
strerror(errno));
|
|
||||||
return fail("persistent IPC connection retained unread data after crash");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
waitbusclosed(Test *t)
|
|
||||||
{
|
|
||||||
struct pollfd pfd;
|
|
||||||
int n, timeout;
|
|
||||||
int64_t deadline;
|
|
||||||
|
|
||||||
deadline = nowms() + Calltimeout;
|
|
||||||
for(;;){
|
|
||||||
if(!dbus_connection_get_is_connected(t->busfirst))
|
|
||||||
return 1;
|
|
||||||
timeout = leftms(deadline);
|
|
||||||
if(timeout == 0)
|
|
||||||
return fail("persistent IBus connection did not disconnect promptly");
|
|
||||||
pfd.fd = t->busfirstfd;
|
|
||||||
pfd.events = POLLIN|POLLHUP|POLLERR;
|
|
||||||
pfd.revents = 0;
|
|
||||||
n = poll(&pfd, 1, timeout);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n <= 0)
|
|
||||||
return fail("poll persistent IBus disconnection: %s",
|
|
||||||
n == 0 ? "timed out" : strerror(errno));
|
|
||||||
if(pfd.revents & POLLNVAL)
|
|
||||||
return fail("persistent IBus descriptor became invalid");
|
|
||||||
dbus_connection_read_write_dispatch(t->busfirst, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
checkstale(Test *t)
|
|
||||||
{
|
|
||||||
struct stat st;
|
|
||||||
char contents[2048], address[512];
|
|
||||||
size_t ncontents;
|
|
||||||
pid_t declared;
|
|
||||||
|
|
||||||
if(lstat(t->l.socket, &st) < 0)
|
|
||||||
return fail("stale IPC socket disappeared: %s", strerror(errno));
|
|
||||||
if(!S_ISSOCK(st.st_mode))
|
|
||||||
return fail("stale IPC endpoint is not a socket");
|
|
||||||
if(lstat(t->l.addrfile, &st) < 0)
|
|
||||||
return fail("stale IBus address file disappeared: %s", strerror(errno));
|
|
||||||
if(!readfile(t->l.addrfile, contents, sizeof contents, &ncontents) ||
|
|
||||||
!parseaddress(contents, ncontents, address, sizeof address, &declared))
|
|
||||||
return 0;
|
|
||||||
if(declared != t->firstpid || strcmp(address, t->firstaddress) != 0)
|
|
||||||
return fail("stale IBus address no longer names the dead first daemon");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The dead daemon's abstract socket has no listener, so this must fail. */
|
|
||||||
static int
|
|
||||||
rejectold(Test *t, char *where)
|
|
||||||
{
|
|
||||||
DBusConnection *conn;
|
|
||||||
DBusError err;
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
dbus_error_init(&err);
|
|
||||||
conn = dbus_connection_open_private(t->firstaddress, &err);
|
|
||||||
if(conn != NULL){
|
|
||||||
dbus_connection_set_exit_on_disconnect(conn, FALSE);
|
|
||||||
closebus(&conn);
|
|
||||||
ok = fail("old private address accepted a connection %s", where);
|
|
||||||
}else
|
|
||||||
ok = dbus_error_is_set(&err) ||
|
|
||||||
fail("old private address failed without a D-Bus error %s", where);
|
|
||||||
dbus_error_free(&err);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
openreplacement(Test *t)
|
|
||||||
{
|
|
||||||
unsigned char selectjp[] = {1, 0, 0, 0, 0, 0};
|
|
||||||
unsigned char keyk[] = {1, 0, 0, 0, 1, 0, 'k'};
|
|
||||||
char path[96];
|
|
||||||
|
|
||||||
t->ipcnew = connectsocket(t->l.socket, nowms() + Calltimeout);
|
|
||||||
if(t->ipcnew < 0)
|
|
||||||
return fail("connect replacement IPC client: %s", strerror(errno));
|
|
||||||
if(!ipcrequest(t->ipcnew, Mctrl, 'n', selectjp, sizeof selectjp,
|
|
||||||
"replacement select Japanese") ||
|
|
||||||
!ipcrequest(t->ipcnew, 0, 'k', keyk, sizeof keyk,
|
|
||||||
"replacement real key"))
|
|
||||||
return 0;
|
|
||||||
t->busnew = openbus(t->secondaddress, "replacement");
|
|
||||||
if(t->busnew == NULL ||
|
|
||||||
!hello(t->busnew, NULL, 0, "replacement Hello") ||
|
|
||||||
!createcontext(t->busnew, path, sizeof path, "replacement context") ||
|
|
||||||
!focusin(t->busnew, path) ||
|
|
||||||
!keycall(t->busnew, path, 'n', Ctrlmask, 1,
|
|
||||||
"replacement select Japanese") ||
|
|
||||||
!keycall(t->busnew, path, 'k', 0, 1, "replacement real key"))
|
|
||||||
return 0;
|
|
||||||
return daemonalive(&t->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
cleanup(Test *t)
|
|
||||||
{
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
ok = 1;
|
|
||||||
closebus(&t->busnew);
|
|
||||||
closebus(&t->busfirst);
|
|
||||||
if(t->ipcnew >= 0){
|
|
||||||
if(close(t->ipcnew) < 0)
|
|
||||||
ok = fail("close replacement IPC client: %s", strerror(errno));
|
|
||||||
t->ipcnew = -1;
|
|
||||||
}
|
|
||||||
if(t->ipcfirst >= 0){
|
|
||||||
if(close(t->ipcfirst) < 0)
|
|
||||||
ok = fail("close persistent IPC client: %s", strerror(errno));
|
|
||||||
t->ipcfirst = -1;
|
|
||||||
}
|
|
||||||
if(!killdaemon(&t->first, NULL))
|
|
||||||
ok = 0;
|
|
||||||
if(!stopdaemon(&t->second))
|
|
||||||
ok = 0;
|
|
||||||
if(!closeerrors(&t->first))
|
|
||||||
ok = 0;
|
|
||||||
if(!closeerrors(&t->second))
|
|
||||||
ok = 0;
|
|
||||||
if(!liveclean(&t->l))
|
|
||||||
ok = 0;
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
runrestart(Test *t, char *program, char *mapdir)
|
|
||||||
{
|
|
||||||
if(!startdaemon(&t->l, &t->first, program, mapdir))
|
|
||||||
return 0;
|
|
||||||
t->firstpid = t->first.pid;
|
|
||||||
if(!waitready(&t->l, &t->first, t->firstaddress, sizeof t->firstaddress) ||
|
|
||||||
!privateaddress(t->firstaddress, t->firstpid) || !openpersistent(t))
|
|
||||||
return 0;
|
|
||||||
if(!survivenote(t))
|
|
||||||
return 0;
|
|
||||||
if(!hardcrash(t) || !waitipcclosed(t) || !waitbusclosed(t) ||
|
|
||||||
!checkstale(t) || !rejectold(t, "after hard crash"))
|
|
||||||
return 0;
|
|
||||||
closebus(&t->busfirst);
|
|
||||||
if(close(t->ipcfirst) < 0)
|
|
||||||
return fail("close disconnected persistent IPC client: %s",
|
|
||||||
strerror(errno));
|
|
||||||
t->ipcfirst = -1;
|
|
||||||
if(!startdaemon(&t->l, &t->second, program, mapdir) ||
|
|
||||||
!waitready(&t->l, &t->second, t->secondaddress,
|
|
||||||
sizeof t->secondaddress) ||
|
|
||||||
!privateaddress(t->secondaddress, t->second.pid) ||
|
|
||||||
!openreplacement(t))
|
|
||||||
return 0;
|
|
||||||
return daemonalive(&t->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
Test test;
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
testname = "daemon_restart_test";
|
|
||||||
if(argc != 3){
|
|
||||||
fprintf(stderr, "usage: daemon_restart_test strans mapdir\n");
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
ok = setup(&test);
|
|
||||||
if(ok)
|
|
||||||
ok = runrestart(&test, argv[1], argv[2]);
|
|
||||||
if(!cleanup(&test))
|
|
||||||
ok = 0;
|
|
||||||
if(!ok){
|
|
||||||
showerrors(&test.first);
|
|
||||||
showerrors(&test.second);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("daemon endpoints across a note and a hard crash: ok\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<dead_acute> <e> : "é"
|
|
||||||
<Multi_key> <o> <c> : "©"
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
ka か
|
|
||||||
sa さ
|
|
||||||
n ん
|
|
||||||
na な
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
;; converter fixture; the test transcodes this file to EUC-JP
|
|
||||||
かんじ 漢字 幹事 感じ
|
|
||||||
えがお 笑顔
|
|
||||||
きごう 記号 普通
|
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
;; converter fixture; the test transcodes this file to EUC-JP
|
|
||||||
かんじ /漢字;common/幹事/
|
|
||||||
えがお /笑顔;face/
|
|
||||||
かんじ /感じ/漢字;duplicate/
|
|
||||||
きごう /記号;symbol/普通/
|
|
||||||
むこう /候補 with space/
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
a alpha
|
|
||||||
ab beta
|
|
||||||
한 값
|
|
||||||
duplicate first
|
|
||||||
duplicate second
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
void
|
|
||||||
dictionary_candidates(struct ct *t)
|
|
||||||
{
|
|
||||||
char many[8*(Maxkouho+1)], item[8];
|
|
||||||
char *p;
|
|
||||||
Str kouho[Maxkouho], key;
|
|
||||||
Trie *saved;
|
|
||||||
Lang *lang;
|
|
||||||
int i, n;
|
|
||||||
|
|
||||||
lang = getlang(LangJP);
|
|
||||||
saved = lang->dict;
|
|
||||||
lang->dict = trienew();
|
|
||||||
trieput(lang->dict, "かな", strlen("かな"), " 候補1 候補2 ",
|
|
||||||
strlen(" 候補1 候補2 "));
|
|
||||||
key = mkstr("かな");
|
|
||||||
n = dictlookup(lang->dict, &key, kouho, Maxkouho);
|
|
||||||
if(CT_EQ_INT(t, 2, n)){
|
|
||||||
checkstr(t, "candidate 1", "候補1", &kouho[0]);
|
|
||||||
checkstr(t, "candidate 2", "候補2", &kouho[1]);
|
|
||||||
}
|
|
||||||
p = many;
|
|
||||||
for(i = 0; i < Maxkouho+1; i++){
|
|
||||||
snprint(item, sizeof item, "c%02d", i);
|
|
||||||
if(i > 0)
|
|
||||||
*p++ = ' ';
|
|
||||||
memmove(p, item, strlen(item));
|
|
||||||
p += strlen(item);
|
|
||||||
}
|
|
||||||
*p = '\0';
|
|
||||||
trieput(lang->dict, "key", 3, many, strlen(many));
|
|
||||||
key = mkstr("key");
|
|
||||||
n = dictlookup(lang->dict, &key, kouho, Maxkouho);
|
|
||||||
if(CT_EQ_INT(t, Maxkouho, n)){
|
|
||||||
checkstr(t, "first capped candidate", "c00", &kouho[0]);
|
|
||||||
snprint(item, sizeof item, "c%02d", Maxkouho-1);
|
|
||||||
checkstr(t, "last capped candidate", item, &kouho[Maxkouho-1]);
|
|
||||||
}
|
|
||||||
CT_EQ_INT(t, 3, dictlookup(lang->dict, &key, kouho, 3));
|
|
||||||
trieclose(lang->dict);
|
|
||||||
lang->dict = saved;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
dictionary_misses(struct ct *t)
|
|
||||||
{
|
|
||||||
Str kouho[Maxkouho], key;
|
|
||||||
Trie *saved;
|
|
||||||
Lang *lang;
|
|
||||||
|
|
||||||
lang = getlang(LangJP);
|
|
||||||
saved = lang->dict;
|
|
||||||
lang->dict = trienew();
|
|
||||||
key = mkstr("");
|
|
||||||
CT_EQ_INT(t, 0, dictlookup(lang->dict, &key, kouho, Maxkouho));
|
|
||||||
key = mkstr("missing");
|
|
||||||
CT_EQ_INT(t, 0, dictlookup(lang->dict, &key, kouho, Maxkouho));
|
|
||||||
CT_EQ_INT(t, 0, dictlookup(getlang(LangKO)->dict, &key, kouho, Maxkouho));
|
|
||||||
trieclose(lang->dict);
|
|
||||||
lang->dict = saved;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
dictionary_prefix(struct ct *t)
|
|
||||||
{
|
|
||||||
Str kouho[Maxkouho], key;
|
|
||||||
Trie *dict;
|
|
||||||
|
|
||||||
dict = trienew();
|
|
||||||
trieput(dict, "smile", 5, "A B", 3);
|
|
||||||
trieput(dict, "smiley", 6, "B C", 3);
|
|
||||||
trieput(dict, "sad", 3, "D", 1);
|
|
||||||
key = mkstr("smile");
|
|
||||||
if(CT_EQ_INT(t, 4, dictprefix(dict, &key, kouho, Maxkouho))){
|
|
||||||
checkstr(t, "own entry first", "A", &kouho[0]);
|
|
||||||
checkstr(t, "entry below", "C", &kouho[3]);
|
|
||||||
}
|
|
||||||
key = mkstr("s");
|
|
||||||
CT_EQ_INT(t, 5, dictprefix(dict, &key, kouho, Maxkouho));
|
|
||||||
CT_EQ_INT(t, 2, dictprefix(dict, &key, kouho, 2));
|
|
||||||
key = mkstr("x");
|
|
||||||
CT_EQ_INT(t, 0, dictprefix(dict, &key, kouho, Maxkouho));
|
|
||||||
trieclose(dict);
|
|
||||||
}
|
|
||||||
2439
tests/engine_test.c
2439
tests/engine_test.c
File diff suppressed because it is too large
Load Diff
@@ -1,257 +0,0 @@
|
|||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Guard = 8,
|
|
||||||
Rgbmask = 0xffffff,
|
|
||||||
};
|
|
||||||
|
|
||||||
#define Testw (6 * Fontsz)
|
|
||||||
#define Testh (3 * Fontsz)
|
|
||||||
#define Testn (Testw * Testh)
|
|
||||||
|
|
||||||
static u32int guardcolor = 0x5a5a5a5a;
|
|
||||||
|
|
||||||
static void
|
|
||||||
fillpixels(u32int *p, int n, u32int color)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for(i = 0; i < n; i++)
|
|
||||||
p[i] = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
hasink(u32int *p, int n, u32int bg)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
bg &= Rgbmask;
|
|
||||||
for(i = 0; i < n; i++)
|
|
||||||
if((p[i] & Rgbmask) != bg)
|
|
||||||
return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
hascolors(u32int *p, int n, u32int bg)
|
|
||||||
{
|
|
||||||
u32int c, first;
|
|
||||||
int b, found, g, i, r;
|
|
||||||
|
|
||||||
bg &= Rgbmask;
|
|
||||||
first = 0;
|
|
||||||
found = 0;
|
|
||||||
for(i = 0; i < n; i++){
|
|
||||||
c = p[i] & Rgbmask;
|
|
||||||
if(c == bg)
|
|
||||||
continue;
|
|
||||||
r = c >> 16 & 0xff;
|
|
||||||
g = c >> 8 & 0xff;
|
|
||||||
b = c & 0xff;
|
|
||||||
if(r == g && g == b)
|
|
||||||
continue;
|
|
||||||
if(!found){
|
|
||||||
first = c;
|
|
||||||
found = 1;
|
|
||||||
}else if(c != first)
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
checkguards(struct ct *t, u32int *mem)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for(i = 0; i < Guard; i++){
|
|
||||||
CT_EQ_UINT(t, guardcolor, mem[i]);
|
|
||||||
CT_EQ_UINT(t, guardcolor, mem[Guard + Testn + i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
checkoutside(struct ct *t, u32int *buf, u32int bg, int row)
|
|
||||||
{
|
|
||||||
int x, y, y0, y1;
|
|
||||||
|
|
||||||
bg &= Rgbmask;
|
|
||||||
y0 = max(row, 0);
|
|
||||||
y1 = min(row + Fontsz, Testh);
|
|
||||||
for(y = 0; y < Testh; y++){
|
|
||||||
if(y >= y0 && y < y1)
|
|
||||||
continue;
|
|
||||||
for(x = 0; x < Testw; x++)
|
|
||||||
CT_EQ_UINT(t, bg, buf[y * Testw + x] & Rgbmask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
inkbounds(u32int *p, u32int bg, int *minx, int *miny, int *maxx, int *maxy)
|
|
||||||
{
|
|
||||||
int x, y;
|
|
||||||
|
|
||||||
bg &= Rgbmask;
|
|
||||||
*minx = Testw;
|
|
||||||
*miny = Testh;
|
|
||||||
*maxx = -1;
|
|
||||||
*maxy = -1;
|
|
||||||
for(y = 0; y < Testh; y++){
|
|
||||||
for(x = 0; x < Testw; x++){
|
|
||||||
if((p[y * Testw + x] & Rgbmask) == bg)
|
|
||||||
continue;
|
|
||||||
*minx = min(*minx, x);
|
|
||||||
*miny = min(*miny, y);
|
|
||||||
*maxx = max(*maxx, x);
|
|
||||||
*maxy = max(*maxy, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return *maxx >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
checktext(struct ct *t, u32int *buf, char *utf)
|
|
||||||
{
|
|
||||||
Str s;
|
|
||||||
int maxx, maxy, minx, miny, w;
|
|
||||||
|
|
||||||
s = mkstr(utf);
|
|
||||||
w = textwidth(&s);
|
|
||||||
CT_CHECK(t, w > 0);
|
|
||||||
CT_CHECK(t, w < Testw - Fontsz);
|
|
||||||
fillpixels(buf, Testn, Colbg);
|
|
||||||
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &s);
|
|
||||||
if(!CT_CHECK(t, inkbounds(buf, Colbg, &minx, &miny, &maxx, &maxy)))
|
|
||||||
return;
|
|
||||||
CT_CHECK(t, minx >= 0 && miny >= 0);
|
|
||||||
CT_CHECK(t, maxx < Testw && maxy < Testh);
|
|
||||||
CT_CHECK(t, maxx - minx + 1 <= w);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
checkcolor(struct ct *t, u32int *buf, char *utf)
|
|
||||||
{
|
|
||||||
Str s;
|
|
||||||
int w;
|
|
||||||
|
|
||||||
s = mkstr(utf);
|
|
||||||
w = textwidth(&s);
|
|
||||||
CT_CHECK(t, w > 0);
|
|
||||||
if(s.n > 1)
|
|
||||||
CT_CHECK(t, w < s.n * Fontsz);
|
|
||||||
fillpixels(buf, Testn, Colbg);
|
|
||||||
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &s);
|
|
||||||
CT_CHECK(t, hasink(buf, Testn, Colbg));
|
|
||||||
CT_CHECK(t, hascolors(buf, Testn, Colbg));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
checkshape(struct ct *t, u32int *buf, char *utf)
|
|
||||||
{
|
|
||||||
Str s;
|
|
||||||
int w;
|
|
||||||
|
|
||||||
s = mkstr(utf);
|
|
||||||
w = textwidth(&s);
|
|
||||||
CT_CHECK(t, w > 0);
|
|
||||||
CT_CHECK(t, w < s.n * Fontsz);
|
|
||||||
fillpixels(buf, Testn, Colbg);
|
|
||||||
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &s);
|
|
||||||
CT_CHECK(t, hasink(buf, Testn, Colbg));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
checkclip(struct ct *t, u32int *mem, int x, int y, Str *s)
|
|
||||||
{
|
|
||||||
u32int *buf;
|
|
||||||
|
|
||||||
buf = mem + Guard;
|
|
||||||
fillpixels(buf, Testn, Colbg);
|
|
||||||
textdraw(buf, Testw, Testh, x, y, Testw, Colfg, s);
|
|
||||||
CT_CHECK(t, hasink(buf, Testn, Colbg));
|
|
||||||
checkoutside(t, buf, Colbg, y);
|
|
||||||
checkguards(t, mem);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
font_render(struct ct *t)
|
|
||||||
{
|
|
||||||
static char *plain[] = { "A", "가", "あ", "漢", "☆", "𠀋" };
|
|
||||||
u32int *buf, *mem;
|
|
||||||
Str a, heart, longrow, missing;
|
|
||||||
int i, maxx, maxy, minx, miny, natural, w, x, y;
|
|
||||||
|
|
||||||
mem = emalloc((Testn + 2 * Guard) * sizeof mem[0]);
|
|
||||||
fillpixels(mem, Testn + 2 * Guard, guardcolor);
|
|
||||||
buf = mem + Guard;
|
|
||||||
fillpixels(buf, Testn, Colbg);
|
|
||||||
a = mkstr("A");
|
|
||||||
textinit();
|
|
||||||
for(i = 0; i < nelem(plain); i++)
|
|
||||||
checktext(t, buf, plain[i]);
|
|
||||||
|
|
||||||
checkcolor(t, buf, "😀");
|
|
||||||
checkcolor(t, buf, "❤️");
|
|
||||||
checkcolor(t, buf, "☕️");
|
|
||||||
checkshape(t, buf, "👩💻");
|
|
||||||
checkshape(t, buf, "👍🏽");
|
|
||||||
checkshape(t, buf, "🇰🇷");
|
|
||||||
checkshape(t, buf, "☕︎");
|
|
||||||
|
|
||||||
heart = mkstr("❤️");
|
|
||||||
fillpixels(buf, Testn, Colsel);
|
|
||||||
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &heart);
|
|
||||||
CT_CHECK(t, hascolors(buf, Testn, Colsel));
|
|
||||||
CT_EQ_UINT(t, Colsel,
|
|
||||||
buf[(Fontsz + Fontsz/2) * Testw + Testw - 1] & Rgbmask);
|
|
||||||
checkoutside(t, buf, Colsel, Fontsz);
|
|
||||||
checkguards(t, mem);
|
|
||||||
|
|
||||||
w = textwidth(&a);
|
|
||||||
checkclip(t, mem, -w / 2, Fontsz, &a);
|
|
||||||
checkclip(t, mem, Testw - w / 2, Fontsz, &a);
|
|
||||||
checkclip(t, mem, Fontsz, -Fontsz / 2, &a);
|
|
||||||
checkclip(t, mem, Fontsz, Testh - Fontsz / 2, &a);
|
|
||||||
|
|
||||||
longrow = mkstr("abcdefghijklmnopqrstuvwxyz");
|
|
||||||
natural = textwidth(&longrow);
|
|
||||||
CT_CHECK(t, natural > 2*Fontsz);
|
|
||||||
fillpixels(buf, Testn, Colbg);
|
|
||||||
textdraw(buf, Testw, Testh, 2*Fontsz, Fontsz, 2*Fontsz, Colfg,
|
|
||||||
&longrow);
|
|
||||||
CT_CHECK(t, hasink(buf, Testn, Colbg));
|
|
||||||
for(y = Fontsz; y < 2*Fontsz; y++){
|
|
||||||
for(x = 0; x < 2*Fontsz; x++)
|
|
||||||
CT_EQ_UINT(t, Colbg, buf[y * Testw + x] & Rgbmask);
|
|
||||||
for(x = 4*Fontsz; x < Testw; x++)
|
|
||||||
CT_EQ_UINT(t, Colbg, buf[y * Testw + x] & Rgbmask);
|
|
||||||
}
|
|
||||||
CT_EQ_INT(t, natural, textwidth(&longrow));
|
|
||||||
|
|
||||||
fillpixels(buf, Testn, Colsel);
|
|
||||||
textdraw(buf, Testw, Testh, 2*Fontsz, Fontsz, 2*Fontsz, Colselfg,
|
|
||||||
&longrow);
|
|
||||||
CT_CHECK(t, (Colselfg & Rgbmask) != (Colsel & Rgbmask));
|
|
||||||
if(CT_CHECK(t, inkbounds(buf, Colsel,
|
|
||||||
&minx, &miny, &maxx, &maxy))){
|
|
||||||
CT_CHECK(t, minx >= 2*Fontsz);
|
|
||||||
CT_CHECK(t, maxx < 4*Fontsz);
|
|
||||||
CT_CHECK(t, miny >= Fontsz);
|
|
||||||
CT_CHECK(t, maxy < 2*Fontsz);
|
|
||||||
}
|
|
||||||
checkoutside(t, buf, Colsel, Fontsz);
|
|
||||||
checkguards(t, mem);
|
|
||||||
CT_EQ_INT(t, natural, textwidth(&longrow));
|
|
||||||
|
|
||||||
missing = mkstr("\xF4\x8F\xBF\xBF");
|
|
||||||
for(i = 0; i < 32; i++){
|
|
||||||
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &heart);
|
|
||||||
textdraw(buf, Testw, Testh, 0, 0, Testw, Colfg, &missing);
|
|
||||||
}
|
|
||||||
checkguards(t, mem);
|
|
||||||
free(mem);
|
|
||||||
textclose();
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,386 +0,0 @@
|
|||||||
#define _POSIX_C_SOURCE 200809L
|
|
||||||
#include <ibus.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
typedef struct Log Log;
|
|
||||||
struct Log
|
|
||||||
{
|
|
||||||
GMainLoop *loop;
|
|
||||||
int *waiting;
|
|
||||||
IBusInputContext *a;
|
|
||||||
IBusInputContext *clearctx;
|
|
||||||
int legacy;
|
|
||||||
int modern;
|
|
||||||
int commit;
|
|
||||||
int invalid;
|
|
||||||
int sawpreedit;
|
|
||||||
int sawcommit;
|
|
||||||
int done;
|
|
||||||
int transfer;
|
|
||||||
int atext;
|
|
||||||
int aclear;
|
|
||||||
int retake;
|
|
||||||
int retook;
|
|
||||||
char revent[16];
|
|
||||||
int rn;
|
|
||||||
int repeat;
|
|
||||||
int rfirst;
|
|
||||||
int rcommit;
|
|
||||||
int rdone;
|
|
||||||
int flushed;
|
|
||||||
int hanja;
|
|
||||||
int required;
|
|
||||||
int deleted;
|
|
||||||
int delbefore;
|
|
||||||
int delcount;
|
|
||||||
int converted;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
revent(Log *log, char c)
|
|
||||||
{
|
|
||||||
if(log->rn + 1 < (int)sizeof log->revent){
|
|
||||||
log->revent[log->rn++] = c;
|
|
||||||
log->revent[log->rn] = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
legacy(IBusInputContext *ctx, IBusText *text, guint cursor,
|
|
||||||
gboolean visible, void *arg)
|
|
||||||
{
|
|
||||||
IBusAttrList *attrs;
|
|
||||||
IBusAttribute *a;
|
|
||||||
Log *log;
|
|
||||||
const char *s;
|
|
||||||
|
|
||||||
(void)ctx;
|
|
||||||
log = arg;
|
|
||||||
log->legacy++;
|
|
||||||
s = ibus_text_get_text(text);
|
|
||||||
if(log->repeat == 3){
|
|
||||||
if(s[0] != '\0' || cursor != 0 || visible)
|
|
||||||
log->invalid = 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(log->repeat != 0){
|
|
||||||
revent(log, 'P');
|
|
||||||
if(log->repeat == 1){
|
|
||||||
if(s[0] == '\0'){
|
|
||||||
if(cursor != 0 || visible)
|
|
||||||
log->invalid = 1;
|
|
||||||
}else if(strcmp(s, "ㅋ") != 0 || cursor != 1 || !visible)
|
|
||||||
log->invalid = 1;
|
|
||||||
else
|
|
||||||
log->rfirst = 1;
|
|
||||||
}else if(s[0] == '\0'){
|
|
||||||
if(cursor != 0 || visible || log->rcommit)
|
|
||||||
log->invalid = 1;
|
|
||||||
}else if(strcmp(s, "ㅋ") != 0 || cursor != 1 || !visible ||
|
|
||||||
!log->rcommit)
|
|
||||||
log->invalid = 1;
|
|
||||||
else
|
|
||||||
log->rdone = 1;
|
|
||||||
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
|
||||||
g_main_loop_quit(log->loop);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(strcmp(s, "k") == 0){
|
|
||||||
attrs = ibus_text_get_attributes(text);
|
|
||||||
a = attrs == NULL ? NULL : ibus_attr_list_get(attrs, 0);
|
|
||||||
if(cursor != 1 || !visible || attrs == NULL || a == NULL ||
|
|
||||||
a->type != IBUS_ATTR_TYPE_UNDERLINE ||
|
|
||||||
a->value != IBUS_ATTR_UNDERLINE_SINGLE ||
|
|
||||||
a->start_index != 0 || a->end_index != 1 ||
|
|
||||||
ibus_attr_list_get(attrs, 1) != NULL)
|
|
||||||
log->invalid = 1;
|
|
||||||
else{
|
|
||||||
log->sawpreedit = 1;
|
|
||||||
if(log->transfer && ctx == log->a)
|
|
||||||
log->atext = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(log->transfer && log->atext && !log->aclear && s[0] == '\0'){
|
|
||||||
if(cursor != 0 || visible)
|
|
||||||
log->invalid = 1;
|
|
||||||
else{
|
|
||||||
log->clearctx = ctx;
|
|
||||||
if(ctx == log->a)
|
|
||||||
log->aclear = 1;
|
|
||||||
else
|
|
||||||
log->invalid = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log->done = log->sawpreedit && log->sawcommit;
|
|
||||||
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
|
||||||
g_main_loop_quit(log->loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
modern(IBusInputContext *ctx, IBusText *text, guint cursor,
|
|
||||||
gboolean visible, guint mode, void *arg)
|
|
||||||
{
|
|
||||||
Log *log;
|
|
||||||
|
|
||||||
(void)ctx;
|
|
||||||
(void)text;
|
|
||||||
(void)cursor;
|
|
||||||
(void)visible;
|
|
||||||
(void)mode;
|
|
||||||
log = arg;
|
|
||||||
log->modern++;
|
|
||||||
log->invalid = 1;
|
|
||||||
if(log->loop != NULL)
|
|
||||||
g_main_loop_quit(log->loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
commit(IBusInputContext *ctx, IBusText *text, void *arg)
|
|
||||||
{
|
|
||||||
IBusAttrList *attrs;
|
|
||||||
Log *log;
|
|
||||||
const char *s;
|
|
||||||
|
|
||||||
(void)ctx;
|
|
||||||
log = arg;
|
|
||||||
log->commit++;
|
|
||||||
s = ibus_text_get_text(text);
|
|
||||||
attrs = ibus_text_get_attributes(text);
|
|
||||||
if(log->hanja){
|
|
||||||
/* The word converted, with the client\'s own syllable taken back. */
|
|
||||||
if(strcmp(s, "漢字") == 0 && log->deleted && log->delbefore == -1 &&
|
|
||||||
log->delcount == 1)
|
|
||||||
log->converted = 1;
|
|
||||||
else
|
|
||||||
log->invalid = 1;
|
|
||||||
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
|
||||||
g_main_loop_quit(log->loop);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(log->repeat == 3){
|
|
||||||
/* Focus loss hands the pending syllable back as a commit. */
|
|
||||||
if(strcmp(s, "ㅋ") == 0)
|
|
||||||
log->flushed = 1;
|
|
||||||
else
|
|
||||||
log->invalid = 1;
|
|
||||||
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
|
||||||
g_main_loop_quit(log->loop);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(log->repeat != 0){
|
|
||||||
revent(log, 'K');
|
|
||||||
if(log->repeat != 2 || strcmp(s, "ㅋ") != 0 || attrs == NULL ||
|
|
||||||
ibus_attr_list_get(attrs, 0) != NULL)
|
|
||||||
log->invalid = 1;
|
|
||||||
else
|
|
||||||
log->rcommit = 1;
|
|
||||||
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
|
||||||
g_main_loop_quit(log->loop);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(log->retake){
|
|
||||||
/* Typing again takes back the reading the other context took. */
|
|
||||||
if(strcmp(s, "k") == 0)
|
|
||||||
log->retook = 1;
|
|
||||||
else
|
|
||||||
log->invalid = 1;
|
|
||||||
log->retake = 0;
|
|
||||||
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
|
||||||
g_main_loop_quit(log->loop);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(strcmp(s, "か") != 0 || attrs == NULL ||
|
|
||||||
ibus_attr_list_get(attrs, 0) != NULL)
|
|
||||||
log->invalid = 1;
|
|
||||||
else
|
|
||||||
log->sawcommit = 1;
|
|
||||||
log->done = log->sawpreedit && log->sawcommit;
|
|
||||||
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
|
||||||
g_main_loop_quit(log->loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
required(IBusInputContext *ctx, void *arg)
|
|
||||||
{
|
|
||||||
Log *log;
|
|
||||||
|
|
||||||
(void)ctx;
|
|
||||||
log = arg;
|
|
||||||
log->required = 1;
|
|
||||||
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
|
||||||
g_main_loop_quit(log->loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
deleted(IBusInputContext *ctx, gint offset, guint n, void *arg)
|
|
||||||
{
|
|
||||||
Log *log;
|
|
||||||
|
|
||||||
(void)ctx;
|
|
||||||
log = arg;
|
|
||||||
log->deleted = 1;
|
|
||||||
log->delbefore = offset;
|
|
||||||
log->delcount = n;
|
|
||||||
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
|
||||||
g_main_loop_quit(log->loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
timeout(void *arg)
|
|
||||||
{
|
|
||||||
g_main_loop_quit(arg);
|
|
||||||
return G_SOURCE_REMOVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
waitflag(Log *log, int *flag)
|
|
||||||
{
|
|
||||||
guint timer;
|
|
||||||
|
|
||||||
if(*flag)
|
|
||||||
return 1;
|
|
||||||
log->waiting = flag;
|
|
||||||
log->loop = g_main_loop_new(NULL, FALSE);
|
|
||||||
timer = g_timeout_add_seconds(4, timeout, log->loop);
|
|
||||||
g_main_loop_run(log->loop);
|
|
||||||
if(g_main_context_find_source_by_id(NULL, timer) != NULL)
|
|
||||||
g_source_remove(timer);
|
|
||||||
g_main_loop_unref(log->loop);
|
|
||||||
log->loop = NULL;
|
|
||||||
log->waiting = NULL;
|
|
||||||
return *flag;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
IBusBus *bus;
|
|
||||||
IBusInputContext *a, *b, *c;
|
|
||||||
IBusText *around;
|
|
||||||
Log log;
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
if(argc != 2){
|
|
||||||
fprintf(stderr, "usage: ibus_client_smoke address\n");
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
if(setenv("IBUS_ADDRESS", argv[1], 1) < 0 ||
|
|
||||||
unsetenv("IBUS_ADDRESS_FILE") < 0 ||
|
|
||||||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 || unsetenv("DISPLAY") < 0){
|
|
||||||
perror("ibus_client_smoke: environment");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
memset(&log, 0, sizeof log);
|
|
||||||
b = c = NULL;
|
|
||||||
ibus_init();
|
|
||||||
bus = ibus_bus_new();
|
|
||||||
if(bus == NULL || !ibus_bus_is_connected(bus)){
|
|
||||||
fprintf(stderr, "ibus_client_smoke: cannot connect to private bus\n");
|
|
||||||
if(bus != NULL) g_object_unref(bus);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
a = ibus_bus_create_input_context(bus, "strans-libibus-smoke-a");
|
|
||||||
if(a == NULL){
|
|
||||||
fprintf(stderr, "ibus_client_smoke: cannot create input context\n");
|
|
||||||
g_object_unref(bus);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
log.a = a;
|
|
||||||
g_signal_connect(a, "update-preedit-text", G_CALLBACK(legacy), &log);
|
|
||||||
g_signal_connect(a, "update-preedit-text-with-mode", G_CALLBACK(modern),
|
|
||||||
&log);
|
|
||||||
g_signal_connect(a, "commit-text", G_CALLBACK(commit), &log);
|
|
||||||
ibus_input_context_set_capabilities(a, IBUS_CAP_PREEDIT_TEXT);
|
|
||||||
ibus_input_context_focus_in(a);
|
|
||||||
ok = ibus_input_context_process_key_event(a, 'n', 0, IBUS_CONTROL_MASK) &&
|
|
||||||
ibus_input_context_process_key_event(a, 'k', 0, 0) &&
|
|
||||||
ibus_input_context_process_key_event(a, 'a', 0, 0) &&
|
|
||||||
ibus_input_context_process_key_event(a, '0', 0, 0) &&
|
|
||||||
waitflag(&log, &log.done);
|
|
||||||
b = ibus_bus_create_input_context(bus, "strans-libibus-smoke-b");
|
|
||||||
if(b == NULL)
|
|
||||||
ok = 0;
|
|
||||||
else{
|
|
||||||
g_signal_connect(b, "update-preedit-text", G_CALLBACK(legacy), &log);
|
|
||||||
g_signal_connect(b, "update-preedit-text-with-mode",
|
|
||||||
G_CALLBACK(modern), &log);
|
|
||||||
ibus_input_context_set_capabilities(b, IBUS_CAP_PREEDIT_TEXT);
|
|
||||||
log.transfer = 1;
|
|
||||||
ok = ok && ibus_input_context_process_key_event(a, 'k', 0, 0) &&
|
|
||||||
waitflag(&log, &log.atext);
|
|
||||||
ibus_input_context_focus_in(b);
|
|
||||||
ok = ok && ibus_input_context_process_key_event(b, 'n', 0, 0) &&
|
|
||||||
waitflag(&log, &log.aclear);
|
|
||||||
ibus_input_context_focus_out(b);
|
|
||||||
}
|
|
||||||
ibus_input_context_focus_in(a);
|
|
||||||
log.retake = 1;
|
|
||||||
ok = ok && ibus_input_context_process_key_event(a, 's', 0,
|
|
||||||
IBUS_CONTROL_MASK) && waitflag(&log, &log.retook);
|
|
||||||
log.repeat = 1;
|
|
||||||
ok = ok && ibus_input_context_process_key_event(a, 'z', 0, 0) &&
|
|
||||||
waitflag(&log, &log.rfirst);
|
|
||||||
log.repeat = 2;
|
|
||||||
log.revent[0] = '\0';
|
|
||||||
log.rn = 0;
|
|
||||||
ok = ok && ibus_input_context_process_key_event(a, 'z', 0, 0) &&
|
|
||||||
waitflag(&log, &log.rdone) && strcmp(log.revent, "PKP") == 0;
|
|
||||||
log.repeat = 3;
|
|
||||||
ibus_input_context_focus_out(a);
|
|
||||||
ok = ok && waitflag(&log, &log.flushed);
|
|
||||||
/*
|
|
||||||
* A word is committed a syllable at a time, so the Hanja key must
|
|
||||||
* reach into what the client already holds and ask for it back.
|
|
||||||
*/
|
|
||||||
c = ibus_bus_create_input_context(bus, "strans-libibus-smoke-c");
|
|
||||||
if(c == NULL)
|
|
||||||
ok = 0;
|
|
||||||
else{
|
|
||||||
g_signal_connect(c, "commit-text", G_CALLBACK(commit), &log);
|
|
||||||
g_signal_connect(c, "require-surrounding-text",
|
|
||||||
G_CALLBACK(required), &log);
|
|
||||||
g_signal_connect(c, "delete-surrounding-text",
|
|
||||||
G_CALLBACK(deleted), &log);
|
|
||||||
log.repeat = 0;
|
|
||||||
ibus_input_context_set_capabilities(c,
|
|
||||||
IBUS_CAP_PREEDIT_TEXT|IBUS_CAP_SURROUNDING_TEXT);
|
|
||||||
ibus_input_context_focus_in(c);
|
|
||||||
ok = ok && waitflag(&log, &log.required);
|
|
||||||
around = ibus_text_new_from_string("저는 한");
|
|
||||||
ibus_input_context_set_surrounding_text(c, around, 4, 4);
|
|
||||||
log.hanja = 1;
|
|
||||||
ok = ok && ibus_input_context_process_key_event(c, 's', 0,
|
|
||||||
IBUS_CONTROL_MASK) &&
|
|
||||||
ibus_input_context_process_key_event(c, 'w', 0, 0) &&
|
|
||||||
ibus_input_context_process_key_event(c, 'k', 0, 0) &&
|
|
||||||
ibus_input_context_process_key_event(c, 'h', 0,
|
|
||||||
IBUS_CONTROL_MASK) &&
|
|
||||||
ibus_input_context_process_key_event(c, IBUS_KEY_Return,
|
|
||||||
0, 0) && waitflag(&log, &log.converted);
|
|
||||||
log.hanja = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(log.legacy == 0 || log.modern != 0 || log.commit != 5 ||
|
|
||||||
log.invalid || !log.done || !log.atext || !log.aclear ||
|
|
||||||
!log.retook || log.clearctx != a || !log.rdone || !log.converted)
|
|
||||||
ok = 0;
|
|
||||||
if(c != NULL)
|
|
||||||
g_object_unref(c);
|
|
||||||
if(b != NULL)
|
|
||||||
g_object_unref(b);
|
|
||||||
g_object_unref(a);
|
|
||||||
g_object_unref(bus);
|
|
||||||
if(!ok){
|
|
||||||
fprintf(stderr,
|
|
||||||
"ibus_client_smoke: legacy=%d modern=%d commit=%d invalid=%d preedit=%d committed=%d transfer-text=%d a-clear=%d retook=%d repeat=%s flushed=%d required=%d deleted=%d,%d converted=%d\n",
|
|
||||||
log.legacy, log.modern, log.commit, log.invalid,
|
|
||||||
log.sawpreedit, log.sawcommit, log.atext, log.aclear,
|
|
||||||
log.retook, log.revent, log.flushed, log.required,
|
|
||||||
log.delbefore, log.delcount, log.converted);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("official libibus client preedit, commit, owner clear, take-back and focus-out hand-back: ok\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
#define _GNU_SOURCE
|
|
||||||
#include <errno.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "live.h"
|
|
||||||
#include "livebus.h"
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Maxconnections = 64,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int
|
|
||||||
invalidcall(DBusConnection *conn, char *path, char *member)
|
|
||||||
{
|
|
||||||
DBusMessage *reply;
|
|
||||||
const char *name;
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
reply = sendcall(conn, contextcall(path, member), member);
|
|
||||||
if(reply == NULL)
|
|
||||||
return 0;
|
|
||||||
name = dbus_message_get_error_name(reply);
|
|
||||||
ok = dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR &&
|
|
||||||
name != NULL && strcmp(name, DBUS_ERROR_INVALID_ARGS) == 0;
|
|
||||||
if(!ok)
|
|
||||||
fail("malformed %s returned %s, expected %s", member,
|
|
||||||
name != NULL ? name : "a non-error reply",
|
|
||||||
DBUS_ERROR_INVALID_ARGS);
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* libibus destroys through the Service interface; the path must go away. */
|
|
||||||
static int
|
|
||||||
destroyed(DBusConnection *conn, char *path)
|
|
||||||
{
|
|
||||||
DBusMessage *m, *reply;
|
|
||||||
const char *name;
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
m = dbus_message_new_method_call("org.freedesktop.IBus", path,
|
|
||||||
"org.freedesktop.IBus.Service", "Destroy");
|
|
||||||
reply = callret(conn, m, "Service.Destroy");
|
|
||||||
if(reply == NULL)
|
|
||||||
return 0;
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
reply = sendcall(conn, contextcall(path, "FocusIn"), "FocusIn");
|
|
||||||
if(reply == NULL)
|
|
||||||
return 0;
|
|
||||||
name = dbus_message_get_error_name(reply);
|
|
||||||
ok = name != NULL && strcmp(name, DBUS_ERROR_UNKNOWN_OBJECT) == 0;
|
|
||||||
if(!ok)
|
|
||||||
fail("context survived Service.Destroy");
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
overflowrejected(char *address)
|
|
||||||
{
|
|
||||||
DBusConnection *conn;
|
|
||||||
DBusError err;
|
|
||||||
struct pollfd pfd;
|
|
||||||
int fd, n, ok, timeout;
|
|
||||||
int64_t deadline;
|
|
||||||
|
|
||||||
dbus_error_init(&err);
|
|
||||||
conn = dbus_connection_open_private(address, &err);
|
|
||||||
if(conn == NULL){
|
|
||||||
ok = dbus_error_is_set(&err);
|
|
||||||
dbus_error_free(&err);
|
|
||||||
return ok || fail("overflow connection failed without a D-Bus error");
|
|
||||||
}
|
|
||||||
dbus_error_free(&err);
|
|
||||||
dbus_connection_set_exit_on_disconnect(conn, FALSE);
|
|
||||||
if(!dbus_connection_get_unix_fd(conn, &fd)){
|
|
||||||
closebus(&conn);
|
|
||||||
return fail("overflow connection has no Unix descriptor");
|
|
||||||
}
|
|
||||||
ok = 0;
|
|
||||||
deadline = nowms() + Calltimeout;
|
|
||||||
for(;;){
|
|
||||||
if(!dbus_connection_get_is_connected(conn)){
|
|
||||||
ok = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
timeout = leftms(deadline);
|
|
||||||
if(timeout == 0){
|
|
||||||
fail("65th IBus connection was not rejected");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pfd.fd = fd;
|
|
||||||
pfd.events = POLLIN;
|
|
||||||
pfd.revents = 0;
|
|
||||||
n = poll(&pfd, 1, timeout);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n < 0){
|
|
||||||
fail("poll overflow IBus connection: %s", strerror(errno));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(n == 0)
|
|
||||||
continue;
|
|
||||||
if(pfd.revents & POLLNVAL){
|
|
||||||
fail("overflow IBus descriptor became invalid");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
dbus_connection_read_write_dispatch(conn, 0);
|
|
||||||
}
|
|
||||||
closebus(&conn);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
runcontract(char *address)
|
|
||||||
{
|
|
||||||
static char *bad[] = {
|
|
||||||
"ProcessKeyEvent",
|
|
||||||
"SetEngine",
|
|
||||||
"SetCapabilities",
|
|
||||||
"SetCursorLocation",
|
|
||||||
"SetCursorLocationRelative",
|
|
||||||
"SetSurroundingText",
|
|
||||||
};
|
|
||||||
DBusConnection *conn;
|
|
||||||
char path[96];
|
|
||||||
int i, ok;
|
|
||||||
|
|
||||||
conn = openbus(address, "contract");
|
|
||||||
if(conn == NULL)
|
|
||||||
return 0;
|
|
||||||
ok = hello(conn, NULL, 0, "contract Hello") &&
|
|
||||||
createcontext(conn, path, sizeof path, "contract context");
|
|
||||||
for(i = 0; ok && i < (int)(sizeof bad / sizeof bad[0]); i++)
|
|
||||||
ok = invalidcall(conn, path, bad[i]);
|
|
||||||
ok = ok && destroyed(conn, path);
|
|
||||||
closebus(&conn);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
runcapacity(char *address)
|
|
||||||
{
|
|
||||||
DBusConnection *conn[Maxconnections];
|
|
||||||
int i, ok;
|
|
||||||
|
|
||||||
for(i = 0; i < Maxconnections; i++)
|
|
||||||
conn[i] = NULL;
|
|
||||||
ok = 0;
|
|
||||||
for(i = 0; i < Maxconnections; i++){
|
|
||||||
conn[i] = openbus(address, "capacity");
|
|
||||||
if(conn[i] == NULL || !hello(conn[i], NULL, 0, "capacity Hello"))
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if(!overflowrejected(address))
|
|
||||||
goto out;
|
|
||||||
closebus(&conn[Maxconnections-1]);
|
|
||||||
if(!createcontext(conn[0], NULL, 0, "capacity context"))
|
|
||||||
goto out;
|
|
||||||
conn[Maxconnections-1] = openbus(address, "replacement");
|
|
||||||
if(conn[Maxconnections-1] == NULL ||
|
|
||||||
!hello(conn[Maxconnections-1], NULL, 0, "replacement Hello") ||
|
|
||||||
!createcontext(conn[Maxconnections-1], NULL, 0, "replacement context"))
|
|
||||||
goto out;
|
|
||||||
ok = 1;
|
|
||||||
out:
|
|
||||||
for(i = 0; i < Maxconnections; i++)
|
|
||||||
closebus(&conn[i]);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
runclient(Live *l, char *address, char *program)
|
|
||||||
{
|
|
||||||
pid_t pid, n;
|
|
||||||
int status;
|
|
||||||
int64_t deadline;
|
|
||||||
|
|
||||||
pid = fork();
|
|
||||||
if(pid < 0)
|
|
||||||
return fail("fork official libibus client: %s", strerror(errno));
|
|
||||||
if(pid == 0){
|
|
||||||
if(setenv("XDG_RUNTIME_DIR", l->runtime, 1) < 0 ||
|
|
||||||
setenv("XDG_CONFIG_HOME", l->config, 1) < 0 ||
|
|
||||||
setenv("HOME", l->home, 1) < 0 ||
|
|
||||||
unsetenv("IBUS_ADDRESS_FILE") < 0)
|
|
||||||
_exit(126);
|
|
||||||
execl(program, program, address, (char*)0);
|
|
||||||
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
|
||||||
_exit(127);
|
|
||||||
}
|
|
||||||
deadline = nowms() + Starttimeout;
|
|
||||||
for(;;){
|
|
||||||
n = waitpid(pid, &status, WNOHANG);
|
|
||||||
if(n == pid)
|
|
||||||
break;
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n < 0)
|
|
||||||
return fail("wait official libibus client: %s", strerror(errno));
|
|
||||||
if(leftms(deadline) == 0){
|
|
||||||
fail("official libibus client timed out");
|
|
||||||
kill(pid, SIGKILL);
|
|
||||||
do
|
|
||||||
n = waitpid(pid, &status, 0);
|
|
||||||
while(n < 0 && errno == EINTR);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
pausems(10);
|
|
||||||
}
|
|
||||||
if(!WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
|
||||||
return fail("official libibus client exited with wait status %#x",
|
|
||||||
status);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
Daemon daemon;
|
|
||||||
Live live;
|
|
||||||
char address[512];
|
|
||||||
int capacity, ok;
|
|
||||||
|
|
||||||
testname = "ibus_live_test";
|
|
||||||
capacity = argc == 4 && strcmp(argv[1], "--capacity") == 0;
|
|
||||||
if(argc != 4){
|
|
||||||
fprintf(stderr, "usage: ibus_live_test strans mapdir libibus-client\n");
|
|
||||||
fprintf(stderr, " ibus_live_test --capacity strans mapdir\n");
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
daemoninit(&daemon, "daemon");
|
|
||||||
ok = livesetup(&live, "ibus") &&
|
|
||||||
startdaemon(&live, &daemon, argv[capacity ? 2 : 1],
|
|
||||||
argv[capacity ? 3 : 2]) &&
|
|
||||||
waitready(&live, &daemon, address, sizeof address);
|
|
||||||
if(ok)
|
|
||||||
ok = capacity ? runcapacity(address) :
|
|
||||||
runclient(&live, address, argv[3]) && runcontract(address);
|
|
||||||
if(!ok)
|
|
||||||
showerrors(&daemon);
|
|
||||||
if(!stopdaemon(&daemon))
|
|
||||||
ok = 0;
|
|
||||||
if(!closeerrors(&daemon))
|
|
||||||
ok = 0;
|
|
||||||
if(!liveclean(&live))
|
|
||||||
ok = 0;
|
|
||||||
if(!ok)
|
|
||||||
return 1;
|
|
||||||
printf(capacity ? "ibus connection capacity: ok\n" :
|
|
||||||
"ibus official client and malformed call: ok\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,439 +0,0 @@
|
|||||||
#include "ibus.c"
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Testctrlmask = 1<<2,
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct Ibusfix Ibusfix;
|
|
||||||
struct Ibusfix
|
|
||||||
{
|
|
||||||
Channel *oldreply;
|
|
||||||
Pump pump;
|
|
||||||
/* A fake connection is nothing but an address to tell two apart. */
|
|
||||||
char fake[2];
|
|
||||||
DBusConnection *c1;
|
|
||||||
DBusConnection *c2;
|
|
||||||
};
|
|
||||||
|
|
||||||
void
|
|
||||||
ibus_machine_id_fallback(struct ct *t)
|
|
||||||
{
|
|
||||||
char root[] = "/tmp/strans-machine-id.XXXXXX";
|
|
||||||
char primary[128], fallback[128], override[128], got[512];
|
|
||||||
char *path[2], *old, *saved;
|
|
||||||
FILE *fp;
|
|
||||||
int madeprimary, madefallback;
|
|
||||||
|
|
||||||
madeprimary = 0;
|
|
||||||
madefallback = 0;
|
|
||||||
if(!CT_CHECK(t, mkdtemp(root) != nil))
|
|
||||||
return;
|
|
||||||
if(!CT_CHECK(t, snprintf(primary, sizeof primary, "%s/primary", root)
|
|
||||||
< (int)sizeof primary) ||
|
|
||||||
!CT_CHECK(t, snprintf(fallback, sizeof fallback, "%s/fallback", root)
|
|
||||||
< (int)sizeof fallback))
|
|
||||||
goto cleanup;
|
|
||||||
if(!CT_CHECK(t, mkdir(primary, 0700) == 0))
|
|
||||||
goto cleanup;
|
|
||||||
madeprimary = 1;
|
|
||||||
fp = fopen(fallback, "w");
|
|
||||||
if(!CT_CHECK(t, fp != nil))
|
|
||||||
goto cleanup;
|
|
||||||
madefallback = 1;
|
|
||||||
if(!CT_CHECK(t, fputs("fallback-machine-id\n", fp) >= 0)){
|
|
||||||
fclose(fp);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
if(!CT_CHECK(t, fclose(fp) == 0))
|
|
||||||
goto cleanup;
|
|
||||||
path[0] = primary;
|
|
||||||
path[1] = fallback;
|
|
||||||
machineidfiles(got, sizeof got, path, nelem(path));
|
|
||||||
CT_EQ_STR(t, "fallback-machine-id", got);
|
|
||||||
path[1] = primary;
|
|
||||||
machineidfiles(got, sizeof got, path, nelem(path));
|
|
||||||
CT_EQ_STR(t, "", got);
|
|
||||||
|
|
||||||
old = getenv("IBUS_ADDRESS_FILE");
|
|
||||||
saved = old == nil ? nil : strdup(old);
|
|
||||||
if(old == nil || CT_CHECK(t, saved != nil)){
|
|
||||||
if(CT_CHECK(t, snprintf(override, sizeof override, "%s/address", root)
|
|
||||||
< (int)sizeof override) &&
|
|
||||||
CT_CHECK(t, setenv("IBUS_ADDRESS_FILE", override, 1) == 0)){
|
|
||||||
CT_EQ_INT(t, 0, buildaddrpath(got, sizeof got));
|
|
||||||
CT_EQ_STR(t, override, got);
|
|
||||||
CT_CHECK(t, setenv("IBUS_ADDRESS_FILE", "", 1) == 0);
|
|
||||||
CT_EQ_INT(t, -1, buildaddrpath(got, sizeof got));
|
|
||||||
}
|
|
||||||
if(saved != nil){
|
|
||||||
setenv("IBUS_ADDRESS_FILE", saved, 1);
|
|
||||||
free(saved);
|
|
||||||
}else
|
|
||||||
unsetenv("IBUS_ADDRESS_FILE");
|
|
||||||
}
|
|
||||||
cleanup:
|
|
||||||
if(madefallback)
|
|
||||||
CT_CHECK(t, unlink(fallback) == 0);
|
|
||||||
if(madeprimary)
|
|
||||||
CT_CHECK(t, rmdir(primary) == 0);
|
|
||||||
CT_CHECK(t, rmdir(root) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
ibusbegin(Ibusfix *f)
|
|
||||||
{
|
|
||||||
Drawcmd dc;
|
|
||||||
|
|
||||||
memset(f, 0, sizeof *f);
|
|
||||||
memset(contexts, 0, sizeof contexts);
|
|
||||||
memset(conns, 0, sizeof conns);
|
|
||||||
nconns = 0;
|
|
||||||
preowner = nil;
|
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
|
||||||
;
|
|
||||||
testengineinit(LangEN);
|
|
||||||
f->oldreply = replyc;
|
|
||||||
replyc = chancreate(sizeof(Keyres), 0);
|
|
||||||
pumpstart(&f->pump, Maxcontexts);
|
|
||||||
f->c1 = (DBusConnection*)&f->fake[0];
|
|
||||||
f->c2 = (DBusConnection*)&f->fake[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
ibusend(Ibusfix *f)
|
|
||||||
{
|
|
||||||
Drawcmd dc;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for(i = 0; i < nelem(contexts); i++)
|
|
||||||
if(contexts[i].conn != nil)
|
|
||||||
dropcontext(&contexts[i]);
|
|
||||||
pumpstop(&f->pump);
|
|
||||||
memset(contexts, 0, sizeof contexts);
|
|
||||||
memset(conns, 0, sizeof conns);
|
|
||||||
nconns = 0;
|
|
||||||
preowner = nil;
|
|
||||||
testengineinit(LangEN);
|
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
|
||||||
;
|
|
||||||
chanfree(replyc);
|
|
||||||
replyc = f->oldreply;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Keyreq
|
|
||||||
nexttrace(struct ct *t, Ibusfix *f, int op, Ictx *ctx)
|
|
||||||
{
|
|
||||||
Keyreq req;
|
|
||||||
|
|
||||||
memset(&req, 0, sizeof req);
|
|
||||||
if(!CT_CHECK(t, channbrecv(f->pump.trace, &req) > 0))
|
|
||||||
return req;
|
|
||||||
CT_EQ_INT(t, op, req.op);
|
|
||||||
CT_EQ_PTR(t, ctx, req.owner);
|
|
||||||
if(ctx != nil)
|
|
||||||
CT_EQ_INT(t, clientpreedit(ctx), req.clientpre);
|
|
||||||
CT_EQ_PTR(t, replyc, req.reply);
|
|
||||||
return req;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
notrace(struct ct *t, Ibusfix *f)
|
|
||||||
{
|
|
||||||
Keyreq req;
|
|
||||||
|
|
||||||
CT_CHECK(t, channbrecv(f->pump.trace, &req) <= 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Keyres
|
|
||||||
contextkey(struct ct *t, Ibusfix *f, Ictx *ctx, u32int sym, u32int state)
|
|
||||||
{
|
|
||||||
Keyres res;
|
|
||||||
char text[Maxutf];
|
|
||||||
|
|
||||||
memset(&res, 0, sizeof res);
|
|
||||||
CT_CHECK(t, processkey(ctx, sym, state, text, sizeof text, &res));
|
|
||||||
nexttrace(t, f, Keypress, ctx);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ibus_capability_policy(struct ct *t)
|
|
||||||
{
|
|
||||||
Ibusfix f;
|
|
||||||
Ictx *a, *b;
|
|
||||||
Keyres res;
|
|
||||||
|
|
||||||
ibusbegin(&f);
|
|
||||||
a = newcontext(f.c1, "/context/cap-a");
|
|
||||||
b = newcontext(f.c2, "/context/cap-b");
|
|
||||||
if(!CT_CHECK(t, a != nil && b != nil))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_INT(t, 0, a->cap);
|
|
||||||
a->focused = 1;
|
|
||||||
res = contextkey(t, &f, a, 'n', Testctrlmask);
|
|
||||||
CT_CHECK(t, res.eaten);
|
|
||||||
res = contextkey(t, &f, a, 'k', 0);
|
|
||||||
checkstr(t, "preedit", "k", &res.preedit);
|
|
||||||
CT_EQ_PTR(t, a, testengineowner());
|
|
||||||
|
|
||||||
a->cap = Ibuscappreedit;
|
|
||||||
memset(&res, 0, sizeof res);
|
|
||||||
sendrequest(a, Keycap, 0, 0, &res);
|
|
||||||
nexttrace(t, &f, Keycap, a);
|
|
||||||
CT_CHECK(t, res.eaten);
|
|
||||||
checkstr(t, "preedit", "k", &res.preedit);
|
|
||||||
|
|
||||||
b->focused = 1;
|
|
||||||
b->cap = Ibuscappreedit;
|
|
||||||
memset(&res, 0, sizeof res);
|
|
||||||
sendrequest(b, Keycap, 0, 0, &res);
|
|
||||||
nexttrace(t, &f, Keycap, b);
|
|
||||||
CT_CHECK(t, !res.eaten);
|
|
||||||
CT_EQ_PTR(t, a, testengineowner());
|
|
||||||
checkenginepreedit(t, "k");
|
|
||||||
|
|
||||||
a->cap = 0;
|
|
||||||
memset(&res, 0, sizeof res);
|
|
||||||
sendrequest(a, Keycap, 0, 0, &res);
|
|
||||||
nexttrace(t, &f, Keycap, a);
|
|
||||||
CT_CHECK(t, res.eaten);
|
|
||||||
checkstr(t, "preedit", "k", &res.preedit);
|
|
||||||
cleanup:
|
|
||||||
ibusend(&f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ibus_private_input_policy(struct ct *t)
|
|
||||||
{
|
|
||||||
static const struct { u32int purpose; int want; } cases[] = {
|
|
||||||
{ 0, 0 },
|
|
||||||
{ Purposepassword, 1 },
|
|
||||||
{ Purposepin, 1 },
|
|
||||||
{ 37, 0 },
|
|
||||||
};
|
|
||||||
Ibusfix f;
|
|
||||||
Ictx *ctx;
|
|
||||||
Keyres res;
|
|
||||||
char text[Maxutf];
|
|
||||||
int i;
|
|
||||||
|
|
||||||
ibusbegin(&f);
|
|
||||||
ctx = newcontext(f.c1, "/context/private");
|
|
||||||
if(!CT_CHECK(t, ctx != nil))
|
|
||||||
goto cleanup;
|
|
||||||
for(i = 0; i < nelem(cases); i++){
|
|
||||||
ctx->purpose = cases[i].purpose;
|
|
||||||
CT_EQ_INT(t, cases[i].want, hidden(ctx));
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->focused = 1;
|
|
||||||
ctx->cap = 1;
|
|
||||||
ctx->purpose = 0;
|
|
||||||
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
|
||||||
res = contextkey(t, &f, ctx, 'k', 0);
|
|
||||||
checkstr(t, "preedit", "k", &res.preedit);
|
|
||||||
ctx->purpose = Purposepassword;
|
|
||||||
memset(&res, 0, sizeof res);
|
|
||||||
CT_CHECK(t, !processkey(ctx, 'x', 0, text, sizeof text, &res));
|
|
||||||
CT_CHECK(t, !res.eaten);
|
|
||||||
notrace(t, &f);
|
|
||||||
checkenginepreedit(t, "k");
|
|
||||||
|
|
||||||
ctx->purpose = 0;
|
|
||||||
res = contextkey(t, &f, ctx, 'a', 0);
|
|
||||||
checkstr(t, "preedit", "か", &res.preedit);
|
|
||||||
cleanup:
|
|
||||||
ibusend(&f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* A second release is silent, and dropping frees the slot. */
|
|
||||||
static void
|
|
||||||
checkdrop(struct ct *t, Ibusfix *f, Ictx *ctx, char *path)
|
|
||||||
{
|
|
||||||
releasecontext(ctx);
|
|
||||||
notrace(t, f);
|
|
||||||
dropcontext(ctx);
|
|
||||||
notrace(t, f);
|
|
||||||
CT_EQ_PTR(t, nil, findcontext(f->c1, path));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ibus_context_lifecycle(struct ct *t)
|
|
||||||
{
|
|
||||||
Ibusfix f;
|
|
||||||
Ictx *a, *b, *reused;
|
|
||||||
Keyreq req;
|
|
||||||
Keyres res;
|
|
||||||
Caret at;
|
|
||||||
char text[Maxutf];
|
|
||||||
|
|
||||||
ibusbegin(&f);
|
|
||||||
a = newcontext(f.c1, "/context/one");
|
|
||||||
b = newcontext(f.c2, "/context/one");
|
|
||||||
if(!CT_CHECK(t, a != nil && b != nil && a != b))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_PTR(t, a, findcontext(f.c1, "/context/one"));
|
|
||||||
CT_EQ_PTR(t, b, findcontext(f.c2, "/context/one"));
|
|
||||||
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/missing"));
|
|
||||||
|
|
||||||
setcursor(a, -10, -20, 14);
|
|
||||||
notrace(t, &f);
|
|
||||||
CT_CHECK(t, a->caret.valid);
|
|
||||||
memset(&res, 0, sizeof res);
|
|
||||||
CT_CHECK(t, !processkey(a, 'x', Relmask, text, sizeof text, &res));
|
|
||||||
CT_CHECK(t, !res.eaten);
|
|
||||||
notrace(t, &f);
|
|
||||||
CT_CHECK(t, !a->focused);
|
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
|
||||||
|
|
||||||
/* A key stands for the FocusIn a client may never send. */
|
|
||||||
memset(&res, 0, sizeof res);
|
|
||||||
CT_CHECK(t, processkey(a, 'x', 0, text, sizeof text, &res));
|
|
||||||
req = nexttrace(t, &f, Keypress, a);
|
|
||||||
CT_CHECK(t, a->focused);
|
|
||||||
CT_CHECK(t, !res.eaten);
|
|
||||||
CT_CHECK(t, req.caret.valid);
|
|
||||||
CT_EQ_INT(t, -10, req.caret.x);
|
|
||||||
CT_EQ_INT(t, -20, req.caret.y);
|
|
||||||
CT_EQ_INT(t, 14, req.caret.h);
|
|
||||||
CT_EQ_PTR(t, a, testengineowner());
|
|
||||||
testenginecaret(&at);
|
|
||||||
CT_CHECK(t, at.valid);
|
|
||||||
CT_EQ_INT(t, -10, at.x);
|
|
||||||
CT_EQ_INT(t, -20, at.y);
|
|
||||||
res = contextkey(t, &f, a, 'n', Testctrlmask);
|
|
||||||
CT_CHECK(t, res.eaten);
|
|
||||||
contextkey(t, &f, a, 'k', 0);
|
|
||||||
res = contextkey(t, &f, a, 'a', 0);
|
|
||||||
checkstr(t, "preedit", "か", &res.preedit);
|
|
||||||
|
|
||||||
b->focused = 1;
|
|
||||||
res = contextkey(t, &f, b, 'n', 0);
|
|
||||||
checkstr(t, "preedit", "ん", &res.preedit);
|
|
||||||
CT_EQ_PTR(t, b, testengineowner());
|
|
||||||
|
|
||||||
setcursor(b, 50, 60, 12);
|
|
||||||
req = nexttrace(t, &f, Keycaret, b);
|
|
||||||
CT_CHECK(t, req.caret.valid);
|
|
||||||
CT_EQ_INT(t, 50, req.caret.x);
|
|
||||||
testenginecaret(&at);
|
|
||||||
CT_CHECK(t, at.valid);
|
|
||||||
CT_EQ_INT(t, 50, at.x);
|
|
||||||
|
|
||||||
memset(&res, 0, sizeof res);
|
|
||||||
sendrequest(a, Keyreset, 0, 0, &res);
|
|
||||||
nexttrace(t, &f, Keyreset, a);
|
|
||||||
checkstr(t, "preedit", "", &res.preedit);
|
|
||||||
checkenginepreedit(t, "ん");
|
|
||||||
CT_EQ_PTR(t, b, testengineowner());
|
|
||||||
|
|
||||||
setcursor(a, 90, 100, 20);
|
|
||||||
nexttrace(t, &f, Keycaret, a);
|
|
||||||
testenginecaret(&at);
|
|
||||||
CT_EQ_INT(t, 50, at.x);
|
|
||||||
CT_EQ_INT(t, 60, at.y);
|
|
||||||
|
|
||||||
releasecontext(a);
|
|
||||||
nexttrace(t, &f, Keyrelease, a);
|
|
||||||
CT_CHECK(t, !a->focused);
|
|
||||||
CT_CHECK(t, !a->caret.valid);
|
|
||||||
checkenginepreedit(t, "ん");
|
|
||||||
CT_EQ_PTR(t, b, testengineowner());
|
|
||||||
checkdrop(t, &f, a, "/context/one");
|
|
||||||
|
|
||||||
reused = newcontext(f.c1, "/context/stale-destroy");
|
|
||||||
CT_EQ_PTR(t, a, reused);
|
|
||||||
reused->focused = 1;
|
|
||||||
dropcontext(reused);
|
|
||||||
nexttrace(t, &f, Keyrelease, reused);
|
|
||||||
notrace(t, &f);
|
|
||||||
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/stale-destroy"));
|
|
||||||
checkenginepreedit(t, "ん");
|
|
||||||
CT_EQ_PTR(t, b, testengineowner());
|
|
||||||
|
|
||||||
reused = newcontext(f.c1, "/context/reused");
|
|
||||||
CT_EQ_PTR(t, a, reused);
|
|
||||||
reused->focused = 1;
|
|
||||||
dropconncontexts(f.c1);
|
|
||||||
nexttrace(t, &f, Keyrelease, reused);
|
|
||||||
notrace(t, &f);
|
|
||||||
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/reused"));
|
|
||||||
checkenginepreedit(t, "ん");
|
|
||||||
CT_EQ_PTR(t, b, testengineowner());
|
|
||||||
dropconncontexts(f.c1);
|
|
||||||
notrace(t, &f);
|
|
||||||
|
|
||||||
memset(&res, 0, sizeof res);
|
|
||||||
CT_CHECK(t, !processkey(b, 'x', Relmask, text, sizeof text, &res));
|
|
||||||
CT_CHECK(t, !res.eaten);
|
|
||||||
notrace(t, &f);
|
|
||||||
CT_EQ_PTR(t, b, testengineowner());
|
|
||||||
cleanup:
|
|
||||||
ibusend(&f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ibus_active_release_lifecycle(struct ct *t)
|
|
||||||
{
|
|
||||||
Ibusfix f;
|
|
||||||
Ictx *ctx, *reused;
|
|
||||||
Keyres res;
|
|
||||||
|
|
||||||
ibusbegin(&f);
|
|
||||||
ctx = newcontext(f.c1, "/context/reset");
|
|
||||||
if(!CT_CHECK(t, ctx != nil))
|
|
||||||
goto cleanup;
|
|
||||||
ctx->focused = 1;
|
|
||||||
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
|
||||||
res = contextkey(t, &f, ctx, 'n', 0);
|
|
||||||
checkstr(t, "preedit", "ん", &res.preedit);
|
|
||||||
|
|
||||||
memset(&res, 0, sizeof res);
|
|
||||||
sendrequest(ctx, Keyreset, 0, 0, &res);
|
|
||||||
nexttrace(t, &f, Keyreset, ctx);
|
|
||||||
checkstr(t, "preedit", "", &res.preedit);
|
|
||||||
checkenginepreedit(t, "");
|
|
||||||
CT_CHECK(t, ctx->focused);
|
|
||||||
CT_EQ_PTR(t, ctx, testengineowner());
|
|
||||||
res = contextkey(t, &f, ctx, 'k', 0);
|
|
||||||
checkstr(t, "preedit", "k", &res.preedit);
|
|
||||||
CT_EQ_PTR(t, ctx, testengineowner());
|
|
||||||
|
|
||||||
releasecontext(ctx);
|
|
||||||
nexttrace(t, &f, Keyrelease, ctx);
|
|
||||||
CT_CHECK(t, !ctx->focused);
|
|
||||||
checkenginepreedit(t, "");
|
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
|
||||||
checkdrop(t, &f, ctx, "/context/reset");
|
|
||||||
|
|
||||||
reused = newcontext(f.c1, "/context/destroy");
|
|
||||||
CT_EQ_PTR(t, ctx, reused);
|
|
||||||
reused->focused = 1;
|
|
||||||
contextkey(t, &f, reused, 'n', 0);
|
|
||||||
CT_EQ_PTR(t, reused, testengineowner());
|
|
||||||
dropcontext(reused);
|
|
||||||
nexttrace(t, &f, Keyrelease, reused);
|
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
|
||||||
notrace(t, &f);
|
|
||||||
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/destroy"));
|
|
||||||
|
|
||||||
reused = newcontext(f.c1, "/context/prune");
|
|
||||||
CT_EQ_PTR(t, ctx, reused);
|
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
|
||||||
contextkey(t, &f, reused, 'n', 0);
|
|
||||||
CT_CHECK(t, reused->focused);
|
|
||||||
CT_EQ_PTR(t, reused, testengineowner());
|
|
||||||
dropconncontexts(f.c1);
|
|
||||||
nexttrace(t, &f, Keyrelease, reused);
|
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
|
||||||
notrace(t, &f);
|
|
||||||
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/prune"));
|
|
||||||
dropconncontexts(f.c1);
|
|
||||||
notrace(t, &f);
|
|
||||||
cleanup:
|
|
||||||
ibusend(&f);
|
|
||||||
}
|
|
||||||
@@ -1,390 +0,0 @@
|
|||||||
#define _GNU_SOURCE
|
|
||||||
#include <errno.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "ipc.h"
|
|
||||||
#include "live.h"
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Maxclients = 64,
|
|
||||||
Cappreedit = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct Response Response;
|
|
||||||
|
|
||||||
struct Response
|
|
||||||
{
|
|
||||||
int eaten;
|
|
||||||
int del;
|
|
||||||
char commit[Ipcfieldmax+1];
|
|
||||||
char preedit[Ipcfieldmax+1];
|
|
||||||
};
|
|
||||||
|
|
||||||
static size_t
|
|
||||||
getlen(unsigned char p[Ipclensz])
|
|
||||||
{
|
|
||||||
return p[0] | (p[1] << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
readresponseuntil(int fd, int want, Response *res, int64_t deadline,
|
|
||||||
int quiet)
|
|
||||||
{
|
|
||||||
unsigned char hdr[Ipcresphdrsz], len[Ipclensz];
|
|
||||||
size_t n;
|
|
||||||
int rv;
|
|
||||||
|
|
||||||
memset(res, 0, sizeof *res);
|
|
||||||
rv = readuntil(fd, hdr, sizeof hdr, deadline);
|
|
||||||
if(rv <= 0){
|
|
||||||
if(!quiet)
|
|
||||||
fail("read response header: %s",
|
|
||||||
rv == 0 ? "peer closed" : strerror(errno));
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
res->eaten = hdr[0] != 0;
|
|
||||||
res->del = hdr[1];
|
|
||||||
n = getlen(hdr + 2);
|
|
||||||
if(n > Ipcfieldmax){
|
|
||||||
fail("invalid commit length %zu", n);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if(n != 0){
|
|
||||||
rv = readuntil(fd, res->commit, n, deadline);
|
|
||||||
if(rv <= 0){
|
|
||||||
if(!quiet)
|
|
||||||
fail("read commit: %s",
|
|
||||||
rv == 0 ? "peer closed" : strerror(errno));
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res->commit[n] = '\0';
|
|
||||||
if(!want)
|
|
||||||
return 1;
|
|
||||||
rv = readuntil(fd, len, sizeof len, deadline);
|
|
||||||
if(rv <= 0){
|
|
||||||
if(!quiet)
|
|
||||||
fail("read preedit length: %s",
|
|
||||||
rv == 0 ? "peer closed" : strerror(errno));
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
n = getlen(len);
|
|
||||||
if(n > Ipcfieldmax){
|
|
||||||
fail("invalid preedit length %zu", n);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if(n != 0){
|
|
||||||
rv = readuntil(fd, res->preedit, n, deadline);
|
|
||||||
if(rv <= 0){
|
|
||||||
if(!quiet)
|
|
||||||
fail("read preedit: %s",
|
|
||||||
rv == 0 ? "peer closed" : strerror(errno));
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res->preedit[n] = '\0';
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
sendkey(int fd, int want, uint32_t mod, uint32_t key)
|
|
||||||
{
|
|
||||||
unsigned char req[Ipcreqsz];
|
|
||||||
|
|
||||||
ipcpackreq(req, want, mod, key);
|
|
||||||
return ipcsend(fd, req, sizeof req) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
sendreset(int fd, int want)
|
|
||||||
{
|
|
||||||
unsigned char req[Ipcreqsz];
|
|
||||||
|
|
||||||
ipcpackreset(req, want);
|
|
||||||
return ipcsend(fd, req, sizeof req) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
sendcap(int fd, int cap)
|
|
||||||
{
|
|
||||||
unsigned char req[Ipcreqsz];
|
|
||||||
|
|
||||||
ipcpackcap(req, cap);
|
|
||||||
return ipcsend(fd, req, sizeof req) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
sendsurround(int fd, char *text)
|
|
||||||
{
|
|
||||||
unsigned char req[Ipcreqsz];
|
|
||||||
size_t n;
|
|
||||||
|
|
||||||
n = strlen(text);
|
|
||||||
ipcpacksurround(req, n);
|
|
||||||
return ipcsend(fd, req, sizeof req) == 0 &&
|
|
||||||
(n == 0 || ipcsend(fd, text, n) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
|
||||||
char *where)
|
|
||||||
{
|
|
||||||
Response res;
|
|
||||||
|
|
||||||
if(readresponseuntil(fd, want, &res, nowms() + Calltimeout, 0) != 1)
|
|
||||||
return 0;
|
|
||||||
if(res.eaten != eaten || strcmp(res.commit, commit) != 0 ||
|
|
||||||
(want && strcmp(res.preedit, preedit) != 0))
|
|
||||||
return fail("%s: eaten=%d commit=%s preedit=%s",
|
|
||||||
where, res.eaten, res.commit, res.preedit);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
requestreset(int fd, int want, int eaten, char *commit, char *preedit,
|
|
||||||
char *where)
|
|
||||||
{
|
|
||||||
if(!sendreset(fd, want))
|
|
||||||
return fail("send %s: %s", where, strerror(errno));
|
|
||||||
return expectresponse(fd, want, eaten, commit, preedit, where);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
requestkey(int fd, int want, uint32_t mod, uint32_t key, int eaten,
|
|
||||||
char *commit, char *preedit, char *where)
|
|
||||||
{
|
|
||||||
if(!sendkey(fd, want, mod, key))
|
|
||||||
return fail("send %s: %s", where, strerror(errno));
|
|
||||||
return expectresponse(fd, want, eaten, commit, preedit, where);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The Korean habit: 한자 typed, then the Hanja key. The 한 is the
|
|
||||||
* client's by then, so the daemon must ask for it back and answer with
|
|
||||||
* the whole word.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
requesthanja(int fd)
|
|
||||||
{
|
|
||||||
Response res;
|
|
||||||
|
|
||||||
if(!sendsurround(fd, "저는 한"))
|
|
||||||
return fail("send surrounding text: %s", strerror(errno));
|
|
||||||
if(!requestkey(fd, 1, Mctrl, 's', 1, "", "", "select Korean") ||
|
|
||||||
!requestkey(fd, 1, 0, 'w', 1, "", "ㅈ", "jamo") ||
|
|
||||||
!requestkey(fd, 1, 0, 'k', 1, "", "자", "syllable") ||
|
|
||||||
!requestkey(fd, 1, Mctrl, 'h', 1, "", "자", "Hanja search"))
|
|
||||||
return 0;
|
|
||||||
if(!sendkey(fd, 1, 0, Kret))
|
|
||||||
return fail("send Hanja pick: %s", strerror(errno));
|
|
||||||
if(readresponseuntil(fd, 1, &res, nowms() + Calltimeout, 0) != 1)
|
|
||||||
return 0;
|
|
||||||
if(strcmp(res.commit, "漢字") != 0 || res.del != 1)
|
|
||||||
return fail("Hanja pick committed %s and took back %d",
|
|
||||||
res.commit, res.del);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
tryclient(char *path, int64_t deadline, int *client)
|
|
||||||
{
|
|
||||||
Response res;
|
|
||||||
int fd, rv;
|
|
||||||
|
|
||||||
fd = connectsocket(path, deadline);
|
|
||||||
if(fd < 0)
|
|
||||||
return errno == ETIMEDOUT ? -1 : 0;
|
|
||||||
if(!sendkey(fd, 1, 0, 0)){
|
|
||||||
close(fd);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
rv = readresponseuntil(fd, 1, &res, deadline, 1);
|
|
||||||
if(rv != 1){
|
|
||||||
close(fd);
|
|
||||||
return rv < 0 && errno == ETIMEDOUT ? -1 : 0;
|
|
||||||
}
|
|
||||||
if(res.eaten || res.commit[0] != '\0' || res.preedit[0] != '\0'){
|
|
||||||
close(fd);
|
|
||||||
fail("replacement client returned invalid modifier response");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
*client = fd;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
waitslot(char *path, int *client, char *where)
|
|
||||||
{
|
|
||||||
int rv;
|
|
||||||
int64_t deadline;
|
|
||||||
|
|
||||||
deadline = nowms() + Calltimeout;
|
|
||||||
for(;;){
|
|
||||||
rv = tryclient(path, deadline, client);
|
|
||||||
if(rv > 0)
|
|
||||||
return 1;
|
|
||||||
if(rv < 0 || leftms(deadline) == 0)
|
|
||||||
return fail("%s: timed out waiting for a worker slot", where);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
overflowrejected(char *path)
|
|
||||||
{
|
|
||||||
struct pollfd pfd;
|
|
||||||
unsigned char byte;
|
|
||||||
ssize_t n;
|
|
||||||
int fd, timeout;
|
|
||||||
int64_t deadline;
|
|
||||||
|
|
||||||
deadline = nowms() + Calltimeout;
|
|
||||||
fd = connectsocket(path, deadline);
|
|
||||||
if(fd < 0)
|
|
||||||
return fail("overflow connect: %s", strerror(errno));
|
|
||||||
if(!sendkey(fd, 1, 0, 0)){
|
|
||||||
close(fd);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
for(;;){
|
|
||||||
timeout = leftms(deadline);
|
|
||||||
if(timeout == 0){
|
|
||||||
close(fd);
|
|
||||||
return fail("overflow client was not rejected");
|
|
||||||
}
|
|
||||||
pfd.fd = fd;
|
|
||||||
pfd.events = POLLIN;
|
|
||||||
pfd.revents = 0;
|
|
||||||
n = poll(&pfd, 1, timeout);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n <= 0){
|
|
||||||
close(fd);
|
|
||||||
return fail("poll overflow client: %s",
|
|
||||||
n == 0 ? "timed out" : strerror(errno));
|
|
||||||
}
|
|
||||||
n = recv(fd, &byte, 1, 0);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n == 0 || (n < 0 && (errno == ECONNRESET || errno == EPIPE))){
|
|
||||||
close(fd);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(n < 0){
|
|
||||||
close(fd);
|
|
||||||
return fail("read overflow rejection: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
close(fd);
|
|
||||||
return fail("overflow client received a response");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
runsmoke(char *path)
|
|
||||||
{
|
|
||||||
int client, ok;
|
|
||||||
|
|
||||||
client = connectsocket(path, nowms() + Calltimeout);
|
|
||||||
if(client < 0)
|
|
||||||
return fail("connect client: %s", strerror(errno));
|
|
||||||
ok = sendcap(client, Cappreedit) &&
|
|
||||||
expectresponse(client, 1, 1, "", "", "negotiate preedit") &&
|
|
||||||
requestkey(client, 1, Mctrl, 'n', 1, "", "",
|
|
||||||
"select Japanese") &&
|
|
||||||
requestkey(client, 1, 0, 'k', 1, "", "k", "preedit") &&
|
|
||||||
requestreset(client, 1, 1, "k", "", "reset") &&
|
|
||||||
requesthanja(client);
|
|
||||||
close(client);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
runcapacity(char *path)
|
|
||||||
{
|
|
||||||
int client[Maxclients];
|
|
||||||
int i, ok;
|
|
||||||
|
|
||||||
for(i = 0; i < Maxclients; i++)
|
|
||||||
client[i] = -1;
|
|
||||||
ok = 0;
|
|
||||||
client[0] = connectsocket(path, nowms() + Calltimeout);
|
|
||||||
if(client[0] < 0){
|
|
||||||
fail("connect first client: %s", strerror(errno));
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if(!requestkey(client[0], 1, Mctrl, 'n', 1, "", "",
|
|
||||||
"select Japanese"))
|
|
||||||
goto out;
|
|
||||||
for(i = 1; i < Maxclients; i++){
|
|
||||||
client[i] = connectsocket(path, nowms() + Calltimeout);
|
|
||||||
if(client[i] < 0){
|
|
||||||
fail("connect capacity client %d: %s", i, strerror(errno));
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if(!requestkey(client[i], 1, 0, 0, 0, "", "",
|
|
||||||
"capacity modifier"))
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if(!overflowrejected(path))
|
|
||||||
goto out;
|
|
||||||
close(client[Maxclients-1]);
|
|
||||||
client[Maxclients-1] = -1;
|
|
||||||
if(!waitslot(path, &client[Maxclients-1], "inactive slot recovery"))
|
|
||||||
goto out;
|
|
||||||
close(client[0]);
|
|
||||||
client[0] = -1;
|
|
||||||
if(!waitslot(path, &client[0], "active slot recovery") ||
|
|
||||||
!requestkey(client[0], 1, 0, 'a', 1, "", "あ",
|
|
||||||
"post-disconnect composition"))
|
|
||||||
goto out;
|
|
||||||
ok = 1;
|
|
||||||
out:
|
|
||||||
for(i = 0; i < Maxclients; i++)
|
|
||||||
if(client[i] >= 0)
|
|
||||||
close(client[i]);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
Daemon daemon;
|
|
||||||
Live live;
|
|
||||||
int capacity, ok;
|
|
||||||
|
|
||||||
testname = "ipc_live_test";
|
|
||||||
capacity = argc == 4 && strcmp(argv[1], "--capacity") == 0;
|
|
||||||
if((!capacity && argc != 3) || (capacity && argc != 4)){
|
|
||||||
fprintf(stderr,
|
|
||||||
"usage: ipc_live_test [--capacity] strans mapdir\n");
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
daemoninit(&daemon, "daemon");
|
|
||||||
ok = livesetup(&live, "ipc") &&
|
|
||||||
startdaemon(&live, &daemon, argv[1+capacity], argv[2+capacity]) &&
|
|
||||||
waitready(&live, &daemon, NULL, 0);
|
|
||||||
if(ok)
|
|
||||||
ok = capacity ? runcapacity(live.socket) : runsmoke(live.socket);
|
|
||||||
if(!ok)
|
|
||||||
showerrors(&daemon);
|
|
||||||
if(!stopdaemon(&daemon))
|
|
||||||
ok = 0;
|
|
||||||
/* SIGTERM must leave no socket behind. */
|
|
||||||
if(access(live.socket, F_OK) == 0)
|
|
||||||
ok = fail("socket %s survived the daemon", live.socket);
|
|
||||||
if(!closeerrors(&daemon))
|
|
||||||
ok = 0;
|
|
||||||
if(!liveclean(&live))
|
|
||||||
ok = 0;
|
|
||||||
if(!ok)
|
|
||||||
return 1;
|
|
||||||
printf("ipc %s: ok\n", capacity ? "connection capacity" : "live smoke");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
447
tests/ipc_test.c
447
tests/ipc_test.c
@@ -1,447 +0,0 @@
|
|||||||
#define _POSIX_C_SOURCE 200809L
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/un.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
#undef accept
|
|
||||||
#undef listen
|
|
||||||
#undef send
|
|
||||||
|
|
||||||
static int64_t
|
|
||||||
nowms(void)
|
|
||||||
{
|
|
||||||
struct timespec ts;
|
|
||||||
|
|
||||||
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
|
||||||
return 0;
|
|
||||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ipcclientdeadlines(struct ct*);
|
|
||||||
static void ipcconnectcloexec(struct ct*);
|
|
||||||
|
|
||||||
void
|
|
||||||
ipc_masks_modifiers(struct ct *t)
|
|
||||||
{
|
|
||||||
uchar buf[Ipcreqsz];
|
|
||||||
u32int key, mod;
|
|
||||||
int want;
|
|
||||||
|
|
||||||
ipcpackreq(buf, 1, Mctrl|Malt|(1<<1)|(1<<4)|(1<<7), 0x1f642);
|
|
||||||
ipcunpackreq(buf, &want, &mod, &key);
|
|
||||||
CT_EQ_INT(t, 1, want);
|
|
||||||
CT_EQ_UINT(t, Mctrl|Malt, mod);
|
|
||||||
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));
|
|
||||||
|
|
||||||
/* Frontends share one keysym and modifier mapping. */
|
|
||||||
CT_EQ_UINT(t, 'a', ipckeysym('a', 'a'));
|
|
||||||
CT_EQ_UINT(t, '1', ipckeysym(0xffb1, '1')); /* KP_1 */
|
|
||||||
CT_EQ_UINT(t, Kret, ipckeysym(0xff0d, '\r'));
|
|
||||||
CT_EQ_UINT(t, Kret, ipckeysym(0xff8d, '\r')); /* KP_Enter */
|
|
||||||
CT_EQ_UINT(t, Ktab, ipckeysym(0xfe20, 0)); /* ISO_Left_Tab */
|
|
||||||
CT_EQ_UINT(t, Kdown, ipckeysym(0xff99, 0)); /* KP_Down */
|
|
||||||
CT_EQ_UINT(t, Kback, ipckeysym(0xff08, 8));
|
|
||||||
CT_EQ_UINT(t, Kspec + 0xff, ipckeysym(0xffff, 0x7f)); /* Delete */
|
|
||||||
CT_EQ_UINT(t, 0x1f642, ipckeysym(0x101f642, 0x1f642));
|
|
||||||
CT_EQ_UINT(t, 0, ipckeysym(0xfe03, 0)); /* ISO_Level3_Shift */
|
|
||||||
CT_EQ_UINT(t, 0, ipckeysym(0xffe1, 0)); /* Shift_L */
|
|
||||||
CT_EQ_UINT(t, Mshift|Mctrl, ipcmod(Mshift|Mctrl|(1<<1)|(1<<4)));
|
|
||||||
CT_EQ_UINT(t, Msuper, ipcmod(1<<6));
|
|
||||||
CT_EQ_UINT(t, Msuper, ipcmod(1<<26));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ipc_control_and_caret_frames(struct ct *t)
|
|
||||||
{
|
|
||||||
static const uchar capon[Ipcreqsz] = {
|
|
||||||
Ipcext|Ipcreqwant, Ipcversion, Ipcopcap, 0, 0, 0,
|
|
||||||
};
|
|
||||||
static const uchar capoff[Ipcreqsz] = {
|
|
||||||
Ipcext, Ipcversion, Ipcopcap, 0, 0, 0,
|
|
||||||
};
|
|
||||||
static const uchar caret[Ipccaretsz] = {
|
|
||||||
Ipcext, Ipcversion, Ipcopcaret, 1,
|
|
||||||
0xfe, 0xff, 0xff, 0xff,
|
|
||||||
0x04, 0x03, 0x02, 0x01,
|
|
||||||
0x06, 0x05, 0x00, 0x00,
|
|
||||||
};
|
|
||||||
static const uchar nocaret[Ipccaretsz] = {
|
|
||||||
Ipcext, Ipcversion, Ipcopcaret, 0,
|
|
||||||
};
|
|
||||||
uchar buf[Ipccaretsz], bad[Ipccaretsz];
|
|
||||||
u32int key, mod;
|
|
||||||
int valid, want;
|
|
||||||
int32_t x, y, h;
|
|
||||||
|
|
||||||
ipcpackcap(buf, 1);
|
|
||||||
CT_EQ_MEM(t, capon, buf, sizeof capon);
|
|
||||||
CT_EQ_INT(t, Ipccap, ipcreqtype(buf));
|
|
||||||
/* The probe remains a harmless key-zero request to an old daemon. */
|
|
||||||
ipcunpackreq(buf, &want, &mod, &key);
|
|
||||||
CT_EQ_INT(t, 1, want);
|
|
||||||
CT_EQ_UINT(t, 0, key);
|
|
||||||
ipcpackcap(buf, 0);
|
|
||||||
CT_EQ_MEM(t, capoff, buf, sizeof capoff);
|
|
||||||
ipcunpackreq(buf, &want, &mod, &key);
|
|
||||||
CT_EQ_INT(t, 0, want);
|
|
||||||
CT_EQ_UINT(t, 0, key);
|
|
||||||
|
|
||||||
ipcpackcaret(buf, 1, -2, 0x01020304, 0x506);
|
|
||||||
CT_EQ_MEM(t, caret, buf, sizeof caret);
|
|
||||||
CT_EQ_INT(t, Ipccaret, ipcreqtype(buf));
|
|
||||||
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h))){
|
|
||||||
CT_EQ_INT(t, 1, valid);
|
|
||||||
CT_EQ_INT(t, -2, x);
|
|
||||||
CT_EQ_INT(t, 0x01020304, y);
|
|
||||||
CT_EQ_INT(t, 0x506, h);
|
|
||||||
}
|
|
||||||
ipcpackcaret(buf, 1, INT32_MIN, INT32_MAX, 0);
|
|
||||||
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h))){
|
|
||||||
CT_EQ_INT(t, INT32_MIN, x);
|
|
||||||
CT_EQ_INT(t, INT32_MAX, y);
|
|
||||||
CT_EQ_INT(t, 0, h);
|
|
||||||
}
|
|
||||||
ipcpackcaret(buf, 0, -2, 3, 4);
|
|
||||||
CT_EQ_MEM(t, nocaret, buf, sizeof nocaret);
|
|
||||||
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h)))
|
|
||||||
CT_EQ_INT(t, 0, valid);
|
|
||||||
|
|
||||||
memcpy(bad, caret, sizeof bad);
|
|
||||||
bad[1]++;
|
|
||||||
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
|
||||||
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
|
||||||
memcpy(bad, caret, sizeof bad);
|
|
||||||
bad[2]++;
|
|
||||||
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
|
||||||
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
|
||||||
memcpy(bad, caret, sizeof bad);
|
|
||||||
bad[3] = 2;
|
|
||||||
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
|
||||||
memcpy(bad, caret, sizeof bad);
|
|
||||||
bad[0] |= Ipcreqwant;
|
|
||||||
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
|
||||||
memcpy(bad, caret, sizeof bad);
|
|
||||||
bad[15] = 0x80;
|
|
||||||
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
|
||||||
memcpy(bad, capon, sizeof capon);
|
|
||||||
bad[3] = 1;
|
|
||||||
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
|
||||||
|
|
||||||
ipcpackreq(buf, 1, 0, 'a');
|
|
||||||
CT_EQ_INT(t, Ipckey, ipcreqtype(buf));
|
|
||||||
ipcpackreset(buf, 1);
|
|
||||||
CT_EQ_INT(t, Ipckey, ipcreqtype(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));
|
|
||||||
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, 3, 1, 0, 'A', 2, 0, 'x', 'y' };
|
|
||||||
static const uchar withoutpre[] = { 1, 0, 1, 0, 'A' };
|
|
||||||
uchar out[Ipcmaxresp+1], field[Ipcfieldmax];
|
|
||||||
int n;
|
|
||||||
|
|
||||||
n = ipcpackresp(out, sizeof out, 7, 3, "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, 0, "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, 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, 0,
|
|
||||||
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
|
||||||
CT_EQ_INT(t, Ipcmaxresp, n);
|
|
||||||
CT_EQ_INT(t, 0, out[2]);
|
|
||||||
CT_EQ_INT(t, 1, out[3]);
|
|
||||||
CT_EQ_INT(t, 0, out[4+Ipcfieldmax]);
|
|
||||||
CT_EQ_INT(t, 1, out[5+Ipcfieldmax]);
|
|
||||||
CT_EQ_INT(t, 0xa5, out[Ipcmaxresp]);
|
|
||||||
CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1, 0,
|
|
||||||
(char*)field, sizeof field, (char*)field, sizeof field, 1));
|
|
||||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, 0,
|
|
||||||
(char*)field, Ipcfieldmax+1, "", 0, 0));
|
|
||||||
/* A take-back is a rune count and never leaves its byte. */
|
|
||||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, -1, "", 0, "", 0, 0));
|
|
||||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, 256, "", 0, "", 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ipc_response_empty_and_preedit(struct ct *t)
|
|
||||||
{
|
|
||||||
uchar first[Ipcmaxresp], second[Ipcmaxresp];
|
|
||||||
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
|
||||||
Ipcresp resp;
|
|
||||||
int fd[2], nfirst, nsecond;
|
|
||||||
|
|
||||||
nfirst = ipcpackresp(first, sizeof first, 0, 0, "", 0, "", 0, 0);
|
|
||||||
nsecond = ipcpackresp(second, sizeof second, 1, 0,
|
|
||||||
"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, preedit, &resp))){
|
|
||||||
CT_EQ_INT(t, 0, resp.eaten);
|
|
||||||
CT_EQ_SIZE(t, 0, resp.commitlen);
|
|
||||||
CT_EQ_SIZE(t, 0, resp.preeditlen);
|
|
||||||
CT_EQ_STR(t, "", commit);
|
|
||||||
}
|
|
||||||
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit, &resp))){
|
|
||||||
CT_EQ_INT(t, 1, resp.eaten);
|
|
||||||
CT_EQ_SIZE(t, 2, resp.commitlen);
|
|
||||||
CT_EQ_SIZE(t, 4, resp.preeditlen);
|
|
||||||
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[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
|
||||||
Ipcresp resp;
|
|
||||||
int fd[2], nfirst, nsecond;
|
|
||||||
|
|
||||||
memset(field, 'x', sizeof field);
|
|
||||||
nfirst = ipcpackresp(first, sizeof first, 1, 0,
|
|
||||||
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
|
||||||
nsecond = ipcpackresp(second, sizeof second, 0, 0, "ok", 2, "", 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, preedit, &resp))){
|
|
||||||
CT_EQ_SIZE(t, Ipcfieldmax, resp.commitlen);
|
|
||||||
CT_EQ_SIZE(t, Ipcfieldmax, resp.preeditlen);
|
|
||||||
CT_EQ_MEM(t, field, commit, Ipcfieldmax);
|
|
||||||
CT_EQ_MEM(t, field, preedit, Ipcfieldmax);
|
|
||||||
CT_EQ_INT(t, 0, commit[Ipcfieldmax]);
|
|
||||||
CT_EQ_INT(t, 0, preedit[Ipcfieldmax]);
|
|
||||||
}
|
|
||||||
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, preedit, &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[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
|
||||||
Ipcresp resp;
|
|
||||||
int fd[2], i, n;
|
|
||||||
|
|
||||||
n = ipcpackresp(frame, sizeof frame, 1, 2, "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, preedit,
|
|
||||||
&resp))){
|
|
||||||
CT_EQ_INT(t, 2, resp.del);
|
|
||||||
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, preedit, &resp));
|
|
||||||
close(fd[0]);
|
|
||||||
close(fd[1]);
|
|
||||||
|
|
||||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
|
||||||
CT_ERRORF(t, "socketpair failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
frame[0] = 2;
|
|
||||||
CT_EQ_INT(t, 0, ipcsend(fd[0], frame, n));
|
|
||||||
errno = 0;
|
|
||||||
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, preedit, &resp));
|
|
||||||
CT_EQ_INT(t, EPROTO, errno);
|
|
||||||
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]);
|
|
||||||
ipcclientdeadlines(t);
|
|
||||||
ipcconnectcloexec(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
ipcclientdeadlines(struct ct *t)
|
|
||||||
{
|
|
||||||
char fill[4096], commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
|
||||||
Ipcresp resp;
|
|
||||||
int fd[2];
|
|
||||||
int64_t start, elapsed;
|
|
||||||
ssize_t n;
|
|
||||||
|
|
||||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
|
||||||
CT_ERRORF(t, "socketpair failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
start = nowms();
|
|
||||||
errno = 0;
|
|
||||||
CT_EQ_INT(t, -1, ipcreadresp(fd[0], 0, commit, preedit, &resp));
|
|
||||||
elapsed = nowms() - start;
|
|
||||||
CT_EQ_INT(t, ETIMEDOUT, errno);
|
|
||||||
CT_CHECK(t, elapsed >= 0 && elapsed < 4*Ipcwaitms);
|
|
||||||
|
|
||||||
memset(fill, 'x', sizeof fill);
|
|
||||||
for(;;){
|
|
||||||
n = send(fd[0], fill, sizeof fill, MSG_DONTWAIT|MSG_NOSIGNAL);
|
|
||||||
if(n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
|
|
||||||
break;
|
|
||||||
if(n <= 0){
|
|
||||||
CT_ERRORF(t, "fill send failed: %s", strerror(errno));
|
|
||||||
close(fd[0]);
|
|
||||||
close(fd[1]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
start = nowms();
|
|
||||||
errno = 0;
|
|
||||||
CT_EQ_INT(t, -1, ipcsend(fd[0], "x", 1));
|
|
||||||
elapsed = nowms() - start;
|
|
||||||
CT_EQ_INT(t, ETIMEDOUT, errno);
|
|
||||||
CT_CHECK(t, elapsed >= 0 && elapsed < 4*Ipcwaitms);
|
|
||||||
close(fd[0]);
|
|
||||||
close(fd[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
ipcconnectcloexec(struct ct *t)
|
|
||||||
{
|
|
||||||
struct sockaddr_un addr;
|
|
||||||
char root[] = "/tmp/strans-ipc.XXXXXX";
|
|
||||||
char *old, *saved;
|
|
||||||
int accepted, client, flags, full, listener;
|
|
||||||
|
|
||||||
accepted = client = full = listener = -1;
|
|
||||||
old = getenv("XDG_RUNTIME_DIR");
|
|
||||||
saved = old == nil ? nil : strdup(old);
|
|
||||||
if(mkdtemp(root) == nil){
|
|
||||||
CT_ERRORF(t, "mkdtemp failed: %s", strerror(errno));
|
|
||||||
free(saved);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setenv("XDG_RUNTIME_DIR", root, 1);
|
|
||||||
memset(&addr, 0, sizeof addr);
|
|
||||||
addr.sun_family = AF_UNIX;
|
|
||||||
if(ipcpath(addr.sun_path, sizeof addr.sun_path) < 0 ||
|
|
||||||
(listener = socket(AF_UNIX, SOCK_STREAM, 0)) < 0 ||
|
|
||||||
bind(listener, (struct sockaddr*)&addr, sizeof addr) < 0 ||
|
|
||||||
listen(listener, 0) < 0){
|
|
||||||
CT_ERRORF(t, "listen failed: %s", strerror(errno));
|
|
||||||
goto Out;
|
|
||||||
}
|
|
||||||
client = ipcconnect();
|
|
||||||
if(!CT_CHECK(t, client >= 0))
|
|
||||||
goto Out;
|
|
||||||
flags = fcntl(client, F_GETFD);
|
|
||||||
CT_CHECK(t, flags >= 0 && (flags & FD_CLOEXEC) != 0);
|
|
||||||
errno = 0;
|
|
||||||
full = ipcconnect();
|
|
||||||
CT_EQ_INT(t, -1, full);
|
|
||||||
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
|
|
||||||
accepted = accept(listener, nil, nil);
|
|
||||||
if(!CT_CHECK(t, accepted >= 0))
|
|
||||||
goto Out;
|
|
||||||
Out:
|
|
||||||
if(full >= 0)
|
|
||||||
close(full);
|
|
||||||
if(accepted >= 0)
|
|
||||||
close(accepted);
|
|
||||||
if(client >= 0)
|
|
||||||
close(client);
|
|
||||||
if(listener >= 0)
|
|
||||||
close(listener);
|
|
||||||
unlink(addr.sun_path);
|
|
||||||
rmdir(root);
|
|
||||||
if(saved != nil){
|
|
||||||
setenv("XDG_RUNTIME_DIR", saved, 1);
|
|
||||||
free(saved);
|
|
||||||
}else
|
|
||||||
unsetenv("XDG_RUNTIME_DIR");
|
|
||||||
}
|
|
||||||
110
tests/ko_test.c
110
tests/ko_test.c
@@ -1,110 +0,0 @@
|
|||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
static Str
|
|
||||||
kotrans(char *keys)
|
|
||||||
{
|
|
||||||
Str out, raw;
|
|
||||||
|
|
||||||
raw = mkstr(keys);
|
|
||||||
transstr(getlang(LangKO), nil, &raw, &out);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
korean_sequences(struct ct *t)
|
|
||||||
{
|
|
||||||
static const struct { char *keys, *want; } cases[] = {
|
|
||||||
{ "r", "ㄱ" },
|
|
||||||
{ "rk", "가" },
|
|
||||||
{ "rkr", "각" },
|
|
||||||
{ "rkrk", "가가" },
|
|
||||||
{ "rkrtk", "각사" },
|
|
||||||
{ "rhk", "과" },
|
|
||||||
{ "rr", "ㄱㄱ" },
|
|
||||||
{ "Rk", "까" },
|
|
||||||
{ "rk1", "가1" },
|
|
||||||
{ "rt", "ㄳ" },
|
|
||||||
{ "rtk", "ㄱ사" },
|
|
||||||
{ "rtt", "ㄳㅅ" },
|
|
||||||
{ "kr", "가" },
|
|
||||||
{ "hkr", "과" },
|
|
||||||
{ "kk", "ㅏㅏ" },
|
|
||||||
};
|
|
||||||
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_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
|
|
||||||
korean_backspace(struct ct *t)
|
|
||||||
{
|
|
||||||
static const struct { char *before, *after; } cases[] = {
|
|
||||||
{ "ㄱ", "" },
|
|
||||||
{ "ㄳ", "ㄱ" },
|
|
||||||
{ "가", "ㄱ" },
|
|
||||||
{ "과", "고" }, { "괘", "고" }, { "괴", "고" },
|
|
||||||
{ "궈", "구" }, { "궤", "구" }, { "귀", "구" },
|
|
||||||
{ "긔", "그" },
|
|
||||||
{ "각", "가" },
|
|
||||||
{ "갃", "각" }, { "갅", "간" }, { "갆", "간" },
|
|
||||||
{ "갉", "갈" }, { "갊", "갈" }, { "갋", "갈" },
|
|
||||||
{ "갌", "갈" }, { "갍", "갈" }, { "갎", "갈" },
|
|
||||||
{ "갏", "갈" }, { "값", "갑" },
|
|
||||||
};
|
|
||||||
Im state;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
memset(&state, 0, sizeof state);
|
|
||||||
for(i = 0; i < nelem(cases); i++){
|
|
||||||
state.pre = mkstr(cases[i].before);
|
|
||||||
backko(&state);
|
|
||||||
checkstr(t, cases[i].before, cases[i].after, &state.pre);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
774
tests/live.c
774
tests/live.c
@@ -1,774 +0,0 @@
|
|||||||
#define _GNU_SOURCE
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/un.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "ipc.h"
|
|
||||||
#include "live.h"
|
|
||||||
|
|
||||||
char *testname = "live_test";
|
|
||||||
|
|
||||||
int
|
|
||||||
fail(char *fmt, ...)
|
|
||||||
{
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
fprintf(stderr, "%s: ", testname);
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
fputc('\n', stderr);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t
|
|
||||||
nowms(void)
|
|
||||||
{
|
|
||||||
struct timespec ts;
|
|
||||||
|
|
||||||
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
|
||||||
return -1;
|
|
||||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* A clock that cannot be read counts as an expired deadline. */
|
|
||||||
int
|
|
||||||
leftms(int64_t deadline)
|
|
||||||
{
|
|
||||||
int64_t n;
|
|
||||||
|
|
||||||
n = nowms();
|
|
||||||
if(n < 0)
|
|
||||||
return 0;
|
|
||||||
n = deadline - n;
|
|
||||||
if(n <= 0)
|
|
||||||
return 0;
|
|
||||||
return n > INT_MAX ? INT_MAX : (int)n;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
pausems(int ms)
|
|
||||||
{
|
|
||||||
struct timespec ts;
|
|
||||||
|
|
||||||
ts.tv_sec = ms / 1000;
|
|
||||||
ts.tv_nsec = (ms % 1000) * 1000000L;
|
|
||||||
while(nanosleep(&ts, &ts) < 0 && errno == EINTR)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
makedir(char *path)
|
|
||||||
{
|
|
||||||
if(mkdir(path, 0700) == 0)
|
|
||||||
return 1;
|
|
||||||
return fail("mkdir %s: %s", path, strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
cleardir(char *path)
|
|
||||||
{
|
|
||||||
DIR *dir;
|
|
||||||
struct dirent *de;
|
|
||||||
char name[576];
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
if(path[0] == '\0')
|
|
||||||
return 1;
|
|
||||||
dir = opendir(path);
|
|
||||||
if(dir == NULL)
|
|
||||||
return errno == ENOENT ||
|
|
||||||
fail("open %s: %s", path, strerror(errno));
|
|
||||||
ok = 1;
|
|
||||||
errno = 0;
|
|
||||||
while((de = readdir(dir)) != NULL){
|
|
||||||
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
|
||||||
continue;
|
|
||||||
if(snprintf(name, sizeof name, "%s/%s", path, de->d_name)
|
|
||||||
>= (int)sizeof name)
|
|
||||||
ok = fail("cleanup path is too long: %s", de->d_name);
|
|
||||||
else if(unlink(name) < 0 && errno != ENOENT)
|
|
||||||
ok = fail("remove %s: %s", name, strerror(errno));
|
|
||||||
errno = 0;
|
|
||||||
}
|
|
||||||
if(errno != 0)
|
|
||||||
ok = fail("read %s: %s", path, strerror(errno));
|
|
||||||
if(closedir(dir) < 0)
|
|
||||||
ok = fail("close %s: %s", path, strerror(errno));
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
rmdirknown(char *path)
|
|
||||||
{
|
|
||||||
if(path[0] == '\0' || rmdir(path) == 0 || errno == ENOENT)
|
|
||||||
return 1;
|
|
||||||
return fail("rmdir %s: %s", path, strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
livesetup(Live *l, char *prefix)
|
|
||||||
{
|
|
||||||
memset(l, 0, sizeof *l);
|
|
||||||
if(nowms() < 0)
|
|
||||||
return fail("read monotonic clock: %s", strerror(errno));
|
|
||||||
if(snprintf(l->root, sizeof l->root, "/tmp/strans-%s.XXXXXX", prefix)
|
|
||||||
>= (int)sizeof l->root){
|
|
||||||
l->root[0] = '\0';
|
|
||||||
return fail("temporary root name is too long");
|
|
||||||
}
|
|
||||||
if(mkdtemp(l->root) == NULL){
|
|
||||||
l->root[0] = '\0';
|
|
||||||
return fail("mkdtemp: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
if(snprintf(l->runtime, sizeof l->runtime, "%s/runtime", l->root)
|
|
||||||
>= (int)sizeof l->runtime ||
|
|
||||||
snprintf(l->config, sizeof l->config, "%s/config", l->root)
|
|
||||||
>= (int)sizeof l->config ||
|
|
||||||
snprintf(l->ibus, sizeof l->ibus, "%s/ibus", l->config)
|
|
||||||
>= (int)sizeof l->ibus ||
|
|
||||||
snprintf(l->bus, sizeof l->bus, "%s/bus", l->ibus)
|
|
||||||
>= (int)sizeof l->bus ||
|
|
||||||
snprintf(l->home, sizeof l->home, "%s/home", l->root)
|
|
||||||
>= (int)sizeof l->home ||
|
|
||||||
snprintf(l->socket, sizeof l->socket, "%s/strans.sock", l->runtime)
|
|
||||||
>= (int)sizeof l->socket)
|
|
||||||
return fail("temporary path is too long");
|
|
||||||
return makedir(l->runtime) && makedir(l->config) && makedir(l->ibus) &&
|
|
||||||
makedir(l->bus) && makedir(l->home);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The daemon owns its endpoints, so leftovers elsewhere are a failure. */
|
|
||||||
int
|
|
||||||
liveclean(Live *l)
|
|
||||||
{
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
ok = 1;
|
|
||||||
if(l->socket[0] != '\0' && unlink(l->socket) < 0 && errno != ENOENT)
|
|
||||||
ok = fail("remove IPC socket %s: %s", l->socket, strerror(errno));
|
|
||||||
if(!cleardir(l->bus)) ok = 0;
|
|
||||||
if(!rmdirknown(l->bus)) ok = 0;
|
|
||||||
if(!rmdirknown(l->ibus)) ok = 0;
|
|
||||||
if(!rmdirknown(l->config)) ok = 0;
|
|
||||||
if(!rmdirknown(l->runtime)) ok = 0;
|
|
||||||
if(!rmdirknown(l->home)) ok = 0;
|
|
||||||
if(!rmdirknown(l->root)) ok = 0;
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
daemoninit(Daemon *d, char *name)
|
|
||||||
{
|
|
||||||
memset(d, 0, sizeof *d);
|
|
||||||
d->pid = -1;
|
|
||||||
d->errfd = -1;
|
|
||||||
d->name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
readerrors(Daemon *d)
|
|
||||||
{
|
|
||||||
ssize_t n;
|
|
||||||
|
|
||||||
if(d->errfd < 0)
|
|
||||||
return;
|
|
||||||
while(d->nerr + 1 < sizeof d->err){
|
|
||||||
n = read(d->errfd, d->err + d->nerr, sizeof d->err - d->nerr - 1);
|
|
||||||
if(n > 0){
|
|
||||||
d->nerr += n;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
d->err[d->nerr] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
showerrors(Daemon *d)
|
|
||||||
{
|
|
||||||
readerrors(d);
|
|
||||||
if(d->nerr != 0)
|
|
||||||
fprintf(stderr, "%s: %s stderr:\n%s", testname, d->name, d->err);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
closeerrors(Daemon *d)
|
|
||||||
{
|
|
||||||
int fd;
|
|
||||||
|
|
||||||
readerrors(d);
|
|
||||||
fd = d->errfd;
|
|
||||||
d->errfd = -1;
|
|
||||||
if(fd >= 0 && close(fd) < 0)
|
|
||||||
return fail("close %s stderr: %s", d->name, strerror(errno));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Point both standard streams at the capture pipe, even if it is 1 or 2. */
|
|
||||||
static int
|
|
||||||
childfds(int fd)
|
|
||||||
{
|
|
||||||
int flags;
|
|
||||||
|
|
||||||
if(fd > STDERR_FILENO){
|
|
||||||
if(dup2(fd, STDOUT_FILENO) < 0 || dup2(fd, STDERR_FILENO) < 0)
|
|
||||||
return 0;
|
|
||||||
close(fd);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
flags = fcntl(fd, F_GETFD);
|
|
||||||
if(flags < 0 || fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC) < 0)
|
|
||||||
return 0;
|
|
||||||
return dup2(fd, STDOUT_FILENO) >= 0 && dup2(fd, STDERR_FILENO) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A private X server for the tests that need one; its display goes in
|
|
||||||
* Live, so the daemon and the clients started after it share it.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
startxvfb(Live *l, Daemon *d, char *program)
|
|
||||||
{
|
|
||||||
struct pollfd pfd;
|
|
||||||
char fdarg[16], line[32];
|
|
||||||
int errpipe[2], n, pipefd[2];
|
|
||||||
int64_t deadline;
|
|
||||||
pid_t pid;
|
|
||||||
ssize_t nr;
|
|
||||||
|
|
||||||
if(pipe2(pipefd, O_CLOEXEC) < 0)
|
|
||||||
return fail("Xvfb display pipe: %s", strerror(errno));
|
|
||||||
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0){
|
|
||||||
close(pipefd[0]);
|
|
||||||
close(pipefd[1]);
|
|
||||||
return fail("pipe2: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
pid = fork();
|
|
||||||
if(pid < 0){
|
|
||||||
close(pipefd[0]);
|
|
||||||
close(pipefd[1]);
|
|
||||||
close(errpipe[0]);
|
|
||||||
close(errpipe[1]);
|
|
||||||
return fail("fork Xvfb: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
if(pid == 0){
|
|
||||||
close(pipefd[0]);
|
|
||||||
close(errpipe[0]);
|
|
||||||
n = fcntl(pipefd[1], F_GETFD);
|
|
||||||
if(n < 0 || fcntl(pipefd[1], F_SETFD, n & ~FD_CLOEXEC) < 0 ||
|
|
||||||
!childfds(errpipe[1]))
|
|
||||||
_exit(126);
|
|
||||||
snprintf(fdarg, sizeof fdarg, "%d", pipefd[1]);
|
|
||||||
execlp(program, program, "-displayfd", fdarg, "-screen", "0",
|
|
||||||
"1024x768x24", "-nolisten", "tcp", "-noreset", (char*)0);
|
|
||||||
_exit(127);
|
|
||||||
}
|
|
||||||
close(pipefd[1]);
|
|
||||||
close(errpipe[1]);
|
|
||||||
d->pid = pid;
|
|
||||||
d->errfd = errpipe[0];
|
|
||||||
pfd.fd = pipefd[0];
|
|
||||||
pfd.events = POLLIN|POLLHUP;
|
|
||||||
deadline = nowms() + Starttimeout;
|
|
||||||
n = 0;
|
|
||||||
while(n + 1 < (int)sizeof line){
|
|
||||||
pfd.revents = 0;
|
|
||||||
if(poll(&pfd, 1, leftms(deadline)) <= 0)
|
|
||||||
break;
|
|
||||||
nr = read(pipefd[0], line + n, sizeof line - n - 1);
|
|
||||||
if(nr < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(nr <= 0)
|
|
||||||
break;
|
|
||||||
n += nr;
|
|
||||||
line[n] = '\0';
|
|
||||||
if(strchr(line, '\n') != NULL)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
close(pipefd[0]);
|
|
||||||
if(n == 0 || strchr(line, '\n') == NULL)
|
|
||||||
return fail("Xvfb did not report a private display");
|
|
||||||
line[strcspn(line, "\r\n")] = '\0';
|
|
||||||
if(strspn(line, "0123456789") != strlen(line))
|
|
||||||
return fail("invalid Xvfb display number %s", line);
|
|
||||||
if(snprintf(l->display, sizeof l->display, ":%s", line)
|
|
||||||
>= (int)sizeof l->display)
|
|
||||||
return fail("Xvfb display number is too long");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
startdaemon(Live *l, Daemon *d, char *program, char *arg)
|
|
||||||
{
|
|
||||||
int errpipe[2], fd;
|
|
||||||
long maxfd;
|
|
||||||
pid_t pid;
|
|
||||||
|
|
||||||
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0)
|
|
||||||
return fail("pipe2: %s", strerror(errno));
|
|
||||||
pid = fork();
|
|
||||||
if(pid < 0){
|
|
||||||
close(errpipe[0]);
|
|
||||||
close(errpipe[1]);
|
|
||||||
return fail("fork: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
if(pid == 0){
|
|
||||||
close(errpipe[0]);
|
|
||||||
if(!childfds(errpipe[1]))
|
|
||||||
_exit(126);
|
|
||||||
if(close_range(3, UINT_MAX, 0) < 0){
|
|
||||||
maxfd = sysconf(_SC_OPEN_MAX);
|
|
||||||
if(maxfd < 0)
|
|
||||||
maxfd = 1024;
|
|
||||||
for(fd = 3; fd < maxfd; fd++)
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
if(setenv("XDG_RUNTIME_DIR", l->runtime, 1) < 0 ||
|
|
||||||
setenv("XDG_CONFIG_HOME", l->config, 1) < 0 ||
|
|
||||||
setenv("HOME", l->home, 1) < 0 ||
|
|
||||||
(l->display[0] != '\0' ?
|
|
||||||
setenv("DISPLAY", l->display, 1) :
|
|
||||||
unsetenv("DISPLAY")) < 0 ||
|
|
||||||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
|
||||||
unsetenv("IBUS_ADDRESS") < 0 ||
|
|
||||||
unsetenv("IBUS_ADDRESS_FILE") < 0){
|
|
||||||
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
|
|
||||||
strerror(errno));
|
|
||||||
_exit(126);
|
|
||||||
}
|
|
||||||
execl(program, program, arg, (char*)0);
|
|
||||||
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
|
||||||
_exit(127);
|
|
||||||
}
|
|
||||||
close(errpipe[1]);
|
|
||||||
d->pid = pid;
|
|
||||||
d->errfd = errpipe[0];
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
daemonalive(Daemon *d)
|
|
||||||
{
|
|
||||||
int n, status;
|
|
||||||
|
|
||||||
if(d->pid <= 0)
|
|
||||||
return fail("%s is no longer running", d->name);
|
|
||||||
do
|
|
||||||
n = waitpid(d->pid, &status, WNOHANG);
|
|
||||||
while(n < 0 && errno == EINTR);
|
|
||||||
if(n == 0)
|
|
||||||
return 1;
|
|
||||||
if(n == d->pid){
|
|
||||||
d->pid = -1;
|
|
||||||
return fail("%s exited unexpectedly with wait status %#x", d->name,
|
|
||||||
status);
|
|
||||||
}
|
|
||||||
if(n < 0){
|
|
||||||
fail("check %s %ld: %s", d->name, (long)d->pid, strerror(errno));
|
|
||||||
if(errno == ECHILD)
|
|
||||||
d->pid = -1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return fail("waitpid returned the wrong child for %s", d->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
killdaemon(Daemon *d, int *status)
|
|
||||||
{
|
|
||||||
int n, ok, wait;
|
|
||||||
|
|
||||||
if(d->pid <= 0)
|
|
||||||
return 1;
|
|
||||||
ok = 1;
|
|
||||||
if(kill(d->pid, SIGKILL) < 0 && errno != ESRCH)
|
|
||||||
ok = fail("kill -9 %s %ld: %s", d->name, (long)d->pid,
|
|
||||||
strerror(errno));
|
|
||||||
do
|
|
||||||
n = waitpid(d->pid, &wait, 0);
|
|
||||||
while(n < 0 && errno == EINTR);
|
|
||||||
if(n != d->pid)
|
|
||||||
ok = fail("reap %s %ld after SIGKILL: %s", d->name, (long)d->pid,
|
|
||||||
n < 0 ? strerror(errno) : "wrong child");
|
|
||||||
else if(status != NULL)
|
|
||||||
*status = wait;
|
|
||||||
d->pid = -1;
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
stopdaemon(Daemon *d)
|
|
||||||
{
|
|
||||||
struct pollfd pfd;
|
|
||||||
int n, ok, reaped, status;
|
|
||||||
int64_t deadline;
|
|
||||||
|
|
||||||
if(d->pid <= 0)
|
|
||||||
return 1;
|
|
||||||
if(!daemonalive(d))
|
|
||||||
return 0;
|
|
||||||
ok = 1;
|
|
||||||
status = 0;
|
|
||||||
reaped = 0;
|
|
||||||
if(kill(d->pid, SIGTERM) < 0 && errno != ESRCH)
|
|
||||||
ok = fail("kill %s %ld: %s", d->name, (long)d->pid, strerror(errno));
|
|
||||||
deadline = nowms() + Stoptimeout;
|
|
||||||
while(d->pid > 0){
|
|
||||||
do
|
|
||||||
n = waitpid(d->pid, &status, WNOHANG);
|
|
||||||
while(n < 0 && errno == EINTR);
|
|
||||||
if(n == d->pid){
|
|
||||||
reaped = 1;
|
|
||||||
d->pid = -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(n < 0){
|
|
||||||
fail("waitpid %s %ld: %s", d->name, (long)d->pid,
|
|
||||||
strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
if(errno == ECHILD)
|
|
||||||
d->pid = -1;
|
|
||||||
else if(!killdaemon(d, NULL))
|
|
||||||
ok = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
n = leftms(deadline);
|
|
||||||
if(n == 0){
|
|
||||||
fail("%s %ld did not stop after SIGTERM", d->name, (long)d->pid);
|
|
||||||
ok = 0;
|
|
||||||
if(!killdaemon(d, NULL))
|
|
||||||
ok = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pfd.fd = d->errfd;
|
|
||||||
pfd.events = POLLIN|POLLHUP;
|
|
||||||
pfd.revents = 0;
|
|
||||||
if(poll(&pfd, 1, n) < 0 && errno != EINTR){
|
|
||||||
fail("poll %s %ld: %s", d->name, (long)d->pid, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
if(!killdaemon(d, NULL))
|
|
||||||
ok = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
readerrors(d);
|
|
||||||
}
|
|
||||||
/* plan9port turns a caught termination note into exit status 1. */
|
|
||||||
if(reaped && !((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) ||
|
|
||||||
(WIFEXITED(status) && WEXITSTATUS(status) <= 1)))
|
|
||||||
ok = fail("%s exited with unexpected wait status %#x", d->name,
|
|
||||||
status);
|
|
||||||
readerrors(d);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
findaddress(Live *l)
|
|
||||||
{
|
|
||||||
DIR *dir;
|
|
||||||
struct dirent *de;
|
|
||||||
int count, ok;
|
|
||||||
|
|
||||||
dir = opendir(l->bus);
|
|
||||||
if(dir == NULL){
|
|
||||||
fail("open IBus directory %s: %s", l->bus, strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
count = 0;
|
|
||||||
ok = 1;
|
|
||||||
errno = 0;
|
|
||||||
while((de = readdir(dir)) != NULL){
|
|
||||||
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0 ||
|
|
||||||
strstr(de->d_name, ".tmp.") != NULL)
|
|
||||||
continue;
|
|
||||||
count++;
|
|
||||||
if(snprintf(l->addrfile, sizeof l->addrfile, "%s/%s", l->bus,
|
|
||||||
de->d_name) >= (int)sizeof l->addrfile){
|
|
||||||
ok = fail("IBus address path is too long");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(errno != 0)
|
|
||||||
ok = fail("read IBus directory %s: %s", l->bus, strerror(errno));
|
|
||||||
if(closedir(dir) < 0)
|
|
||||||
ok = fail("close IBus directory %s: %s", l->bus, strerror(errno));
|
|
||||||
if(!ok)
|
|
||||||
return -1;
|
|
||||||
if(count > 1){
|
|
||||||
fail("found %d IBus address files", count);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
readfile(char *path, char *buf, size_t cap, size_t *nread)
|
|
||||||
{
|
|
||||||
struct stat st;
|
|
||||||
ssize_t n;
|
|
||||||
size_t off;
|
|
||||||
char extra;
|
|
||||||
int fd, ok;
|
|
||||||
|
|
||||||
fd = open(path, O_RDONLY|O_CLOEXEC);
|
|
||||||
if(fd < 0)
|
|
||||||
return fail("open %s: %s", path, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
if(fstat(fd, &st) < 0){
|
|
||||||
fail("stat open file %s: %s", path, strerror(errno));
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if(st.st_size < 0 || (uintmax_t)st.st_size >= cap){
|
|
||||||
fail("file %s is too large", path);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
off = 0;
|
|
||||||
while(off < (size_t)st.st_size){
|
|
||||||
n = read(fd, buf + off, (size_t)st.st_size - off);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n <= 0){
|
|
||||||
fail("read %s: %s", path,
|
|
||||||
n == 0 ? "unexpected EOF" : strerror(errno));
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
off += n;
|
|
||||||
}
|
|
||||||
do
|
|
||||||
n = read(fd, &extra, 1);
|
|
||||||
while(n < 0 && errno == EINTR);
|
|
||||||
if(n != 0){
|
|
||||||
fail("file %s changed while being read", path);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
buf[off] = '\0';
|
|
||||||
*nread = off;
|
|
||||||
ok = 1;
|
|
||||||
out:
|
|
||||||
if(close(fd) < 0){
|
|
||||||
fail("close %s: %s", path, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
parseaddress(char *contents, size_t ncontents, char *address, size_t naddress,
|
|
||||||
pid_t *declared)
|
|
||||||
{
|
|
||||||
char found[512];
|
|
||||||
long pid;
|
|
||||||
int consumed;
|
|
||||||
|
|
||||||
consumed = -1;
|
|
||||||
if(strlen(contents) != ncontents ||
|
|
||||||
sscanf(contents, "IBUS_ADDRESS=%511[^\n]\nIBUS_DAEMON_PID=%ld\n%n",
|
|
||||||
found, &pid, &consumed) != 2 || consumed != (int)ncontents || pid <= 0)
|
|
||||||
return fail("invalid IBus address file contents");
|
|
||||||
if(snprintf(address, naddress, "%s", found) >= (int)naddress)
|
|
||||||
return fail("private IBus address is too long");
|
|
||||||
*declared = (pid_t)pid;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Wait for the protected IPC socket and, when addr is not null, for the
|
|
||||||
* IBus address file the daemon publishes for itself.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
waitready(Live *l, Daemon *d, char *addr, size_t naddr)
|
|
||||||
{
|
|
||||||
struct pollfd pfd;
|
|
||||||
struct stat st;
|
|
||||||
char contents[2048];
|
|
||||||
size_t ncontents;
|
|
||||||
pid_t declared;
|
|
||||||
int count, n, timeout;
|
|
||||||
int64_t deadline;
|
|
||||||
|
|
||||||
deadline = nowms() + Starttimeout;
|
|
||||||
for(;;){
|
|
||||||
if(lstat(l->socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
|
||||||
(st.st_mode & 0777) == 0600){
|
|
||||||
if(addr == NULL)
|
|
||||||
return 1;
|
|
||||||
count = findaddress(l);
|
|
||||||
if(count < 0)
|
|
||||||
return 0;
|
|
||||||
if(count == 1 &&
|
|
||||||
readfile(l->addrfile, contents, sizeof contents, &ncontents) &&
|
|
||||||
parseaddress(contents, ncontents, addr, naddr, &declared) &&
|
|
||||||
declared == d->pid)
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(!daemonalive(d))
|
|
||||||
return 0;
|
|
||||||
timeout = leftms(deadline);
|
|
||||||
if(timeout == 0)
|
|
||||||
return fail("timed out waiting for the %s endpoints", d->name);
|
|
||||||
pfd.fd = d->errfd;
|
|
||||||
pfd.events = POLLIN|POLLHUP;
|
|
||||||
pfd.revents = 0;
|
|
||||||
n = poll(&pfd, 1, timeout > 20 ? 20 : timeout);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n < 0)
|
|
||||||
return fail("poll %s readiness: %s", d->name, strerror(errno));
|
|
||||||
if(pfd.revents & (POLLERR|POLLNVAL))
|
|
||||||
return fail("%s stderr pipe became unusable: %#x", d->name,
|
|
||||||
pfd.revents);
|
|
||||||
if(pfd.revents & (POLLIN|POLLHUP))
|
|
||||||
readerrors(d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
connectsocket(char *path, int64_t deadline)
|
|
||||||
{
|
|
||||||
struct sockaddr_un addr;
|
|
||||||
struct pollfd pfd;
|
|
||||||
socklen_t nerr;
|
|
||||||
int err, fd, flags, n;
|
|
||||||
|
|
||||||
memset(&addr, 0, sizeof addr);
|
|
||||||
addr.sun_family = AF_UNIX;
|
|
||||||
if(snprintf(addr.sun_path, sizeof addr.sun_path, "%s", path)
|
|
||||||
>= (int)sizeof addr.sun_path){
|
|
||||||
errno = ENAMETOOLONG;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
|
|
||||||
if(fd < 0)
|
|
||||||
return -1;
|
|
||||||
if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0 &&
|
|
||||||
errno != EINPROGRESS){
|
|
||||||
err = errno;
|
|
||||||
close(fd);
|
|
||||||
errno = err;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
for(;;){
|
|
||||||
n = leftms(deadline);
|
|
||||||
if(n == 0){
|
|
||||||
close(fd);
|
|
||||||
errno = ETIMEDOUT;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
pfd.fd = fd;
|
|
||||||
pfd.events = POLLOUT;
|
|
||||||
pfd.revents = 0;
|
|
||||||
n = poll(&pfd, 1, n);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n <= 0){
|
|
||||||
err = n == 0 ? ETIMEDOUT : errno;
|
|
||||||
close(fd);
|
|
||||||
errno = err;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
err = 0;
|
|
||||||
nerr = sizeof err;
|
|
||||||
if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &nerr) < 0 || err != 0){
|
|
||||||
if(err == 0)
|
|
||||||
err = errno;
|
|
||||||
close(fd);
|
|
||||||
errno = err;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
flags = fcntl(fd, F_GETFL);
|
|
||||||
if(flags < 0 || fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0){
|
|
||||||
err = errno;
|
|
||||||
close(fd);
|
|
||||||
errno = err;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 1 is complete, 0 is peer closure, and -1 is a timeout or I/O error. */
|
|
||||||
int
|
|
||||||
readuntil(int fd, void *buf, size_t n, int64_t deadline)
|
|
||||||
{
|
|
||||||
struct pollfd pfd;
|
|
||||||
unsigned char *p;
|
|
||||||
ssize_t r;
|
|
||||||
int timeout;
|
|
||||||
|
|
||||||
p = buf;
|
|
||||||
while(n > 0){
|
|
||||||
timeout = leftms(deadline);
|
|
||||||
if(timeout == 0){
|
|
||||||
errno = ETIMEDOUT;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
pfd.fd = fd;
|
|
||||||
pfd.events = POLLIN;
|
|
||||||
pfd.revents = 0;
|
|
||||||
r = poll(&pfd, 1, timeout);
|
|
||||||
if(r < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(r <= 0){
|
|
||||||
if(r == 0)
|
|
||||||
errno = ETIMEDOUT;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
r = recv(fd, p, n, 0);
|
|
||||||
if(r < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(r < 0)
|
|
||||||
return -1;
|
|
||||||
if(r == 0)
|
|
||||||
return 0;
|
|
||||||
p += r;
|
|
||||||
n -= r;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
ipcrequest(int fd, uint32_t mod, uint32_t key, unsigned char *want,
|
|
||||||
size_t nwant, char *where)
|
|
||||||
{
|
|
||||||
unsigned char req[Ipcreqsz], got[16];
|
|
||||||
int rv;
|
|
||||||
|
|
||||||
if(nwant > sizeof got)
|
|
||||||
return fail("%s expected IPC response is too large", where);
|
|
||||||
ipcpackreq(req, 1, mod, key);
|
|
||||||
if(ipcsend(fd, req, sizeof req) < 0)
|
|
||||||
return fail("send %s IPC request: %s", where, strerror(errno));
|
|
||||||
rv = readuntil(fd, got, nwant, nowms() + Calltimeout);
|
|
||||||
if(rv != 1)
|
|
||||||
return fail("read %s IPC response: %s", where,
|
|
||||||
rv == 0 ? "peer closed" : strerror(errno));
|
|
||||||
if(memcmp(got, want, nwant) != 0)
|
|
||||||
return fail("%s IPC response did not match", where);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
ipcprobe(int fd, char *where)
|
|
||||||
{
|
|
||||||
/* eaten, take-back, no commit, no preedit */
|
|
||||||
unsigned char empty[] = {0, 0, 0, 0, 0, 0};
|
|
||||||
|
|
||||||
return ipcrequest(fd, 0, 0, empty, sizeof empty, where);
|
|
||||||
}
|
|
||||||
75
tests/live.h
75
tests/live.h
@@ -1,75 +0,0 @@
|
|||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Harness shared by the live tests: a strans daemon started in a private
|
|
||||||
* XDG_RUNTIME_DIR with its output captured, the endpoints it publishes,
|
|
||||||
* and the IPC probe used to prove the daemon answers.
|
|
||||||
* Set testname before anything else; fail prefixes it to every message.
|
|
||||||
*/
|
|
||||||
|
|
||||||
typedef struct Daemon Daemon;
|
|
||||||
typedef struct Live Live;
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Calltimeout = 4000,
|
|
||||||
Starttimeout = 8000,
|
|
||||||
Stoptimeout = 3000,
|
|
||||||
Errmax = 8192,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Daemon
|
|
||||||
{
|
|
||||||
pid_t pid;
|
|
||||||
int errfd;
|
|
||||||
char *name;
|
|
||||||
char err[Errmax];
|
|
||||||
size_t nerr;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Live
|
|
||||||
{
|
|
||||||
char root[256];
|
|
||||||
char runtime[320];
|
|
||||||
char config[320];
|
|
||||||
char ibus[384];
|
|
||||||
char bus[448];
|
|
||||||
char home[320];
|
|
||||||
char socket[384];
|
|
||||||
char addrfile[512];
|
|
||||||
char display[32];
|
|
||||||
};
|
|
||||||
|
|
||||||
extern char *testname;
|
|
||||||
|
|
||||||
int fail(char*, ...);
|
|
||||||
int64_t nowms(void);
|
|
||||||
int leftms(int64_t);
|
|
||||||
void pausems(int);
|
|
||||||
int makedir(char*);
|
|
||||||
int cleardir(char*);
|
|
||||||
|
|
||||||
int livesetup(Live*, char*);
|
|
||||||
int liveclean(Live*);
|
|
||||||
|
|
||||||
void daemoninit(Daemon*, char*);
|
|
||||||
int startxvfb(Live*, Daemon*, char*);
|
|
||||||
int startdaemon(Live*, Daemon*, char*, char*);
|
|
||||||
int waitready(Live*, Daemon*, char*, size_t);
|
|
||||||
int daemonalive(Daemon*);
|
|
||||||
int stopdaemon(Daemon*);
|
|
||||||
int killdaemon(Daemon*, int*);
|
|
||||||
void readerrors(Daemon*);
|
|
||||||
void showerrors(Daemon*);
|
|
||||||
int closeerrors(Daemon*);
|
|
||||||
|
|
||||||
int findaddress(Live*);
|
|
||||||
int readfile(char*, char*, size_t, size_t*);
|
|
||||||
int parseaddress(char*, size_t, char*, size_t, pid_t*);
|
|
||||||
|
|
||||||
int connectsocket(char*, int64_t);
|
|
||||||
int readuntil(int, void*, size_t, int64_t);
|
|
||||||
int ipcrequest(int, uint32_t, uint32_t, unsigned char*, size_t, char*);
|
|
||||||
int ipcprobe(int, char*);
|
|
||||||
168
tests/livebus.c
168
tests/livebus.c
@@ -1,168 +0,0 @@
|
|||||||
#define _GNU_SOURCE
|
|
||||||
#include <dbus/dbus.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "live.h"
|
|
||||||
#include "livebus.h"
|
|
||||||
|
|
||||||
DBusConnection*
|
|
||||||
openbus(char *address, char *where)
|
|
||||||
{
|
|
||||||
DBusConnection *conn;
|
|
||||||
DBusError err;
|
|
||||||
|
|
||||||
dbus_error_init(&err);
|
|
||||||
conn = dbus_connection_open_private(address, &err);
|
|
||||||
if(conn == NULL){
|
|
||||||
fail("open %s private IBus connection: %s", where,
|
|
||||||
err.message != NULL ? err.message : "D-Bus error");
|
|
||||||
dbus_error_free(&err);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
dbus_error_free(&err);
|
|
||||||
dbus_connection_set_exit_on_disconnect(conn, FALSE);
|
|
||||||
return conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
closebus(DBusConnection **conn)
|
|
||||||
{
|
|
||||||
if(*conn == NULL)
|
|
||||||
return;
|
|
||||||
dbus_connection_close(*conn);
|
|
||||||
dbus_connection_unref(*conn);
|
|
||||||
*conn = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
DBusMessage*
|
|
||||||
contextcall(char *path, char *member)
|
|
||||||
{
|
|
||||||
return dbus_message_new_method_call("org.freedesktop.IBus", path,
|
|
||||||
"org.freedesktop.IBus.InputContext", member);
|
|
||||||
}
|
|
||||||
|
|
||||||
DBusMessage*
|
|
||||||
sendcall(DBusConnection *conn, DBusMessage *m, char *where)
|
|
||||||
{
|
|
||||||
DBusMessage *reply;
|
|
||||||
DBusPendingCall *pending;
|
|
||||||
int timeout;
|
|
||||||
int64_t deadline;
|
|
||||||
|
|
||||||
if(m == NULL){
|
|
||||||
fail("allocate %s call", where);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
pending = NULL;
|
|
||||||
if(!dbus_connection_send_with_reply(conn, m, &pending, Calltimeout) ||
|
|
||||||
pending == NULL){
|
|
||||||
dbus_message_unref(m);
|
|
||||||
fail("queue %s call", where);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
dbus_message_unref(m);
|
|
||||||
deadline = nowms() + Calltimeout;
|
|
||||||
while(!dbus_pending_call_get_completed(pending)){
|
|
||||||
timeout = leftms(deadline);
|
|
||||||
if(timeout == 0){
|
|
||||||
fail("timed out waiting for %s", where);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if(!dbus_connection_read_write_dispatch(conn, timeout)){
|
|
||||||
fail("connection closed waiting for %s", where);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reply = dbus_pending_call_steal_reply(pending);
|
|
||||||
dbus_pending_call_unref(pending);
|
|
||||||
if(reply == NULL)
|
|
||||||
fail("%s completed without a reply", where);
|
|
||||||
return reply;
|
|
||||||
out:
|
|
||||||
dbus_pending_call_cancel(pending);
|
|
||||||
dbus_pending_call_unref(pending);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
DBusMessage*
|
|
||||||
callret(DBusConnection *conn, DBusMessage *m, char *where)
|
|
||||||
{
|
|
||||||
DBusMessage *reply;
|
|
||||||
|
|
||||||
reply = sendcall(conn, m, where);
|
|
||||||
if(reply == NULL)
|
|
||||||
return NULL;
|
|
||||||
if(dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_METHOD_RETURN){
|
|
||||||
fail("%s returned D-Bus message type %d", where,
|
|
||||||
dbus_message_get_type(reply));
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return reply;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
hello(DBusConnection *conn, char *name, size_t nname, char *where)
|
|
||||||
{
|
|
||||||
DBusMessage *m, *reply;
|
|
||||||
DBusError err;
|
|
||||||
const char *s;
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
m = dbus_message_new_method_call("org.freedesktop.DBus",
|
|
||||||
"/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello");
|
|
||||||
reply = callret(conn, m, where);
|
|
||||||
if(reply == NULL)
|
|
||||||
return 0;
|
|
||||||
dbus_error_init(&err);
|
|
||||||
ok = dbus_message_has_signature(reply, "s") &&
|
|
||||||
dbus_message_get_args(reply, &err, DBUS_TYPE_STRING, &s,
|
|
||||||
DBUS_TYPE_INVALID);
|
|
||||||
if(!ok)
|
|
||||||
fail("%s returned an invalid Hello reply", where);
|
|
||||||
else if(s[0] != ':')
|
|
||||||
ok = fail("%s returned invalid Hello name %s", where, s);
|
|
||||||
else if(name != NULL && snprintf(name, nname, "%s", s) >= (int)nname)
|
|
||||||
ok = fail("%s Hello name is too long", where);
|
|
||||||
dbus_error_free(&err);
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
createcontext(DBusConnection *conn, char *path, size_t npath, char *where)
|
|
||||||
{
|
|
||||||
DBusMessage *m, *reply;
|
|
||||||
DBusError err;
|
|
||||||
const char *client, *p;
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
client = testname;
|
|
||||||
m = dbus_message_new_method_call("org.freedesktop.IBus",
|
|
||||||
"/org/freedesktop/IBus", "org.freedesktop.IBus",
|
|
||||||
"CreateInputContext");
|
|
||||||
if(m == NULL || !dbus_message_append_args(m, DBUS_TYPE_STRING, &client,
|
|
||||||
DBUS_TYPE_INVALID)){
|
|
||||||
if(m != NULL)
|
|
||||||
dbus_message_unref(m);
|
|
||||||
return fail("build %s CreateInputContext call", where);
|
|
||||||
}
|
|
||||||
reply = callret(conn, m, where);
|
|
||||||
if(reply == NULL)
|
|
||||||
return 0;
|
|
||||||
dbus_error_init(&err);
|
|
||||||
ok = dbus_message_has_signature(reply, "o") &&
|
|
||||||
dbus_message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, &p,
|
|
||||||
DBUS_TYPE_INVALID);
|
|
||||||
if(!ok)
|
|
||||||
fail("%s returned an invalid context reply", where);
|
|
||||||
else if(p[0] != '/')
|
|
||||||
ok = fail("%s returned invalid context path %s", where, p);
|
|
||||||
else if(path != NULL && snprintf(path, npath, "%s", p) >= (int)npath)
|
|
||||||
ok = fail("%s context path is too long", where);
|
|
||||||
dbus_error_free(&err);
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
#include <dbus/dbus.h>
|
|
||||||
|
|
||||||
/*
|
|
||||||
* D-Bus half of the live harness: private connections to the IBus address
|
|
||||||
* a strans daemon publishes, and the calls every IBus client makes first.
|
|
||||||
* sendcall returns any reply, callret only a method return.
|
|
||||||
*/
|
|
||||||
|
|
||||||
DBusConnection* openbus(char*, char*);
|
|
||||||
void closebus(DBusConnection**);
|
|
||||||
DBusMessage* contextcall(char*, char*);
|
|
||||||
DBusMessage* sendcall(DBusConnection*, DBusMessage*, char*);
|
|
||||||
DBusMessage* callret(DBusConnection*, DBusMessage*, char*);
|
|
||||||
int hello(DBusConnection*, char*, size_t, char*);
|
|
||||||
int createcontext(DBusConnection*, char*, size_t, char*);
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import unittest
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
|
||||||
MKEMOJI = ROOT / "map" / "mkemoji"
|
|
||||||
TIMEOUT = 10
|
|
||||||
|
|
||||||
|
|
||||||
def generate(*sources):
|
|
||||||
args = [sys.executable, "-B", str(MKEMOJI)] + [str(s) for s in sources]
|
|
||||||
return subprocess.run(
|
|
||||||
args, capture_output=True, text=True, check=False, timeout=TIMEOUT
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def table(output):
|
|
||||||
return dict(line.split("\t", 1) for line in output.splitlines())
|
|
||||||
|
|
||||||
|
|
||||||
class MkemojiTest(unittest.TestCase):
|
|
||||||
def source(self, text):
|
|
||||||
tmp = tempfile.TemporaryDirectory()
|
|
||||||
path = Path(tmp.name) / "emoji.src"
|
|
||||||
path.write_text(text, encoding="utf-8")
|
|
||||||
self.addCleanup(tmp.cleanup)
|
|
||||||
return path
|
|
||||||
|
|
||||||
def test_production_digit_slots(self):
|
|
||||||
result = generate()
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
data = table(result.stdout)
|
|
||||||
# The engine walks a prefix's children in rune order, so only the
|
|
||||||
# prefix's own row decides which candidate a digit picks.
|
|
||||||
self.assertEqual(data["^"].split(), list("¹²³⁴⁵⁶⁷⁸⁹"))
|
|
||||||
self.assertEqual(data["_"].split(), list("₁₂₃₄₅₆₇₈₉"))
|
|
||||||
self.assertEqual(data["<"].split()[2], "♥")
|
|
||||||
self.assertEqual(data["^1"], "¹")
|
|
||||||
self.assertEqual(data["_2"], "₂")
|
|
||||||
self.assertEqual(data["<3"].split()[0], "♥")
|
|
||||||
self.assertIn("😀", data["smile"].split())
|
|
||||||
self.assertIn("😀", data["웃음"].split())
|
|
||||||
|
|
||||||
def test_fold_normalize_and_source_order(self):
|
|
||||||
source = self.source(
|
|
||||||
"β\tALPHABET\talpha\n"
|
|
||||||
"α\talpha\n"
|
|
||||||
"é\tÉ\n"
|
|
||||||
"#\thash\n"
|
|
||||||
)
|
|
||||||
first = generate(source)
|
|
||||||
second = generate(source, source)
|
|
||||||
self.assertEqual(first.returncode, 0, first.stderr)
|
|
||||||
self.assertEqual(first.stdout, second.stdout)
|
|
||||||
data = table(first.stdout)
|
|
||||||
self.assertEqual(list(data), ["alphabet", "alpha", "é", "hash"])
|
|
||||||
self.assertEqual(data["alpha"].split(), ["β", "α"])
|
|
||||||
self.assertEqual(data["é"], "é")
|
|
||||||
self.assertEqual(data["hash"], "#")
|
|
||||||
|
|
||||||
def test_rejects_malformed_and_oversized_rows(self):
|
|
||||||
bad = [
|
|
||||||
"x\n",
|
|
||||||
"bad result\ta\n",
|
|
||||||
"x\t\n",
|
|
||||||
"x\t a\n",
|
|
||||||
"\0\ta\n",
|
|
||||||
"x\ta\0b\n",
|
|
||||||
"x\t;hidden\n",
|
|
||||||
"x" * 65 + "\ta\n",
|
|
||||||
"x\t" + "a" * 65 + "\n",
|
|
||||||
]
|
|
||||||
for text in bad:
|
|
||||||
with self.subTest(text=repr(text)):
|
|
||||||
result = generate(self.source(text))
|
|
||||||
self.assertEqual(result.returncode, 1)
|
|
||||||
|
|
||||||
def test_accepts_runtime_boundary(self):
|
|
||||||
source = self.source("x" * 64 + "\t" + "a" * 64 + "\n")
|
|
||||||
result = generate(source)
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
self.assertIn("a" * 64 + "\t" + "x" * 64, result.stdout)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import unittest
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
|
||||||
IMPORT = ROOT / "map" / "libhangul2hanja"
|
|
||||||
GENERATE = ROOT / "map" / "mkhanja"
|
|
||||||
TIMEOUT = 10
|
|
||||||
|
|
||||||
|
|
||||||
def run(program, source=None, stdin=None, hostile=False):
|
|
||||||
command = [sys.executable, "-B", str(program)]
|
|
||||||
if source is not None:
|
|
||||||
command.append(str(source))
|
|
||||||
env = None
|
|
||||||
if hostile:
|
|
||||||
env = os.environ.copy()
|
|
||||||
env.update(LC_ALL="C", LANG="C", PYTHONCOERCECLOCALE="0", PYTHONUTF8="0")
|
|
||||||
env.pop("PYTHONIOENCODING", None)
|
|
||||||
return subprocess.run(
|
|
||||||
command,
|
|
||||||
input=stdin,
|
|
||||||
capture_output=True,
|
|
||||||
encoding="utf-8",
|
|
||||||
env=env,
|
|
||||||
check=False,
|
|
||||||
timeout=TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MkhanjaTest(unittest.TestCase):
|
|
||||||
def source(self, text):
|
|
||||||
tmp = tempfile.TemporaryDirectory()
|
|
||||||
path = Path(tmp.name) / "hanja.txt"
|
|
||||||
path.write_text(text, encoding="utf-8")
|
|
||||||
self.addCleanup(tmp.cleanup)
|
|
||||||
return path
|
|
||||||
|
|
||||||
def test_imports_bmp_hanja_rows(self):
|
|
||||||
result = run(
|
|
||||||
IMPORT,
|
|
||||||
self.source(
|
|
||||||
"# Copyright holder\n"
|
|
||||||
"# BSD license\n"
|
|
||||||
"\ud55c:\u6f22:first\n"
|
|
||||||
"\uac00:\u3400:extension A\n"
|
|
||||||
"\uae40:\u91d1:compatibility\n"
|
|
||||||
"\ud55c\uc790:\u6f22\u5b57:word\n"
|
|
||||||
"\ud55c:\U00020000:astral\n"
|
|
||||||
"\u3131:\u52a0:jamo\n"
|
|
||||||
"\ud55c\uae00:\u97d3glyph:mixed\n"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
lines = result.stdout.splitlines()
|
|
||||||
self.assertEqual(lines[:2], ["# Copyright holder", "# BSD license"])
|
|
||||||
self.assertEqual(lines[2:], ["", "\u6f22\t\ud55c", "\u3400\t\uac00",
|
|
||||||
"\u91d1\t\uae40",
|
|
||||||
"\u6f22\u5b57\t\ud55c\uc790"])
|
|
||||||
|
|
||||||
def test_imports_symbol_rows_under_a_jamo(self):
|
|
||||||
result = run(
|
|
||||||
IMPORT,
|
|
||||||
self.source(
|
|
||||||
"\u3141:\u203b:reference mark\n"
|
|
||||||
"\u3134:\u300c:bracket\n"
|
|
||||||
"\u3131:\u3000:ideographic space\n"
|
|
||||||
"\u3131:\u00ad:soft hyphen\n"
|
|
||||||
"\u3131:\u52a0:Hanja under a jamo\n"
|
|
||||||
"\u3141:\u203b\u203b:two runes\n"
|
|
||||||
"\u3141\u3134:\u203b:two jamo\n"
|
|
||||||
"\u314f:\u203b:a vowel\n"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
self.assertEqual(result.stdout.splitlines(),
|
|
||||||
["\u203b\t\u3141", "\u300c\t\u3134"])
|
|
||||||
|
|
||||||
def test_import_rejects_malformed_and_duplicate_rows(self):
|
|
||||||
bad = [
|
|
||||||
"한 漢\n",
|
|
||||||
"한:漢:first\n한:漢:again\n",
|
|
||||||
]
|
|
||||||
for text in bad:
|
|
||||||
with self.subTest(text=repr(text)):
|
|
||||||
self.assertEqual(run(IMPORT, self.source(text)).returncode, 1)
|
|
||||||
|
|
||||||
def test_import_uses_utf8_without_locale(self):
|
|
||||||
text = "# 저작권\n한:漢:first\n"
|
|
||||||
expected = "# 저작권\n\n漢\t한\n"
|
|
||||||
for source, stdin in ((self.source(text), None), (None, text)):
|
|
||||||
with self.subTest(source=source):
|
|
||||||
result = run(IMPORT, source, stdin, hostile=True)
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
self.assertEqual(result.stdout, expected)
|
|
||||||
|
|
||||||
def test_groups_readings_and_preserves_order(self):
|
|
||||||
result = run(
|
|
||||||
GENERATE,
|
|
||||||
self.source(
|
|
||||||
"# Copyright holder\n"
|
|
||||||
"# BSD license\n"
|
|
||||||
"漢\t한\n"
|
|
||||||
"韓\t한\n"
|
|
||||||
"㐀\t가\n"
|
|
||||||
"家\t가\n"
|
|
||||||
"金\t금\n"
|
|
||||||
"漢字\t한자\n"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
self.assertEqual(
|
|
||||||
result.stdout.splitlines(),
|
|
||||||
[
|
|
||||||
";; Copyright holder",
|
|
||||||
";; BSD license",
|
|
||||||
"",
|
|
||||||
"한\t漢 韓",
|
|
||||||
"가\t㐀 家",
|
|
||||||
"금\t金",
|
|
||||||
"한자\t漢字",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_groups_symbols_under_their_jamo(self):
|
|
||||||
result = run(
|
|
||||||
GENERATE,
|
|
||||||
self.source(
|
|
||||||
"※\tㅁ\n"
|
|
||||||
"○\tㅁ\n"
|
|
||||||
"「\tㄴ\n"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
self.assertEqual(result.stdout.splitlines(),
|
|
||||||
["ㅁ\t※ ○", "ㄴ\t「"])
|
|
||||||
|
|
||||||
def test_generator_rejects_bad_rows(self):
|
|
||||||
bad = [
|
|
||||||
"漢a\t한\n",
|
|
||||||
"漢\t한a\n",
|
|
||||||
"𠀀\t한\n",
|
|
||||||
"漢\t한\n漢\t한\n",
|
|
||||||
"漢 한\n",
|
|
||||||
"加\tㅁ\n",
|
|
||||||
"※\t한\n",
|
|
||||||
"※\tㅁㄴ\n",
|
|
||||||
"※※\tㅁ\n",
|
|
||||||
"\u3000\tㄱ\n",
|
|
||||||
"\u00ad\tㄱ\n",
|
|
||||||
]
|
|
||||||
for text in bad:
|
|
||||||
with self.subTest(text=repr(text)):
|
|
||||||
self.assertEqual(run(GENERATE, self.source(text)).returncode, 1)
|
|
||||||
|
|
||||||
def test_generator_uses_utf8_without_locale(self):
|
|
||||||
result = run(GENERATE, self.source("# 저작권\n漢\t한\n"), hostile=True)
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
self.assertEqual(result.stdout, ";; 저작권\n\n한\t漢\n")
|
|
||||||
|
|
||||||
result = run(GENERATE, hostile=True)
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
self.assertIn("한\t", result.stdout)
|
|
||||||
|
|
||||||
def test_generator_reads_both_sources(self):
|
|
||||||
result = run(GENERATE)
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
lines = result.stdout.splitlines()
|
|
||||||
self.assertEqual(lines.count(";; All rights reserved."), 2)
|
|
||||||
rows = dict(line.split("\t") for line in lines if "\t" in line)
|
|
||||||
self.assertIn("漢", rows["한"].split())
|
|
||||||
self.assertIn("※", rows["ㅁ"].split())
|
|
||||||
|
|
||||||
def test_generator_keeps_runtime_candidate_limit(self):
|
|
||||||
rows = "".join(f"{chr(0x4E00 + n)}\t한\n" for n in range(129))
|
|
||||||
result = run(GENERATE, self.source(rows))
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
candidates = result.stdout.rstrip().split("\t", 1)[1].split()
|
|
||||||
self.assertEqual(len(candidates), 128)
|
|
||||||
self.assertEqual(candidates[-1], chr(0x4E00 + 127))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
@@ -1,323 +0,0 @@
|
|||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
/* A popup with every section, tall enough for the tests. */
|
|
||||||
#define Imgh (2*PopupPad + (Maxdisp + 2)*Fontsz + PopupSep)
|
|
||||||
|
|
||||||
enum {
|
|
||||||
Rgbmask = 0xffffff,
|
|
||||||
};
|
|
||||||
|
|
||||||
static u32int
|
|
||||||
pixel(u32int *img, Popup *p, int x, int y)
|
|
||||||
{
|
|
||||||
return img[y*p->w + x] & Rgbmask;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
rectcolor(u32int *img, Popup *p, int x0, int y0, int x1, int y1,
|
|
||||||
u32int color)
|
|
||||||
{
|
|
||||||
int x, y;
|
|
||||||
|
|
||||||
color &= Rgbmask;
|
|
||||||
for(y = y0; y < y1; y++)
|
|
||||||
for(x = x0; x < x1; x++)
|
|
||||||
if(pixel(img, p, x, y) != color)
|
|
||||||
return 0;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
checkpadding(struct ct *t, u32int *img, Popup *p)
|
|
||||||
{
|
|
||||||
CT_CHECK(t, rectcolor(img, p, 0, 0, p->w, PopupPad, Colbg));
|
|
||||||
CT_CHECK(t, rectcolor(img, p, 0, p->h - PopupPad,
|
|
||||||
p->w, p->h, Colbg));
|
|
||||||
CT_CHECK(t, rectcolor(img, p, 0, 0, PopupPad, p->h, Colbg));
|
|
||||||
CT_CHECK(t, rectcolor(img, p, p->w - PopupPad, 0,
|
|
||||||
p->w, p->h, Colbg));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
popup_layout(struct ct *t)
|
|
||||||
{
|
|
||||||
static const struct {
|
|
||||||
int first;
|
|
||||||
int shown;
|
|
||||||
int total;
|
|
||||||
char *want;
|
|
||||||
} markers[] = {
|
|
||||||
{ 0, 0, 0, "" },
|
|
||||||
{ 0, 9, 9, "" },
|
|
||||||
{ 0, 3, 9, "1-3/9" },
|
|
||||||
{ 8, 1, 9, "9-9/9" },
|
|
||||||
{ 0, 9, 10, "1-9/10" },
|
|
||||||
{ 9, 1, 10, "10-10/10" },
|
|
||||||
{ 0, 9, 18, "1-9/18" },
|
|
||||||
{ 9, 9, 18, "10-18/18" },
|
|
||||||
{ 9, 9, 32, "10-18/32" },
|
|
||||||
{ 18, 9, 32, "19-27/32" },
|
|
||||||
{ 27, 5, 32, "28-32/32" },
|
|
||||||
};
|
|
||||||
char buf[32];
|
|
||||||
Area area, mon[2], out, work;
|
|
||||||
Caret caret;
|
|
||||||
Drawcmd dc;
|
|
||||||
Popup p;
|
|
||||||
Str longrow;
|
|
||||||
u32int c, *img;
|
|
||||||
int i, ink, markink, n, numink, selbg, textink, x, y;
|
|
||||||
|
|
||||||
for(i = 0; i < nelem(markers); i++){
|
|
||||||
n = pagemarker(buf, sizeof buf, markers[i].first,
|
|
||||||
markers[i].shown, markers[i].total);
|
|
||||||
CT_EQ_INT(t, strlen(markers[i].want), n);
|
|
||||||
CT_CHECK(t, strcmp(markers[i].want, buf) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Monitor selection retains origins and intersects the EWMH work area. */
|
|
||||||
mon[0] = (Area){0, 0, 1920, 1080};
|
|
||||||
mon[1] = (Area){1920, 200, 1280, 1024};
|
|
||||||
work = (Area){0, 30, 3200, 1070};
|
|
||||||
popuparea(mon, 2, &work, 2500, 500, &out);
|
|
||||||
CT_EQ_INT(t, 1920, out.x);
|
|
||||||
CT_EQ_INT(t, 200, out.y);
|
|
||||||
CT_EQ_INT(t, 1280, out.w);
|
|
||||||
CT_EQ_INT(t, 900, out.h);
|
|
||||||
work = (Area){60, 0, 3140, 1224};
|
|
||||||
popuparea(mon, 2, &work, 100, 100, &out);
|
|
||||||
CT_EQ_INT(t, 60, out.x);
|
|
||||||
CT_EQ_INT(t, 0, out.y);
|
|
||||||
CT_EQ_INT(t, 1860, out.w);
|
|
||||||
CT_EQ_INT(t, 1080, out.h);
|
|
||||||
work = (Area){5000, 0, 100, 100};
|
|
||||||
popuparea(mon, 2, &work, 2500, 500, &out);
|
|
||||||
CT_EQ_INT(t, mon[1].x, out.x);
|
|
||||||
CT_EQ_INT(t, mon[1].y, out.y);
|
|
||||||
CT_EQ_INT(t, mon[1].w, out.w);
|
|
||||||
CT_EQ_INT(t, mon[1].h, out.h);
|
|
||||||
mon[0] = (Area){0, 0, 100, 100};
|
|
||||||
mon[1] = (Area){200, 0, 100, 100};
|
|
||||||
popuparea(mon, 2, nil, 150, 50, &out);
|
|
||||||
CT_EQ_INT(t, 0, out.x);
|
|
||||||
|
|
||||||
memset(&caret, 0, sizeof caret);
|
|
||||||
area = (Area){1920, 30, 1280, 900};
|
|
||||||
popupposition(&caret, 2000, 50, &area, 100, 60, &x, &y);
|
|
||||||
CT_EQ_INT(t, 2010, x);
|
|
||||||
CT_EQ_INT(t, 60, y);
|
|
||||||
popupposition(&caret, 2000, 900, &area, 100, 60, &x, &y);
|
|
||||||
CT_EQ_INT(t, 830, y); /* above the pointer, not over it */
|
|
||||||
caret.valid = 1;
|
|
||||||
caret.x = 2100;
|
|
||||||
caret.y = 80;
|
|
||||||
caret.h = 20;
|
|
||||||
popupposition(&caret, 0, 0, &area, 100, 60, &x, &y);
|
|
||||||
CT_EQ_INT(t, 2100, x);
|
|
||||||
CT_EQ_INT(t, 100, y);
|
|
||||||
caret.x = 3150;
|
|
||||||
caret.y = 850;
|
|
||||||
popupposition(&caret, 0, 0, &area, 200, 100, &x, &y);
|
|
||||||
CT_EQ_INT(t, 3000, x);
|
|
||||||
CT_EQ_INT(t, 750, y);
|
|
||||||
|
|
||||||
/* A popup no larger than a small work area stays inside its origin. */
|
|
||||||
area = (Area){100, 200, 80, 80};
|
|
||||||
caret.x = 120;
|
|
||||||
caret.y = 230;
|
|
||||||
caret.h = 20;
|
|
||||||
popupposition(&caret, 0, 0, &area, 80, 80, &x, &y);
|
|
||||||
CT_EQ_INT(t, 100, x);
|
|
||||||
CT_EQ_INT(t, 200, y);
|
|
||||||
|
|
||||||
textinit();
|
|
||||||
|
|
||||||
/* Empty sections reserve nothing. */
|
|
||||||
memset(&dc, 0, sizeof dc);
|
|
||||||
dc.sel = -1;
|
|
||||||
popuplayout(&dc, PopupBasew, Imgh, &p);
|
|
||||||
CT_EQ_INT(t, 0, p.w);
|
|
||||||
CT_EQ_INT(t, 0, p.h);
|
|
||||||
CT_EQ_INT(t, -1, p.prey);
|
|
||||||
CT_EQ_INT(t, -1, p.rowsy);
|
|
||||||
CT_EQ_INT(t, -1, p.marky);
|
|
||||||
|
|
||||||
/* Preedit only: as wide as its text. */
|
|
||||||
dc.pre = mkstr("preedit");
|
|
||||||
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
|
|
||||||
CT_EQ_INT(t, textwidth(&dc.pre) + 2*PopupPad, p.w);
|
|
||||||
CT_EQ_INT(t, PopupPad, p.prey);
|
|
||||||
CT_EQ_INT(t, -1, p.sepy);
|
|
||||||
CT_EQ_INT(t, -1, p.rowsy);
|
|
||||||
CT_EQ_INT(t, -1, p.marky);
|
|
||||||
CT_EQ_INT(t, 2*PopupPad + Fontsz, p.h);
|
|
||||||
|
|
||||||
/* Candidates only. */
|
|
||||||
memset(&dc, 0, sizeof dc);
|
|
||||||
dc.sel = -1;
|
|
||||||
dc.nkouho = 2;
|
|
||||||
dc.kouho[0] = mkstr("short");
|
|
||||||
dc.kouho[1] = mkstr("candidate");
|
|
||||||
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
|
|
||||||
CT_EQ_INT(t, PopupBasew, p.w);
|
|
||||||
CT_EQ_INT(t, -1, p.prey);
|
|
||||||
CT_EQ_INT(t, -1, p.sepy);
|
|
||||||
CT_EQ_INT(t, PopupPad, p.rowsy);
|
|
||||||
CT_EQ_INT(t, -1, p.marky);
|
|
||||||
CT_EQ_INT(t, 2*PopupPad + 2*Fontsz, p.h);
|
|
||||||
CT_EQ_INT(t, PopupTextw, p.textw);
|
|
||||||
CT_EQ_INT(t, p.w - PopupPad, PopupPad + PopupNumw + p.textw);
|
|
||||||
|
|
||||||
/* Preedit and candidates meet at one separator, with no empty rows. */
|
|
||||||
dc.pre = mkstr("preedit");
|
|
||||||
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
|
|
||||||
CT_EQ_INT(t, PopupBasew, p.w);
|
|
||||||
CT_EQ_INT(t, PopupPad, p.prey);
|
|
||||||
CT_EQ_INT(t, p.prey + Fontsz, p.sepy);
|
|
||||||
CT_EQ_INT(t, p.sepy + PopupSep, p.rowsy);
|
|
||||||
CT_EQ_INT(t, -1, p.marky);
|
|
||||||
CT_EQ_INT(t, p.rowsy + 2*Fontsz + PopupPad, p.h);
|
|
||||||
CT_EQ_INT(t, 2*PopupPad + 3*Fontsz + PopupSep, p.h);
|
|
||||||
|
|
||||||
/* Long preedit expands to the monitor cap just like a candidate. */
|
|
||||||
sclear(&longrow);
|
|
||||||
for(i = 0; i < Maxrunes; i++)
|
|
||||||
sputr(&longrow, 'W');
|
|
||||||
memset(&dc, 0, sizeof dc);
|
|
||||||
dc.pre = longrow;
|
|
||||||
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
|
|
||||||
CT_EQ_INT(t, 2*PopupBasew, p.w);
|
|
||||||
|
|
||||||
/* The largest panel includes all sections and fits its backing image. */
|
|
||||||
memset(&dc, 0, sizeof dc);
|
|
||||||
dc.pre = mkstr("preedit");
|
|
||||||
dc.nkouho = Maxdisp;
|
|
||||||
dc.sel = 4;
|
|
||||||
dc.first = 0;
|
|
||||||
dc.total = Maxdisp + 1;
|
|
||||||
for(i = 0; i < Maxdisp; i++)
|
|
||||||
dc.kouho[i] = mkstr(i == dc.sel ? "M" : "candidate");
|
|
||||||
dc.kouho[0] = longrow;
|
|
||||||
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
|
|
||||||
CT_EQ_INT(t, Maxdisp, p.n);
|
|
||||||
CT_EQ_INT(t, PopupPad, p.prey);
|
|
||||||
CT_EQ_INT(t, p.prey + Fontsz, p.sepy);
|
|
||||||
CT_EQ_INT(t, p.sepy + PopupSep, p.rowsy);
|
|
||||||
CT_EQ_INT(t, p.rowsy + Maxdisp*Fontsz, p.marky);
|
|
||||||
CT_EQ_INT(t, p.marky + Fontsz + PopupPad, p.h);
|
|
||||||
CT_EQ_INT(t, Imgh, p.h);
|
|
||||||
CT_EQ_INT(t, 2*PopupBasew, p.w);
|
|
||||||
CT_CHECK(t, p.h <= Imgh);
|
|
||||||
CT_EQ_INT(t, p.w - PopupPad, p.markx + p.markw);
|
|
||||||
CT_EQ_INT(t, p.w - 2*PopupPad, p.selw);
|
|
||||||
CT_EQ_INT(t, p.rowsy + dc.sel*Fontsz, p.sely);
|
|
||||||
CT_EQ_INT(t, p.w - PopupPad, PopupPad + PopupNumw + p.textw);
|
|
||||||
|
|
||||||
img = emalloc(2*PopupBasew*Imgh*sizeof img[0]);
|
|
||||||
popupdraw(img, &dc, &p);
|
|
||||||
checkpadding(t, img, &p);
|
|
||||||
for(x = 0; x < p.w; x++)
|
|
||||||
CT_EQ_UINT(t,
|
|
||||||
x >= PopupPad && x < p.w - PopupPad ? Colsep : Colbg,
|
|
||||||
pixel(img, &p, x, p.sepy));
|
|
||||||
|
|
||||||
/* Selection paint and both foreground columns stay inside the row. */
|
|
||||||
selbg = numink = textink = 0;
|
|
||||||
for(y = p.sely; y < p.sely + Fontsz; y++){
|
|
||||||
for(x = 0; x < p.w; x++){
|
|
||||||
c = pixel(img, &p, x, y);
|
|
||||||
if(x < PopupPad || x >= PopupPad + p.selw){
|
|
||||||
CT_EQ_UINT(t, Colbg, c);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(c == (Colsel & Rgbmask)){
|
|
||||||
selbg++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
CT_CHECK(t,
|
|
||||||
(x >= PopupPad && x < PopupPad + PopupNumw) ||
|
|
||||||
(x >= (PopupPad + PopupNumw) && x < (PopupPad + PopupNumw) + p.textw));
|
|
||||||
if(x < PopupPad + PopupNumw)
|
|
||||||
numink++;
|
|
||||||
else
|
|
||||||
textink++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CT_CHECK(t, Colsel != Colselfg);
|
|
||||||
CT_CHECK(t, selbg > p.selw*Fontsz/2);
|
|
||||||
CT_CHECK(t, numink > 0);
|
|
||||||
CT_CHECK(t, textink > 0);
|
|
||||||
|
|
||||||
/* The marker occupies only its compact, right-aligned footer box. */
|
|
||||||
markink = 0;
|
|
||||||
for(y = p.marky; y < p.marky + Fontsz; y++){
|
|
||||||
for(x = 0; x < p.w; x++){
|
|
||||||
if(pixel(img, &p, x, y) == (Colbg & Rgbmask))
|
|
||||||
continue;
|
|
||||||
markink++;
|
|
||||||
CT_CHECK(t, x >= p.markx && x < p.markx + p.markw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CT_CHECK(t, markink > 0);
|
|
||||||
|
|
||||||
/* A long candidate grows from the stable base to the monitor cap. */
|
|
||||||
memset(&dc, 0, sizeof dc);
|
|
||||||
dc.sel = -1;
|
|
||||||
dc.nkouho = 1;
|
|
||||||
dc.kouho[0] = longrow;
|
|
||||||
n = textwidth(&longrow);
|
|
||||||
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
|
|
||||||
CT_EQ_INT(t, 2*PopupBasew, p.w);
|
|
||||||
CT_CHECK(t, p.textw > PopupTextw);
|
|
||||||
CT_CHECK(t, n > p.textw);
|
|
||||||
CT_EQ_INT(t, PopupPad, p.rowsy);
|
|
||||||
CT_EQ_INT(t, p.w - PopupPad, PopupPad + PopupNumw + p.textw);
|
|
||||||
popupdraw(img, &dc, &p);
|
|
||||||
ink = 0;
|
|
||||||
for(y = p.rowsy; y < p.rowsy + Fontsz; y++){
|
|
||||||
for(x = 0; x < p.w; x++){
|
|
||||||
c = pixel(img, &p, x, y);
|
|
||||||
if(x < PopupPad || x >= p.w - PopupPad)
|
|
||||||
CT_EQ_UINT(t, Colbg, c);
|
|
||||||
if(x >= (PopupPad + PopupNumw) && x < (PopupPad + PopupNumw) + p.textw &&
|
|
||||||
c != (Colbg & Rgbmask))
|
|
||||||
ink++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CT_CHECK(t, ink > 0);
|
|
||||||
CT_EQ_INT(t, n, textwidth(&longrow));
|
|
||||||
|
|
||||||
popuplayout(&dc, PopupBasew - 1, Imgh, &p);
|
|
||||||
CT_EQ_INT(t, PopupBasew - 1, p.w);
|
|
||||||
|
|
||||||
/* Height truncation scrolls the row window to the selected candidate. */
|
|
||||||
memset(&dc, 0, sizeof dc);
|
|
||||||
dc.nkouho = Maxdisp;
|
|
||||||
dc.sel = Maxdisp - 1;
|
|
||||||
dc.total = Maxdisp;
|
|
||||||
for(i = 0; i < Maxdisp; i++)
|
|
||||||
dc.kouho[i] = mkstr("candidate");
|
|
||||||
popuplayout(&dc, PopupBasew, 2*PopupPad + 2*Fontsz, &p);
|
|
||||||
CT_EQ_INT(t, 1, p.n);
|
|
||||||
CT_EQ_INT(t, Maxdisp - 1, p.row0);
|
|
||||||
CT_EQ_INT(t, PopupPad, p.sely);
|
|
||||||
CT_CHECK(t, p.marky >= 0);
|
|
||||||
CT_CHECK(t, p.h <= 2*PopupPad + 2*Fontsz);
|
|
||||||
popupdraw(img, &dc, &p);
|
|
||||||
numink = 0;
|
|
||||||
for(y = p.rowsy; y < p.rowsy + Fontsz; y++)
|
|
||||||
for(x = PopupPad; x < PopupPad + PopupNumw; x++)
|
|
||||||
if(pixel(img, &p, x, y) != (Colsel & Rgbmask))
|
|
||||||
numink++;
|
|
||||||
markink = 0;
|
|
||||||
for(y = p.marky; y < p.marky + Fontsz; y++)
|
|
||||||
for(x = p.markx; x < p.markx + p.markw; x++)
|
|
||||||
if(pixel(img, &p, x, y) != (Colbg & Rgbmask))
|
|
||||||
markink++;
|
|
||||||
CT_CHECK(t, numink > 0); /* full-width 9 */
|
|
||||||
CT_CHECK(t, markink > 0); /* 9-9/9 */
|
|
||||||
|
|
||||||
free(img);
|
|
||||||
}
|
|
||||||
@@ -1,549 +0,0 @@
|
|||||||
#include <errno.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "srv.c"
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
#undef recv
|
|
||||||
|
|
||||||
typedef struct Serverfix Serverfix;
|
|
||||||
typedef struct Testclient Testclient;
|
|
||||||
|
|
||||||
/* A held pump plus a private client-slot channel of the wanted depth. */
|
|
||||||
struct Serverfix
|
|
||||||
{
|
|
||||||
Pump pump;
|
|
||||||
Channel *oldclientc;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Testclient
|
|
||||||
{
|
|
||||||
int fd;
|
|
||||||
int peer;
|
|
||||||
dev_t dev;
|
|
||||||
ino_t ino;
|
|
||||||
Channel *done;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
serverbegin(Serverfix *f, int nclients)
|
|
||||||
{
|
|
||||||
Drawcmd dc;
|
|
||||||
uchar token;
|
|
||||||
|
|
||||||
f->oldclientc = clientc;
|
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
|
||||||
;
|
|
||||||
clientc = chancreate(sizeof token, nclients);
|
|
||||||
testengineinit(LangJP);
|
|
||||||
pumpstart(&f->pump, 0);
|
|
||||||
pumphold(&f->pump, Pumpall);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
serverend(Serverfix *f)
|
|
||||||
{
|
|
||||||
Drawcmd dc;
|
|
||||||
|
|
||||||
pumpstop(&f->pump);
|
|
||||||
chanfree(clientc);
|
|
||||||
clientc = f->oldclientc;
|
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
clientproc(void *arg)
|
|
||||||
{
|
|
||||||
Testclient *client;
|
|
||||||
uchar token;
|
|
||||||
|
|
||||||
client = arg;
|
|
||||||
clientthread((void*)(uintptr)client->fd);
|
|
||||||
token = 0;
|
|
||||||
chansend(client->done, &token);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
startclient(struct ct *t, Testclient *client)
|
|
||||||
{
|
|
||||||
struct stat st;
|
|
||||||
int fd[2];
|
|
||||||
uchar token;
|
|
||||||
|
|
||||||
memset(client, 0, sizeof *client);
|
|
||||||
client->fd = -1;
|
|
||||||
client->peer = -1;
|
|
||||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0)
|
|
||||||
return CT_ERRORF(t, "socketpair failed: %s", strerror(errno));
|
|
||||||
client->fd = fd[0];
|
|
||||||
client->peer = fd[1];
|
|
||||||
if(fstat(client->fd, &st) < 0){
|
|
||||||
close(client->fd);
|
|
||||||
close(client->peer);
|
|
||||||
client->fd = -1;
|
|
||||||
client->peer = -1;
|
|
||||||
return CT_ERRORF(t, "socket stat failed: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
client->dev = st.st_dev;
|
|
||||||
client->ino = st.st_ino;
|
|
||||||
client->done = chancreate(sizeof token, 0);
|
|
||||||
token = 0;
|
|
||||||
if(channbsend(clientc, &token) <= 0){
|
|
||||||
close(client->fd);
|
|
||||||
close(client->peer);
|
|
||||||
chanfree(client->done);
|
|
||||||
client->fd = -1;
|
|
||||||
client->peer = -1;
|
|
||||||
client->done = nil;
|
|
||||||
return CT_ERRORF(t, "client slot unavailable");
|
|
||||||
}
|
|
||||||
if(proccreate(clientproc, client, 8192) < 0){
|
|
||||||
chanrecv(clientc, &token);
|
|
||||||
close(client->fd);
|
|
||||||
close(client->peer);
|
|
||||||
chanfree(client->done);
|
|
||||||
client->fd = -1;
|
|
||||||
client->peer = -1;
|
|
||||||
client->done = nil;
|
|
||||||
return CT_ERRORF(t, "client worker creation failed");
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
sendkey(struct ct *t, Testclient *client, int want, u32int mod, u32int key)
|
|
||||||
{
|
|
||||||
uchar req[Ipcreqsz];
|
|
||||||
|
|
||||||
ipcpackreq(req, want, mod, key);
|
|
||||||
if(ipcsend(client->peer, req, sizeof req) == 0)
|
|
||||||
return 1;
|
|
||||||
return CT_ERRORF(t, "request send failed: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
sendreset(struct ct *t, Testclient *client, int want)
|
|
||||||
{
|
|
||||||
uchar req[Ipcreqsz];
|
|
||||||
|
|
||||||
ipcpackreset(req, want);
|
|
||||||
if(ipcsend(client->peer, req, sizeof req) == 0)
|
|
||||||
return 1;
|
|
||||||
return CT_ERRORF(t, "reset send failed: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Keyreq
|
|
||||||
nextrequestcap(struct ct *t, Pump *p, int op, int cap)
|
|
||||||
{
|
|
||||||
Keyreq req;
|
|
||||||
|
|
||||||
memset(&req, 0, sizeof req);
|
|
||||||
chanrecv(p->trace, &req);
|
|
||||||
CT_EQ_INT(t, op, req.op);
|
|
||||||
CT_EQ_INT(t, cap, req.clientpre);
|
|
||||||
return req;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Keyreq
|
|
||||||
nextrequest(struct ct *t, Pump *p, int op)
|
|
||||||
{
|
|
||||||
return nextrequestcap(t, p, op, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
allowrequest(Pump *p)
|
|
||||||
{
|
|
||||||
uchar token;
|
|
||||||
|
|
||||||
token = 0;
|
|
||||||
chansend(p->go, &token);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
readreply(struct ct *t, Testclient *client, int want, char *wantcommit,
|
|
||||||
char *preedit)
|
|
||||||
{
|
|
||||||
char commit[Ipcfieldmax+1];
|
|
||||||
Ipcresp resp;
|
|
||||||
|
|
||||||
if(ipcreadresp(client->peer, want, commit, preedit, &resp) < 0)
|
|
||||||
return CT_ERRORF(t, "response read failed: %s", strerror(errno));
|
|
||||||
CT_EQ_STR(t, wantcommit, commit);
|
|
||||||
return resp.eaten;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
sendframe(struct ct *t, Testclient *client, uchar *frame, int n, int fragment)
|
|
||||||
{
|
|
||||||
int i, m;
|
|
||||||
|
|
||||||
for(i = 0; i < n; i += m){
|
|
||||||
m = fragment && n-i > 1 ? 1 : n-i;
|
|
||||||
if(ipcsend(client->peer, frame+i, m) < 0)
|
|
||||||
return CT_ERRORF(t, "frame send failed: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
checknoreply(struct ct *t, Testclient *client)
|
|
||||||
{
|
|
||||||
uchar byte;
|
|
||||||
ssize_t n;
|
|
||||||
|
|
||||||
errno = 0;
|
|
||||||
n = recv(client->peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
|
|
||||||
CT_EQ_INT(t, -1, n);
|
|
||||||
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
waitclient(struct ct *t, Testclient *client)
|
|
||||||
{
|
|
||||||
struct stat st;
|
|
||||||
uchar token;
|
|
||||||
|
|
||||||
chanrecv(client->done, &token);
|
|
||||||
errno = 0;
|
|
||||||
if(fstat(client->fd, &st) == 0)
|
|
||||||
CT_CHECK(t, st.st_dev != client->dev || st.st_ino != client->ino);
|
|
||||||
else
|
|
||||||
CT_EQ_INT(t, EBADF, errno);
|
|
||||||
chanfree(client->done);
|
|
||||||
client->done = nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
disconnectclient(struct ct *t, Pump *p, Testclient *client, void *owner)
|
|
||||||
{
|
|
||||||
Keyreq req;
|
|
||||||
uchar token;
|
|
||||||
|
|
||||||
close(client->peer);
|
|
||||||
client->peer = -1;
|
|
||||||
req = nextrequest(t, p, Keyrelease);
|
|
||||||
if(owner != nil)
|
|
||||||
CT_EQ_PTR(t, owner, req.owner);
|
|
||||||
CT_CHECK(t, channbrecv(client->done, &token) <= 0);
|
|
||||||
allowrequest(p);
|
|
||||||
waitclient(t, client);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
server_connection_ownership(struct ct *t)
|
|
||||||
{
|
|
||||||
Serverfix f;
|
|
||||||
Testclient a, b, c;
|
|
||||||
Keyreq req;
|
|
||||||
Str shown;
|
|
||||||
char preedit[Ipcfieldmax+1];
|
|
||||||
void *aowner, *bowner, *cowner;
|
|
||||||
uchar byte, token;
|
|
||||||
ssize_t n;
|
|
||||||
|
|
||||||
memset(&a, 0, sizeof a);
|
|
||||||
memset(&b, 0, sizeof b);
|
|
||||||
memset(&c, 0, sizeof c);
|
|
||||||
a.fd = a.peer = b.fd = b.peer = c.fd = c.peer = -1;
|
|
||||||
aowner = bowner = cowner = nil;
|
|
||||||
serverbegin(&f, 3);
|
|
||||||
if(!startclient(t, &a) || !startclient(t, &b))
|
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
if(!sendkey(t, &a, 1, 0, 'k'))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keypress);
|
|
||||||
aowner = req.owner;
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
|
|
||||||
CT_EQ_STR(t, "k", preedit);
|
|
||||||
if(!sendkey(t, &a, 1, 0, 'a'))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keypress);
|
|
||||||
CT_EQ_PTR(t, aowner, req.owner);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
|
|
||||||
CT_EQ_STR(t, "か", preedit);
|
|
||||||
CT_EQ_PTR(t, aowner, testengineowner());
|
|
||||||
|
|
||||||
if(!sendkey(t, &b, 1, 0, 'n'))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keypress);
|
|
||||||
bowner = req.owner;
|
|
||||||
CT_CHECK(t, bowner != aowner);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
|
||||||
CT_EQ_STR(t, "ん", preedit);
|
|
||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
|
||||||
|
|
||||||
if(!sendreset(t, &a, 1))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keyreset);
|
|
||||||
CT_EQ_PTR(t, aowner, req.owner);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
/* The reset hands back what the takeover took from this client. */
|
|
||||||
CT_CHECK(t, readreply(t, &a, 1, "か", preedit));
|
|
||||||
CT_EQ_STR(t, "", preedit);
|
|
||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
|
||||||
if(!sendkey(t, &b, 1, 0, 0))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keypress);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_CHECK(t, !readreply(t, &b, 1, "", preedit));
|
|
||||||
CT_EQ_STR(t, "ん", preedit);
|
|
||||||
|
|
||||||
disconnectclient(t, &f.pump, &a, aowner);
|
|
||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
|
||||||
if(!sendkey(t, &b, 1, 0, 'y'))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keypress);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
|
||||||
if(!sendkey(t, &b, 1, 0, 'a'))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keypress);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
|
||||||
CT_EQ_STR(t, "にゃ", preedit);
|
|
||||||
|
|
||||||
if(!sendreset(t, &b, 1))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keyreset);
|
|
||||||
CT_EQ_PTR(t, bowner, req.owner);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_CHECK(t, readreply(t, &b, 1, "にゃ", preedit));
|
|
||||||
CT_EQ_STR(t, "", preedit);
|
|
||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
|
||||||
|
|
||||||
if(!sendkey(t, &b, 0, 0, 'k'))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequestcap(t, &f.pump, Keypress, 0);
|
|
||||||
CT_EQ_PTR(t, bowner, req.owner);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_CHECK(t, readreply(t, &b, 0, "", preedit));
|
|
||||||
errno = 0;
|
|
||||||
n = recv(b.peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
|
|
||||||
CT_EQ_INT(t, -1, n);
|
|
||||||
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
|
|
||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
|
||||||
if(!sendkey(t, &b, 1, 0, 'a'))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keypress);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
|
||||||
CT_EQ_STR(t, "か", preedit);
|
|
||||||
|
|
||||||
disconnectclient(t, &f.pump, &b, bowner);
|
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
|
||||||
testenginepreedit(&shown);
|
|
||||||
CT_EQ_INT(t, 0, shown.n);
|
|
||||||
|
|
||||||
if(!startclient(t, &c))
|
|
||||||
goto cleanup;
|
|
||||||
if(!sendkey(t, &c, 1, 0, 'k'))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keypress);
|
|
||||||
cowner = req.owner;
|
|
||||||
close(c.peer);
|
|
||||||
c.peer = -1;
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
req = nextrequest(t, &f.pump, Keyrelease);
|
|
||||||
CT_EQ_PTR(t, cowner, req.owner);
|
|
||||||
CT_EQ_PTR(t, cowner, testengineowner());
|
|
||||||
CT_CHECK(t, channbrecv(c.done, &token) <= 0);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
waitclient(t, &c);
|
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
|
||||||
CT_CHECK(t, channbrecv(clientc, &token) <= 0);
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
if(a.peer >= 0)
|
|
||||||
disconnectclient(t, &f.pump, &a, aowner);
|
|
||||||
if(b.peer >= 0)
|
|
||||||
disconnectclient(t, &f.pump, &b, bowner);
|
|
||||||
if(c.peer >= 0)
|
|
||||||
disconnectclient(t, &f.pump, &c, cowner);
|
|
||||||
serverend(&f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
server_extension_stream(struct ct *t)
|
|
||||||
{
|
|
||||||
Serverfix f;
|
|
||||||
Testclient a, b;
|
|
||||||
Keyreq req;
|
|
||||||
uchar frame[Ipccaretsz];
|
|
||||||
char preedit[Ipcfieldmax+1];
|
|
||||||
void *aowner, *bowner;
|
|
||||||
|
|
||||||
memset(&a, 0, sizeof a);
|
|
||||||
memset(&b, 0, sizeof b);
|
|
||||||
a.fd = a.peer = b.fd = b.peer = -1;
|
|
||||||
aowner = bowner = nil;
|
|
||||||
serverbegin(&f, 2);
|
|
||||||
if(!startclient(t, &a))
|
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
/* Capability and caret frames may be split at any byte boundary. */
|
|
||||||
ipcpackcap(frame, 1);
|
|
||||||
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keycap);
|
|
||||||
aowner = req.owner;
|
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
|
||||||
CT_EQ_STR(t, "", preedit);
|
|
||||||
|
|
||||||
ipcpackcaret(frame, 1, -101, -7, 23);
|
|
||||||
if(!sendframe(t, &a, frame, Ipccaretsz, 1))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keycaret);
|
|
||||||
CT_EQ_PTR(t, aowner, req.owner);
|
|
||||||
CT_EQ_INT(t, 1, req.caret.valid);
|
|
||||||
CT_EQ_INT(t, -101, req.caret.x);
|
|
||||||
CT_EQ_INT(t, -7, req.caret.y);
|
|
||||||
CT_EQ_INT(t, 23, req.caret.h);
|
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
checknoreply(t, &a);
|
|
||||||
|
|
||||||
ipcpackreq(frame, 1, 0, 'k');
|
|
||||||
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keypress);
|
|
||||||
CT_EQ_PTR(t, aowner, req.owner);
|
|
||||||
CT_EQ_INT(t, 1, req.caret.valid);
|
|
||||||
CT_EQ_INT(t, -101, req.caret.x);
|
|
||||||
CT_EQ_INT(t, -7, req.caret.y);
|
|
||||||
CT_EQ_INT(t, 23, req.caret.h);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
|
||||||
CT_EQ_STR(t, "k", preedit);
|
|
||||||
CT_EQ_PTR(t, aowner, testengineowner());
|
|
||||||
|
|
||||||
ipcpackcap(frame, 0);
|
|
||||||
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequestcap(t, &f.pump, Keycap, 0);
|
|
||||||
CT_EQ_PTR(t, aowner, req.owner);
|
|
||||||
CT_EQ_INT(t, 1, req.caret.valid);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_EQ_INT(t, 1, readreply(t, &a, 0, "", preedit));
|
|
||||||
|
|
||||||
/* Reset is still the old six-byte frame and does not discard caret. */
|
|
||||||
ipcpackreset(frame, 0);
|
|
||||||
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequestcap(t, &f.pump, Keyreset, 0);
|
|
||||||
CT_EQ_PTR(t, aowner, req.owner);
|
|
||||||
CT_EQ_INT(t, 1, req.caret.valid);
|
|
||||||
CT_EQ_INT(t, -101, req.caret.x);
|
|
||||||
CT_EQ_INT(t, -7, req.caret.y);
|
|
||||||
CT_EQ_INT(t, 23, req.caret.h);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_EQ_INT(t, 1, readreply(t, &a, 0, "k", preedit));
|
|
||||||
|
|
||||||
ipcpackcap(frame, 1);
|
|
||||||
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keycap);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
|
||||||
CT_EQ_STR(t, "", preedit);
|
|
||||||
ipcpackreq(frame, 1, 0, 'n');
|
|
||||||
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keypress);
|
|
||||||
CT_EQ_INT(t, 1, req.caret.valid);
|
|
||||||
CT_EQ_INT(t, -101, req.caret.x);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
|
||||||
CT_EQ_STR(t, "ん", preedit);
|
|
||||||
|
|
||||||
disconnectclient(t, &f.pump, &a, aowner);
|
|
||||||
|
|
||||||
/* Disconnect drops state; a legacy client gets the historical defaults. */
|
|
||||||
if(!startclient(t, &b) || !sendkey(t, &b, 1, 0, 'k'))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keypress);
|
|
||||||
bowner = req.owner;
|
|
||||||
CT_EQ_INT(t, 0, req.caret.valid);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_EQ_INT(t, 1, readreply(t, &b, 1, "", preedit));
|
|
||||||
CT_EQ_STR(t, "k", preedit);
|
|
||||||
|
|
||||||
disconnectclient(t, &f.pump, &b, bowner);
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
if(a.peer >= 0)
|
|
||||||
disconnectclient(t, &f.pump, &a, aowner);
|
|
||||||
if(b.peer >= 0)
|
|
||||||
disconnectclient(t, &f.pump, &b, bowner);
|
|
||||||
serverend(&f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
server_rejects_unknown_extension(struct ct *t)
|
|
||||||
{
|
|
||||||
Serverfix f;
|
|
||||||
Testclient badver, badop, good;
|
|
||||||
Keyreq req;
|
|
||||||
uchar frame[Ipccaretsz];
|
|
||||||
char preedit[Ipcfieldmax+1];
|
|
||||||
void *owner;
|
|
||||||
|
|
||||||
memset(&badver, 0, sizeof badver);
|
|
||||||
memset(&badop, 0, sizeof badop);
|
|
||||||
memset(&good, 0, sizeof good);
|
|
||||||
badver.fd = badver.peer = badop.fd = badop.peer = -1;
|
|
||||||
good.fd = good.peer = -1;
|
|
||||||
owner = nil;
|
|
||||||
serverbegin(&f, 3);
|
|
||||||
|
|
||||||
if(!startclient(t, &badver))
|
|
||||||
goto cleanup;
|
|
||||||
ipcpackcaret(frame, 1, 3, 4, 5);
|
|
||||||
frame[1]++;
|
|
||||||
if(!sendframe(t, &badver, frame, sizeof frame, 0))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keyrelease);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
waitclient(t, &badver);
|
|
||||||
close(badver.peer);
|
|
||||||
badver.peer = -1;
|
|
||||||
|
|
||||||
if(!startclient(t, &badop))
|
|
||||||
goto cleanup;
|
|
||||||
ipcpackcaret(frame, 1, 3, 4, 5);
|
|
||||||
frame[2]++;
|
|
||||||
if(!sendframe(t, &badop, frame, sizeof frame, 1))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keyrelease);
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
waitclient(t, &badop);
|
|
||||||
close(badop.peer);
|
|
||||||
badop.peer = -1;
|
|
||||||
|
|
||||||
/* A malformed peer must not disturb another client connection. */
|
|
||||||
if(!startclient(t, &good) || !sendkey(t, &good, 1, 0, 'k'))
|
|
||||||
goto cleanup;
|
|
||||||
req = nextrequest(t, &f.pump, Keypress);
|
|
||||||
owner = req.owner;
|
|
||||||
allowrequest(&f.pump);
|
|
||||||
CT_EQ_INT(t, 1, readreply(t, &good, 1, "", preedit));
|
|
||||||
CT_EQ_STR(t, "k", preedit);
|
|
||||||
disconnectclient(t, &f.pump, &good, owner);
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
if(badver.peer >= 0)
|
|
||||||
disconnectclient(t, &f.pump, &badver, nil);
|
|
||||||
if(badop.peer >= 0)
|
|
||||||
disconnectclient(t, &f.pump, &badop, nil);
|
|
||||||
if(good.peer >= 0)
|
|
||||||
disconnectclient(t, &f.pump, &good, owner);
|
|
||||||
serverend(&f);
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
TIMEOUT = 10
|
|
||||||
|
|
||||||
|
|
||||||
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,
|
|
||||||
timeout=TIMEOUT)
|
|
||||||
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,
|
|
||||||
timeout=TIMEOUT)
|
|
||||||
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,
|
|
||||||
timeout=TIMEOUT)
|
|
||||||
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,
|
|
||||||
timeout=TIMEOUT)
|
|
||||||
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,
|
|
||||||
timeout=TIMEOUT)
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr.decode())
|
|
||||||
self.assertEqual(result.stdout, b"a\tA\nb\tB\n")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
124
tests/str_test.c
124
tests/str_test.c
@@ -1,124 +0,0 @@
|
|||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
void
|
|
||||||
str_init_utf8(struct ct *t)
|
|
||||||
{
|
|
||||||
static char badtail[] = { 'a', (char)0x80 };
|
|
||||||
static char incomplete[] = { (char)0xea, (char)0xb0 };
|
|
||||||
static char overlong[] = { (char)0xc0, (char)0xaf };
|
|
||||||
static char outofrange[] = {
|
|
||||||
(char)0xf4, (char)0x90, (char)0x80, (char)0x80,
|
|
||||||
};
|
|
||||||
static char surrogate[] = {
|
|
||||||
(char)0xed, (char)0xa0, (char)0x80,
|
|
||||||
};
|
|
||||||
static char unexpected[] = { (char)0x80 };
|
|
||||||
static const struct {
|
|
||||||
char *name;
|
|
||||||
char *src;
|
|
||||||
int n;
|
|
||||||
int ok;
|
|
||||||
char *want;
|
|
||||||
} cases[] = {
|
|
||||||
{ "canonical Runeerror", "\xef\xbf\xbd", 3, 1, "\xef\xbf\xbd" },
|
|
||||||
{ "incomplete", incomplete, sizeof incomplete, 0, "" },
|
|
||||||
{ "unexpected continuation", unexpected, sizeof unexpected, 0, "" },
|
|
||||||
{ "overlong", overlong, sizeof overlong, 0, "" },
|
|
||||||
{ "surrogate", surrogate, sizeof surrogate, 0, "" },
|
|
||||||
{ "out of range", outofrange, sizeof outofrange, 0, "" },
|
|
||||||
{ "bad tail", badtail, sizeof badtail, 0, "" },
|
|
||||||
};
|
|
||||||
char full[Maxrunes+2];
|
|
||||||
Str s;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
s = mkstr("A한😀");
|
|
||||||
CT_EQ_INT(t, 3, s.n);
|
|
||||||
checkstr(t, "round trip", "A한😀", &s);
|
|
||||||
for(i = 0; i < nelem(cases); i++){
|
|
||||||
memset(&s, 0xa5, sizeof s);
|
|
||||||
if(sinit(&s, cases[i].src, cases[i].n) != cases[i].ok)
|
|
||||||
CT_ERRORF(t, "%s: wrong validity", cases[i].name);
|
|
||||||
checkstr(t, cases[i].name, cases[i].want, &s);
|
|
||||||
}
|
|
||||||
memset(full, 'a', sizeof full);
|
|
||||||
full[sizeof full-1] = '\0';
|
|
||||||
CT_CHECK(t, !sinit(&s, full, strlen(full)));
|
|
||||||
CT_EQ_INT(t, 0, s.n);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
str_edit_and_alias(struct ct *t)
|
|
||||||
{
|
|
||||||
Str s;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
s = mkstr("ab");
|
|
||||||
sappend(&s, &s);
|
|
||||||
checkstr(t, "self append", "abab", &s);
|
|
||||||
for(i = 0; i < 40; i++)
|
|
||||||
s.r[i] = 'a' + i % 26;
|
|
||||||
s.n = 40;
|
|
||||||
sappend(&s, &s);
|
|
||||||
CT_EQ_INT(t, Maxrunes, s.n);
|
|
||||||
for(i = 0; i < 24; i++)
|
|
||||||
if(s.r[40+i] != s.r[i])
|
|
||||||
CT_ERRORF(t, "capped self append differs at rune %d", i);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
str_utf8_capacity(struct ct *t)
|
|
||||||
{
|
|
||||||
static const struct {
|
|
||||||
int size;
|
|
||||||
int n;
|
|
||||||
char *want;
|
|
||||||
} cases[] = {
|
|
||||||
{ 1, 0, "" },
|
|
||||||
{ 2, 1, "a" },
|
|
||||||
{ 3, 1, "a" },
|
|
||||||
{ 4, 1, "a" },
|
|
||||||
{ 5, 4, "a한" },
|
|
||||||
{ 6, 5, "a한b" },
|
|
||||||
};
|
|
||||||
char buf[16];
|
|
||||||
Str s;
|
|
||||||
int i, n;
|
|
||||||
|
|
||||||
s = mkstr("a한b");
|
|
||||||
for(i = 0; i < nelem(cases); i++){
|
|
||||||
memset(buf, 'Z', sizeof buf);
|
|
||||||
n = stoutf(&s, buf, cases[i].size);
|
|
||||||
if(n != cases[i].n)
|
|
||||||
CT_ERRORF(t, "size %d: want length %d, got %d",
|
|
||||||
cases[i].size, cases[i].n, n);
|
|
||||||
if(strcmp(cases[i].want, buf) != 0)
|
|
||||||
CT_ERRORF(t, "size %d: want \"%s\", got \"%s\"",
|
|
||||||
cases[i].size, cases[i].want, buf);
|
|
||||||
CT_EQ_INT(t, 'Z', buf[cases[i].size]);
|
|
||||||
}
|
|
||||||
|
|
||||||
s = mkstr("😀");
|
|
||||||
memset(buf, 'Z', sizeof buf);
|
|
||||||
CT_EQ_INT(t, 0, stoutf(&s, buf, 4));
|
|
||||||
CT_EQ_STR(t, "", buf);
|
|
||||||
CT_EQ_INT(t, 4, stoutf(&s, buf, 5));
|
|
||||||
CT_EQ_STR(t, "😀", buf);
|
|
||||||
CT_EQ_INT(t, 'Z', buf[5]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
str_invalid_and_full_appends(struct ct *t)
|
|
||||||
{
|
|
||||||
Str s;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
133
tests/test.h
133
tests/test.h
@@ -1,133 +0,0 @@
|
|||||||
/* Included after dat.h and fn.h (or after the source under test). */
|
|
||||||
#include "cutest/cutest.h"
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A test's stand-in for imthread: handles engine requests from keyc,
|
|
||||||
* tracing each one first. Requests whose op is holdop (every request
|
|
||||||
* when holdop is Pumpall) wait on go before the engine sees them.
|
|
||||||
*/
|
|
||||||
typedef struct Pump Pump;
|
|
||||||
struct Pump
|
|
||||||
{
|
|
||||||
Channel *trace;
|
|
||||||
Channel *go;
|
|
||||||
Channel *stop;
|
|
||||||
Channel *done;
|
|
||||||
int holdop;
|
|
||||||
int active;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Pumpnone = -1,
|
|
||||||
Pumpall = -2,
|
|
||||||
};
|
|
||||||
|
|
||||||
Str mkstr(char*);
|
|
||||||
int checkstr(struct ct*, char*, char*, Str*);
|
|
||||||
void checkenginepreedit(struct ct*, char*);
|
|
||||||
void pumpstart(Pump*, int);
|
|
||||||
void pumphold(Pump*, int);
|
|
||||||
void pumpstop(Pump*);
|
|
||||||
|
|
||||||
/* Engine internals reached through engine_test.c. */
|
|
||||||
void testengineinit(int);
|
|
||||||
void testenginehandle(Keyreq*);
|
|
||||||
void* testengineowner(void);
|
|
||||||
void testenginepreedit(Str*);
|
|
||||||
void testenginecaret(Caret*);
|
|
||||||
|
|
||||||
void str_init_utf8(struct ct*);
|
|
||||||
void str_edit_and_alias(struct ct*);
|
|
||||||
void str_utf8_capacity(struct ct*);
|
|
||||||
void str_invalid_and_full_appends(struct ct*);
|
|
||||||
void trie_exact_prefix_and_duplicate(struct ct*);
|
|
||||||
void trie_put_and_unloaded(struct ct*);
|
|
||||||
void popup_layout(struct ct*);
|
|
||||||
void font_render(struct ct*);
|
|
||||||
void production_maps_load(struct ct*);
|
|
||||||
void transmap_states(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 vietnamese_transitions(struct ct*);
|
|
||||||
void vietnamese_backspace(struct ct*);
|
|
||||||
void vietnamese_state_lifetime(struct ct*);
|
|
||||||
void engine_clears_candidates(struct ct*);
|
|
||||||
void engine_backspace_clears_candidates(struct ct*);
|
|
||||||
void engine_selects_visible_candidate(struct ct*);
|
|
||||||
void engine_candidate_shortcut_modifiers(struct ct*);
|
|
||||||
void engine_candidate_page_metadata(struct ct*);
|
|
||||||
void engine_candidate_page_snapshots(struct ct*);
|
|
||||||
void engine_candidate_page_movement(struct ct*);
|
|
||||||
void engine_candidate_completion(struct ct*);
|
|
||||||
void engine_active_owner_lifecycle(struct ct*);
|
|
||||||
void engine_active_owner_reset(struct ct*);
|
|
||||||
void engine_active_owner_caret(struct ct*);
|
|
||||||
void engine_popup_preedit_capability(struct ct*);
|
|
||||||
void engine_vietnamese_client_preedit(struct ct*);
|
|
||||||
void engine_commit_contract(struct ct*);
|
|
||||||
void engine_language_switch_state(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_queries(struct ct*);
|
|
||||||
void engine_emoji_japanese_and_multirune(struct ct*);
|
|
||||||
void engine_emoji_digit_aliases(struct ct*);
|
|
||||||
void engine_emoji_navigation(struct ct*);
|
|
||||||
void engine_search_candidate_keys(struct ct*);
|
|
||||||
void engine_emoji_preedit_languages(struct ct*);
|
|
||||||
void engine_emoji_start_and_unknown(struct ct*);
|
|
||||||
void engine_hanja_search(struct ct*);
|
|
||||||
void engine_hanja_unknown_and_cancel(struct ct*);
|
|
||||||
void engine_hanja_reaches_back(struct ct*);
|
|
||||||
void engine_hanja_word_prefix(struct ct*);
|
|
||||||
void engine_hanja_korean_keys(struct ct*);
|
|
||||||
void engine_hanja_backspace(struct ct*);
|
|
||||||
void engine_hanja_input_languages(struct ct*);
|
|
||||||
void engine_randomized_stress(struct ct*);
|
|
||||||
void engine_full_boundary_passthrough(struct ct*);
|
|
||||||
void dictionary_candidates(struct ct*);
|
|
||||||
void dictionary_misses(struct ct*);
|
|
||||||
void dictionary_prefix(struct ct*);
|
|
||||||
void ipc_masks_modifiers(struct ct*);
|
|
||||||
void ipc_control_and_caret_frames(struct ct*);
|
|
||||||
void ipc_runtime_path(struct ct*);
|
|
||||||
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*);
|
|
||||||
void server_connection_ownership(struct ct*);
|
|
||||||
void server_extension_stream(struct ct*);
|
|
||||||
void server_rejects_unknown_extension(struct ct*);
|
|
||||||
void ibus_machine_id_fallback(struct ct*);
|
|
||||||
void ibus_capability_policy(struct ct*);
|
|
||||||
void ibus_private_input_policy(struct ct*);
|
|
||||||
void ibus_context_lifecycle(struct ct*);
|
|
||||||
void ibus_active_release_lifecycle(struct ct*);
|
|
||||||
void compose_sequences(struct ct*);
|
|
||||||
void xim_keymap_lookup(struct ct*);
|
|
||||||
void xim_compound_text(struct ct*);
|
|
||||||
void xim_adapter_key_contract(struct ct*);
|
|
||||||
void xim_adapter_release_lifecycle(struct ct*);
|
|
||||||
void xim_adapter_free_waits_for_release(struct ct*);
|
|
||||||
void xim_adapter_styles(struct ct*);
|
|
||||||
void xim_adapter_placement(struct ct*);
|
|
||||||
void xim_adapter_placement_updates(struct ct*);
|
|
||||||
void xim_adapter_callback_replacement(struct ct*);
|
|
||||||
void xim_adapter_callback_unicode(struct ct*);
|
|
||||||
void xim_adapter_callback_cleanup(struct ct*);
|
|
||||||
void xim_adapter_callback_transfer(struct ct*);
|
|
||||||
void xim_adapter_callback_owner_loss(struct ct*);
|
|
||||||
void xim_adapter_commit_encoding(struct ct*);
|
|
||||||
void wl_modifier_mask(struct ct*);
|
|
||||||
void wl_forwarded_keys(struct ct*);
|
|
||||||
void wl_surrounding_text(struct ct*);
|
|
||||||
void wl_repeat_ends(struct ct*);
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
Str
|
|
||||||
mkstr(char *s)
|
|
||||||
{
|
|
||||||
Str r;
|
|
||||||
|
|
||||||
sinit(&r, s, strlen(s));
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
checkstr(struct ct *t, char *where, char *want, Str *got)
|
|
||||||
{
|
|
||||||
char buf[Maxutf];
|
|
||||||
|
|
||||||
stoutf(got, buf, sizeof buf);
|
|
||||||
if(strcmp(want, buf) == 0)
|
|
||||||
return 1;
|
|
||||||
return CT_ERRORF(t, "%s: want \"%s\", got \"%s\"",
|
|
||||||
where, want, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
checkenginepreedit(struct ct *t, char *want)
|
|
||||||
{
|
|
||||||
Str preedit;
|
|
||||||
|
|
||||||
testenginepreedit(&preedit);
|
|
||||||
checkstr(t, "engine preedit", want, &preedit);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
pumpthread(void *arg)
|
|
||||||
{
|
|
||||||
Pump *p;
|
|
||||||
Keyreq req;
|
|
||||||
uchar token;
|
|
||||||
Alt alts[] = {
|
|
||||||
{nil, &req, CHANRCV, nil},
|
|
||||||
{nil, &token, CHANRCV, nil},
|
|
||||||
{nil, nil, CHANEND, nil},
|
|
||||||
};
|
|
||||||
|
|
||||||
p = arg;
|
|
||||||
alts[0].c = keyc;
|
|
||||||
alts[1].c = p->stop;
|
|
||||||
for(;;)
|
|
||||||
switch(alt(alts)){
|
|
||||||
case 0:
|
|
||||||
chansend(p->trace, &req);
|
|
||||||
if(p->holdop == Pumpall || p->holdop == req.op)
|
|
||||||
chanrecv(p->go, &token);
|
|
||||||
testenginehandle(&req);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
chansend(p->done, &token);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ntrace is the trace channel's depth: 0 makes every trace a rendezvous. */
|
|
||||||
void
|
|
||||||
pumpstart(Pump *p, int ntrace)
|
|
||||||
{
|
|
||||||
memset(p, 0, sizeof *p);
|
|
||||||
p->trace = chancreate(sizeof(Keyreq), ntrace);
|
|
||||||
p->go = chancreate(sizeof(uchar), 0);
|
|
||||||
p->stop = chancreate(sizeof(uchar), 0);
|
|
||||||
p->done = chancreate(sizeof(uchar), 0);
|
|
||||||
p->holdop = Pumpnone;
|
|
||||||
/* Its own proc: tests block in socket I/O while the engine runs. */
|
|
||||||
proccreate(pumpthread, p, 8192);
|
|
||||||
p->active = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
pumphold(Pump *p, int op)
|
|
||||||
{
|
|
||||||
p->holdop = op;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Every held request must have been let go before stopping. */
|
|
||||||
void
|
|
||||||
pumpstop(Pump *p)
|
|
||||||
{
|
|
||||||
Keyreq req;
|
|
||||||
uchar token;
|
|
||||||
|
|
||||||
if(!p->active)
|
|
||||||
return;
|
|
||||||
while(channbrecv(p->trace, &req) > 0)
|
|
||||||
;
|
|
||||||
token = 0;
|
|
||||||
chansend(p->stop, &token);
|
|
||||||
chanrecv(p->done, &token);
|
|
||||||
chanfree(p->trace);
|
|
||||||
chanfree(p->go);
|
|
||||||
chanfree(p->stop);
|
|
||||||
chanfree(p->done);
|
|
||||||
p->active = 0;
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
void
|
|
||||||
trie_exact_prefix_and_duplicate(struct ct *t)
|
|
||||||
{
|
|
||||||
static const struct {
|
|
||||||
char *key;
|
|
||||||
int match;
|
|
||||||
char *want;
|
|
||||||
} cases[] = {
|
|
||||||
{ "a", TrieExact, "alpha" },
|
|
||||||
{ "ab", TrieExact, "beta" },
|
|
||||||
{ "한", TrieExact, "값" },
|
|
||||||
{ "dupli", TriePrefix, nil },
|
|
||||||
{ "duplicate", TrieExact, "second" },
|
|
||||||
{ "missing", TrieMiss, nil },
|
|
||||||
{ "", TriePrefix, nil },
|
|
||||||
};
|
|
||||||
char *v;
|
|
||||||
Trie *trie;
|
|
||||||
Str key;
|
|
||||||
int i, match, n;
|
|
||||||
|
|
||||||
trie = trieopen("data/trie.map");
|
|
||||||
for(i = 0; i < nelem(cases); i++){
|
|
||||||
key = mkstr(cases[i].key);
|
|
||||||
match = trielookup(trie, &key, &v, &n);
|
|
||||||
if(match != cases[i].match){
|
|
||||||
CT_ERRORF(t, "case %d: want match %d, got %d",
|
|
||||||
i, cases[i].match, match);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(cases[i].want == nil){
|
|
||||||
CT_EQ_PTR(t, nil, v);
|
|
||||||
CT_EQ_INT(t, 0, n);
|
|
||||||
}else{
|
|
||||||
CT_EQ_INT(t, strlen(cases[i].want), n);
|
|
||||||
CT_EQ_MEM(t, cases[i].want, v, n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trieclose(trie);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
trie_put_and_unloaded(struct ct *t)
|
|
||||||
{
|
|
||||||
char *v;
|
|
||||||
Trie *trie;
|
|
||||||
Str key;
|
|
||||||
int n;
|
|
||||||
|
|
||||||
key = mkstr("k");
|
|
||||||
CT_EQ_INT(t, TrieMiss, trielookup(nil, &key, &v, &n));
|
|
||||||
trie = trienew();
|
|
||||||
CT_EQ_INT(t, TrieMiss, trielookup(trie, &key, &v, &n));
|
|
||||||
trieput(trie, "k", 1, "one two", 7);
|
|
||||||
trieput(trie, "ka", 2, "", 0);
|
|
||||||
if(CT_EQ_INT(t, TrieExact, trielookup(trie, &key, &v, &n)))
|
|
||||||
CT_EQ_MEM(t, "one two", v, 7);
|
|
||||||
key = mkstr("ka");
|
|
||||||
if(CT_EQ_INT(t, TrieExact, trielookup(trie, &key, &v, &n)))
|
|
||||||
CT_EQ_INT(t, 0, n);
|
|
||||||
trieput(trie, "k", 1, "three", 5);
|
|
||||||
key = mkstr("k");
|
|
||||||
if(CT_EQ_INT(t, TrieExact, trielookup(trie, &key, &v, &n)))
|
|
||||||
CT_EQ_MEM(t, "three", v, 5);
|
|
||||||
trieclose(trie);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
production_maps_load(struct ct *t)
|
|
||||||
{
|
|
||||||
char path[256];
|
|
||||||
Trie *map;
|
|
||||||
int i, loaded, registered;
|
|
||||||
|
|
||||||
loaded = registered = 0;
|
|
||||||
for(i = 0; i < nlang; i++){
|
|
||||||
if(langs[i].mapname == nil)
|
|
||||||
continue;
|
|
||||||
registered++;
|
|
||||||
snprint(path, sizeof path, "../map/%s.map", langs[i].mapname);
|
|
||||||
map = trieopen(path);
|
|
||||||
if(!CT_CHECK(t, map != nil))
|
|
||||||
continue;
|
|
||||||
trieclose(map);
|
|
||||||
loaded++;
|
|
||||||
}
|
|
||||||
CT_CHECK(t, registered > 0);
|
|
||||||
CT_EQ_INT(t, registered, loaded);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
transmap_states(struct ct *t)
|
|
||||||
{
|
|
||||||
static const struct {
|
|
||||||
char *pre;
|
|
||||||
Rune key;
|
|
||||||
int eat;
|
|
||||||
char *emit;
|
|
||||||
char *next;
|
|
||||||
} cases[] = {
|
|
||||||
{ "", 'k', 1, "", "k" },
|
|
||||||
{ "k", 'a', 1, "", "ka" },
|
|
||||||
{ "ka", 's', 1, "か", "s" },
|
|
||||||
{ "ka", 'q', 0, "か", "" },
|
|
||||||
{ "k", 'q', 0, "k", "" },
|
|
||||||
};
|
|
||||||
Emit e;
|
|
||||||
Im state;
|
|
||||||
Trie *fixture, *saved;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
fixture = trieopen("data/hira.map");
|
|
||||||
memset(&state, 0, sizeof state);
|
|
||||||
state.l = getlang(LangJP);
|
|
||||||
saved = state.l->map;
|
|
||||||
state.l->map = fixture;
|
|
||||||
for(i = 0; i < nelem(cases); i++){
|
|
||||||
state.pre = mkstr(cases[i].pre);
|
|
||||||
e = transmap(&state, cases[i].key);
|
|
||||||
if(e.eat != cases[i].eat)
|
|
||||||
CT_ERRORF(t, "case %d: want eat %d, got %d",
|
|
||||||
i, cases[i].eat, e.eat);
|
|
||||||
checkstr(t, "emit", cases[i].emit, &e.s);
|
|
||||||
checkstr(t, "next", cases[i].next, &e.next);
|
|
||||||
}
|
|
||||||
trieclose(fixture);
|
|
||||||
state.l->map = saved;
|
|
||||||
}
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
#define CT_IMPLEMENTATION
|
|
||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
#include <stdarg.h>
|
|
||||||
|
|
||||||
Channel *drawc;
|
|
||||||
Channel *keyc;
|
|
||||||
|
|
||||||
void
|
|
||||||
die(char *fmt, ...)
|
|
||||||
{
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
fprint(2, "unit_test: ");
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprint(2, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
fprint(2, "\n");
|
|
||||||
threadexitsall("die");
|
|
||||||
}
|
|
||||||
|
|
||||||
void*
|
|
||||||
emalloc(ulong n)
|
|
||||||
{
|
|
||||||
void *p;
|
|
||||||
|
|
||||||
p = mallocz(n, 1);
|
|
||||||
if(p == nil)
|
|
||||||
die("out of memory");
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
void*
|
|
||||||
erealloc(void *p, ulong n)
|
|
||||||
{
|
|
||||||
p = realloc(p, n);
|
|
||||||
if(p == nil)
|
|
||||||
die("out of memory");
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
testmapinit(void)
|
|
||||||
{
|
|
||||||
Lang *jp, *kata, *vi;
|
|
||||||
|
|
||||||
jp = getlang(LangJP);
|
|
||||||
kata = getlang(LangJPK);
|
|
||||||
vi = getlang(LangVI);
|
|
||||||
if(jp == nil || kata == nil || vi == nil)
|
|
||||||
die("test language is not registered");
|
|
||||||
jp->map = trieopen("../map/hira.map");
|
|
||||||
kata->map = trieopen("../map/kata.map");
|
|
||||||
vi->map = trieopen("../map/telex.map");
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef STRESS
|
|
||||||
static const struct ct_test tests[] = {
|
|
||||||
{ "str/init-utf8", str_init_utf8 },
|
|
||||||
{ "str/edit-and-alias", str_edit_and_alias },
|
|
||||||
{ "str/utf8-capacity", str_utf8_capacity },
|
|
||||||
{ "str/invalid-full-appends", str_invalid_and_full_appends },
|
|
||||||
{ "trie/exact-prefix-duplicate", trie_exact_prefix_and_duplicate },
|
|
||||||
{ "trie/put-and-unloaded", trie_put_and_unloaded },
|
|
||||||
{ "popup/layout", popup_layout },
|
|
||||||
{ "font/render", font_render },
|
|
||||||
{ "map/production-lifecycle", production_maps_load },
|
|
||||||
{ "transmap/states", transmap_states },
|
|
||||||
{ "hangul/sequences", korean_sequences },
|
|
||||||
{ "hangul/compound-vowels", korean_compound_vowels },
|
|
||||||
{ "hangul/compound-finals", korean_compound_finals },
|
|
||||||
{ "hangul/backspace", korean_backspace },
|
|
||||||
{ "telex/transitions", vietnamese_transitions },
|
|
||||||
{ "telex/backspace", vietnamese_backspace },
|
|
||||||
{ "telex/state-lifetime", vietnamese_state_lifetime },
|
|
||||||
{ "engine/clears-candidates", engine_clears_candidates },
|
|
||||||
{ "engine/backspace-clears-candidates", engine_backspace_clears_candidates },
|
|
||||||
{ "engine/selects-visible-candidate", engine_selects_visible_candidate },
|
|
||||||
{ "engine/candidate-shortcut-modifiers", engine_candidate_shortcut_modifiers },
|
|
||||||
{ "engine/candidate-page-metadata", engine_candidate_page_metadata },
|
|
||||||
{ "engine/candidate-page-snapshots", engine_candidate_page_snapshots },
|
|
||||||
{ "engine/candidate-page-movement", engine_candidate_page_movement },
|
|
||||||
{ "engine/candidate-completion", engine_candidate_completion },
|
|
||||||
{ "engine/active-owner-lifecycle", engine_active_owner_lifecycle },
|
|
||||||
{ "engine/active-owner-reset", engine_active_owner_reset },
|
|
||||||
{ "engine/active-owner-caret", engine_active_owner_caret },
|
|
||||||
{ "engine/popup-preedit-capability", engine_popup_preedit_capability },
|
|
||||||
{ "engine/vietnamese-client-preedit", engine_vietnamese_client_preedit },
|
|
||||||
{ "engine/commit-contract", engine_commit_contract },
|
|
||||||
{ "engine/language-switch-state", engine_language_switch_state },
|
|
||||||
{ "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-queries", engine_emoji_queries },
|
|
||||||
{ "engine/emoji-japanese-multirune", engine_emoji_japanese_and_multirune },
|
|
||||||
{ "engine/emoji-digit-aliases", engine_emoji_digit_aliases },
|
|
||||||
{ "engine/emoji-navigation", engine_emoji_navigation },
|
|
||||||
{ "engine/search-candidate-keys", engine_search_candidate_keys },
|
|
||||||
{ "engine/emoji-preedit-languages", engine_emoji_preedit_languages },
|
|
||||||
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
|
|
||||||
{ "engine/hanja-search", engine_hanja_search },
|
|
||||||
{ "engine/hanja-unknown-cancel", engine_hanja_unknown_and_cancel },
|
|
||||||
{ "engine/hanja-reaches-back", engine_hanja_reaches_back },
|
|
||||||
{ "engine/hanja-word-prefix", engine_hanja_word_prefix },
|
|
||||||
{ "engine/hanja-korean-keys", engine_hanja_korean_keys },
|
|
||||||
{ "engine/hanja-backspace", engine_hanja_backspace },
|
|
||||||
{ "engine/hanja-input-languages", engine_hanja_input_languages },
|
|
||||||
{ "engine/full-boundary-passthrough", engine_full_boundary_passthrough },
|
|
||||||
{ "dict/candidates", dictionary_candidates },
|
|
||||||
{ "dict/misses", dictionary_misses },
|
|
||||||
{ "dict/prefix", dictionary_prefix },
|
|
||||||
{ "ipc/masks-modifiers", ipc_masks_modifiers },
|
|
||||||
{ "ipc/control-caret-frames", ipc_control_and_caret_frames },
|
|
||||||
{ "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 },
|
|
||||||
{ "server/connection-ownership", server_connection_ownership },
|
|
||||||
{ "server/extension-stream", server_extension_stream },
|
|
||||||
{ "server/rejects-unknown-extension", server_rejects_unknown_extension },
|
|
||||||
{ "ibus/machine-id-fallback", ibus_machine_id_fallback },
|
|
||||||
{ "ibus/capability-policy", ibus_capability_policy },
|
|
||||||
{ "ibus/private-input-policy", ibus_private_input_policy },
|
|
||||||
{ "ibus/context-lifecycle", ibus_context_lifecycle },
|
|
||||||
{ "ibus/active-release-lifecycle", ibus_active_release_lifecycle },
|
|
||||||
{ "compose/sequences", compose_sequences },
|
|
||||||
{ "xim/keymap-lookup", xim_keymap_lookup },
|
|
||||||
{ "xim/compound-text", xim_compound_text },
|
|
||||||
{ "xim/adapter-key-contract", xim_adapter_key_contract },
|
|
||||||
{ "xim/adapter-release-lifecycle", xim_adapter_release_lifecycle },
|
|
||||||
{ "xim/adapter-free-waits-release", xim_adapter_free_waits_for_release },
|
|
||||||
{ "xim/styles", xim_adapter_styles },
|
|
||||||
{ "xim/placement", xim_adapter_placement },
|
|
||||||
{ "xim/placement-updates", xim_adapter_placement_updates },
|
|
||||||
{ "xim/callback-replacement", xim_adapter_callback_replacement },
|
|
||||||
{ "xim/callback-unicode", xim_adapter_callback_unicode },
|
|
||||||
{ "xim/callback-cleanup", xim_adapter_callback_cleanup },
|
|
||||||
{ "xim/callback-transfer", xim_adapter_callback_transfer },
|
|
||||||
{ "xim/callback-owner-loss", xim_adapter_callback_owner_loss },
|
|
||||||
{ "xim/commit-encoding", xim_adapter_commit_encoding },
|
|
||||||
{ "wl/modifier-mask", wl_modifier_mask },
|
|
||||||
{ "wl/forwarded-keys", wl_forwarded_keys },
|
|
||||||
{ "wl/surrounding-text", wl_surrounding_text },
|
|
||||||
{ "wl/repeat-ends", wl_repeat_ends },
|
|
||||||
};
|
|
||||||
#else
|
|
||||||
static const struct ct_test tests[] = {
|
|
||||||
{ "engine/randomized-stress", engine_randomized_stress },
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void
|
|
||||||
threadmain(int argc, char **argv)
|
|
||||||
{
|
|
||||||
int i, status;
|
|
||||||
|
|
||||||
drawc = chancreate(sizeof(Drawcmd), 4);
|
|
||||||
keyc = chancreate(sizeof(Keyreq), 0);
|
|
||||||
testmapinit();
|
|
||||||
status = CT_RUN_ARGS(tests, argc, argv);
|
|
||||||
for(i = 0; i < nlang; i++){
|
|
||||||
trieclose(langs[i].map);
|
|
||||||
langs[i].map = nil;
|
|
||||||
}
|
|
||||||
chanfree(drawc);
|
|
||||||
chanfree(keyc);
|
|
||||||
threadexitsall(status == 0 ? nil : "tests failed");
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user