Compare commits
275 Commits
5ae0945015
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f77e0e9ac | |||
| 761428dbc4 | |||
| 1bc0ba0491 | |||
| 74607afef8 | |||
| 591029237e | |||
| bdf05e3c4a | |||
| 4802b6593e | |||
| ba0a08abe2 | |||
| 08d0e0fec8 | |||
| d3d46c8cb1 | |||
| b08f3bb7a2 | |||
| cf0eba6642 | |||
| 5d9a2a614e | |||
| 921e632eae | |||
| 910bf51347 | |||
| f7277f54a3 | |||
| 997e4c8d93 | |||
| dab80b3a77 | |||
| d9e27e8b01 | |||
| 5abd885e11 | |||
| a351f50308 | |||
| c9c01fdeb9 | |||
| bfcd9f6786 | |||
| 3f8f51adf4 | |||
| 8ebe6b32df | |||
| b61c04b280 | |||
| dadda19b3a | |||
| 62ddc2177b | |||
| 82078a60e7 | |||
| 46ab39ab95 | |||
| 65066eef53 | |||
| 0f128a46b3 | |||
| 650f00812d | |||
| e64e4ea6af | |||
| 867c6ebe40 | |||
| 8a08efe2bb | |||
| 5ff54237ff | |||
| bbc0c97656 | |||
| aef577026d | |||
| 8472474332 | |||
| 33710178d4 | |||
| 5cb9f69590 | |||
| 6d511919e8 | |||
| 40fd4ab551 | |||
| 841d93ac35 | |||
| f6dd056ffb | |||
| ad2f2fc5eb | |||
| 2e1e0b9645 | |||
| 5664a008de | |||
| 4ac0625c6b | |||
| 233884e51f | |||
| 27180bc0fe | |||
| f6477cb97d | |||
| 059e8bbc75 | |||
| e93ec43706 | |||
| 544ed835b7 | |||
| ae7dbae993 | |||
| 2e53627b7d | |||
| 3527bcd489 | |||
| 6a6749e824 | |||
| c2aec44aa9 | |||
| cf8eca2ff1 | |||
| d129f406d5 | |||
| e54783dc92 | |||
| 3a90826238 | |||
| 0ba7dc7f50 | |||
| ef7fb627c6 | |||
| a0e83f98c6 | |||
| 34c0c501aa | |||
| 0f684ae6d3 | |||
| c102c87d55 | |||
| bfa919f623 | |||
| 5a6f352d72 | |||
| 88ad5f7630 | |||
| 33023d7f51 | |||
| 46bb8b1d46 | |||
| 82cb07eba2 | |||
| b979afc3f7 | |||
| dd389edc4f | |||
| 268b687ee6 | |||
| e32cea3296 | |||
| a03fb05324 | |||
| 42568ab020 | |||
| 7c9e736996 | |||
| 0cb395579f | |||
| 33848709a5 | |||
| 47f9801ad8 | |||
| fcdce7dc16 | |||
| 0c4271b8b5 | |||
| bab40c95a7 | |||
| 87cb03fdf8 | |||
| cd637be4ca | |||
| 5faefd9a87 | |||
| 3d862bdc0d | |||
| 97a838eda1 | |||
| 1c8d9fac07 | |||
| 97e6f2cf1a | |||
| 852e129f62 | |||
| 1285476b41 | |||
| f0498868eb | |||
| ab0366b7f7 | |||
| f61f7ab5f8 | |||
| 95dff0b82b | |||
| abff5ed122 | |||
| 221117f93d | |||
| b77400fa86 | |||
| 63fc60dc06 | |||
| e8519b4e06 | |||
| ea363ca792 | |||
| bab6077c75 | |||
| 48d3e165c3 | |||
| 6b1de2df4f | |||
| 45c6be7e1b | |||
| 4ff696d06c | |||
| 30abd05e68 | |||
| 693a1210a6 | |||
| 91b3e538f1 | |||
| 5e72a48c2c | |||
| 11bf379ad3 | |||
| 4c8954b8ae | |||
| ae1bc51256 | |||
| e860008d84 | |||
| 32a9723043 | |||
| 6402c26830 | |||
| 9853d65d4c | |||
| cdd3e38195 | |||
| f6adb117c9 | |||
| 333300b297 | |||
| 4b6cc6fb2f | |||
| 0aef3506b6 | |||
| 723a271a9c | |||
| 3f1ff03b94 | |||
| 68678283cd | |||
| 03c196a7f6 | |||
| 8831b84ffc | |||
| 45c0290fd4 | |||
| dde9fb431b | |||
| 7220286ab5 | |||
| e1c36af9ba | |||
| 7c73e4a463 | |||
| 795f11be22 | |||
| 826da31486 | |||
| 849bf1278b | |||
| eaf31da0d1 | |||
| c2baa47026 | |||
| a70cfc42e3 | |||
| 9080bb833c | |||
| 68869e2589 | |||
| a557fb114a | |||
| abaea77248 | |||
| ebcec3af6b | |||
| c7718ece52 | |||
| eb0f88f764 | |||
| 43b469f70b | |||
| 66ea2892f8 | |||
| d3c9813f6e | |||
| 61840fc108 | |||
| c1971c8096 | |||
| 7047c57d29 | |||
| a1ef8a4744 | |||
| 67ed2ef478 | |||
| f489c56fb1 | |||
| d29d43f443 | |||
| aeb6010f3c | |||
| f0abace40b | |||
| 7468d573ec | |||
| 133cc97e55 | |||
| 516ec98a70 | |||
| bfe83d0119 | |||
| 38475318db | |||
| 59482d00f3 | |||
| 8df43cecd8 | |||
| a637f457f7 | |||
| 9804c1f55c | |||
| 534ddcd9bb | |||
| 5dc2530951 | |||
| f2fb71cd89 | |||
| 77c6dd66ca | |||
| 0471d58dec | |||
| f222b4d573 | |||
| 3576c4ea68 | |||
| e4fc327ffd | |||
| 193ed009bd | |||
| 7619d2bd9d | |||
| a841894015 | |||
| 4cac6f1a15 | |||
| 2e6d7d8e48 | |||
| 76f9e1a21f | |||
| 6a58b63223 | |||
| b536573622 | |||
| 9a3a92170e | |||
| 76af1a022f | |||
| 68dc1e6a18 | |||
| e6bf32b0f6 | |||
| 2b4c509d0d | |||
| 2376035524 | |||
| c1b1b7016e | |||
| 4eba8c2904 | |||
| f5dc552c08 | |||
| 4e07699460 | |||
| 08369e718e | |||
| 54355b8190 | |||
| 279b9230a4 | |||
| c2cb18cbdd | |||
| 24e380651c | |||
| c4b0141413 | |||
| 46c8ccd779 | |||
| d7115c5eb5 | |||
| f395f29918 | |||
| 6a2b2bccc4 | |||
| 05ff88c74f | |||
| 6c0a533c4e | |||
| e44fc005da | |||
| 469278209f | |||
| 9569c2eaaf | |||
| 335783ebe5 | |||
| 0f8396915b | |||
| 91b3042e66 | |||
| b44663096f | |||
| 685d78bab5 | |||
| f8ebfcdc86 | |||
| f399eb0af6 | |||
| 88d26c598d | |||
| 9f4f9ceb14 | |||
| bb2a970db1 | |||
| 21a94d447c | |||
| 367cbb6cc3 | |||
| b6a9ccd9d9 | |||
| 13f0b436b8 | |||
| c07673ad4c | |||
| e01a991de4 | |||
| fffbb9d8de | |||
| f46d51b539 | |||
| 125e9349c0 | |||
| 768ac3de74 | |||
| 136695a900 | |||
| 4e1c15dde4 | |||
| 0216cc3c8a | |||
| e69883d0ad | |||
| 9051c69b49 | |||
| 9566995c07 | |||
| 1f9af6ee3e | |||
| bae39c0188 | |||
| 4e90fdd5c7 | |||
| de536da0bb | |||
| 8d68e00f2b | |||
| a8a6c3b455 | |||
| 0d06ba43cd | |||
| 91931196e2 | |||
| 211a6917b2 | |||
| 4e8720a40d | |||
| 71af269efe | |||
| 3f5e107230 | |||
| c41a1b4227 | |||
| 368e6adcd4 | |||
| 5927f6a5e1 | |||
| 9d60c4bbb0 | |||
| c7d4f16d3e | |||
| a03efcf6bf | |||
| 432df3730c | |||
| 4120f90736 | |||
| 9c43bcb75c | |||
| 1d8d711882 | |||
| 1295480858 | |||
| 103f2ec448 | |||
| cec62cc40f | |||
| bc5712b6ac | |||
| 5ba5cf209c | |||
| 9c244d1dd1 | |||
| fabce14933 | |||
| d0def814ef | |||
| d2fa51ceba | |||
| f0ba7fc5f6 | |||
| 6dfbc08704 | |||
| ec7e6eb992 |
76
.gitignore
vendored
76
.gitignore
vendored
@@ -1,59 +1,21 @@
|
|||||||
strans
|
/*.o
|
||||||
strans-xim
|
/strans
|
||||||
|
/imv2.c
|
||||||
|
/imv2.h
|
||||||
|
/vkv1.c
|
||||||
|
/vkv1.h
|
||||||
|
/tests/*.o
|
||||||
/tests/unit_test
|
/tests/unit_test
|
||||||
|
/tests/stress_test
|
||||||
|
/tests/ibus_live_test
|
||||||
|
/tests/ibus_client_smoke
|
||||||
|
/tests/gtk_live_test
|
||||||
|
/tests/xim_live_test
|
||||||
|
/tests/ipc_live_test
|
||||||
|
/tests/daemon_collision_test
|
||||||
|
/tests/daemon_failure_test
|
||||||
|
/tests/daemon_restart_test
|
||||||
|
/gtk/im-strans.so
|
||||||
/bench/bench
|
/bench/bench
|
||||||
|
/bench/perf.data*
|
||||||
# Prerequisites
|
/ref-*/
|
||||||
*.d
|
|
||||||
|
|
||||||
# Object files
|
|
||||||
*.o
|
|
||||||
*.ko
|
|
||||||
*.obj
|
|
||||||
*.elf
|
|
||||||
|
|
||||||
# Linker output
|
|
||||||
*.ilk
|
|
||||||
*.exp
|
|
||||||
|
|
||||||
# Precompiled Headers
|
|
||||||
*.gch
|
|
||||||
*.pch
|
|
||||||
|
|
||||||
# Libraries
|
|
||||||
*.lib
|
|
||||||
*.a
|
|
||||||
*.la
|
|
||||||
*.lo
|
|
||||||
|
|
||||||
# Shared objects (inc. Windows DLLs)
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.so.*
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Executables
|
|
||||||
*.exe
|
|
||||||
*.out
|
|
||||||
*.app
|
|
||||||
*.i*86
|
|
||||||
*.x86_64
|
|
||||||
*.hex
|
|
||||||
|
|
||||||
# Debug files
|
|
||||||
*.dSYM/
|
|
||||||
*.su
|
|
||||||
*.idb
|
|
||||||
*.pdb
|
|
||||||
|
|
||||||
# Kernel Module Compile Results
|
|
||||||
*.mod*
|
|
||||||
*.cmd
|
|
||||||
.tmp_versions/
|
|
||||||
modules.order
|
|
||||||
Module.symvers
|
|
||||||
Mkfile.old
|
|
||||||
dkms.conf
|
|
||||||
|
|
||||||
# debug information files
|
|
||||||
*.dwo
|
|
||||||
|
|||||||
30
Dockerfile
30
Dockerfile
@@ -1,24 +1,50 @@
|
|||||||
FROM archlinux:base
|
FROM archlinux:base@sha256:b0deabeb3d283da2c7f7dbf0eea051b7b2cd0554e0b737cc457fd21683bdcdd1
|
||||||
|
|
||||||
RUN pacman -Syu --noconfirm --needed \
|
RUN pacman -Syu --noconfirm --needed \
|
||||||
|
cairo \
|
||||||
|
fontconfig \
|
||||||
diffutils \
|
diffutils \
|
||||||
gcc \
|
gcc \
|
||||||
gtk3 \
|
gtk3 \
|
||||||
|
ibus \
|
||||||
|
harfbuzz \
|
||||||
|
libx11 \
|
||||||
libxcb \
|
libxcb \
|
||||||
libxkbcommon \
|
libxkbcommon \
|
||||||
make \
|
make \
|
||||||
|
noto-fonts-emoji \
|
||||||
|
pango \
|
||||||
pkgconf \
|
pkgconf \
|
||||||
plan9port \
|
plan9port \
|
||||||
python \
|
python \
|
||||||
|
ttf-dejavu \
|
||||||
|
ttf-jigmo \
|
||||||
|
valgrind \
|
||||||
wayland \
|
wayland \
|
||||||
which \
|
which \
|
||||||
|
xorg-server-xvfb \
|
||||||
xcb-imdkit \
|
xcb-imdkit \
|
||||||
|
xcb-util \
|
||||||
dbus \
|
dbus \
|
||||||
&& pacman -Scc --noconfirm
|
&& pacman -Scc --noconfirm
|
||||||
|
|
||||||
RUN printf "egrep='grep -E'\n" > /usr/lib/plan9/config
|
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 PLAN9=/usr/lib/plan9
|
||||||
ENV PATH=$PATH:/usr/lib/plan9/bin
|
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
|
WORKDIR /src
|
||||||
|
|||||||
26
LICENSES/BSD-3-Clause-libhangul-hanja.txt
Normal file
26
LICENSES/BSD-3-Clause-libhangul-hanja.txt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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.
|
||||||
339
LICENSES/GPL-2.0-or-later.txt
Normal file
339
LICENSES/GPL-2.0-or-later.txt
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Lesser General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License.
|
||||||
17
LICENSES/README.md
Normal file
17
LICENSES/README.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 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.
|
||||||
39
LICENSES/Unicode-3.0.txt
Normal file
39
LICENSES/Unicode-3.0.txt
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
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.
|
||||||
119
Makefile
119
Makefile
@@ -1,61 +1,118 @@
|
|||||||
CC = 9c
|
CC = 9c
|
||||||
LD = 9l
|
LD = 9l
|
||||||
PKG_CFLAGS = $(shell pkg-config --cflags dbus-1 wayland-client xkbcommon)
|
PKG_CONFIG ?= pkg-config
|
||||||
PKG_LIBS = $(shell pkg-config --libs dbus-1 wayland-client xkbcommon)
|
SCANNER ?= wayland-scanner
|
||||||
CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS)
|
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_IMAGE = strans-build
|
||||||
|
DOCKER_RUN = docker run --rm --user "$$(id -u):$$(id -g)" \
|
||||||
|
-v "$(CURDIR):/src" $(DOCKER_IMAGE)
|
||||||
|
|
||||||
SRCS = $(wildcard *.c)
|
SRCS = compose.c dict.c font.c ibus.c ipc.c ko.c main.c popup_layout.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) xim gtk
|
all: $(PROG) gtk
|
||||||
|
|
||||||
$(PROG): $(OBJS)
|
$(PROG): $(OBJS) $(PROTOOBJ)
|
||||||
$(LD) -o $@ $(OBJS) -lthread -lString -lbio -lxcb -lm $(PKG_LIBS)
|
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(PROTOOBJ) $(PROJECT_LDLIBS) $(LDLIBS)
|
||||||
|
|
||||||
$(OBJS): dat.h fn.h ipc.h
|
$(OBJS): dat.h fn.h ipc.h
|
||||||
# Avoid Arch's GLIBC_2.43 sqrtf optimization for older glibc runtimes.
|
wl.o: imv2.h vkv1.h
|
||||||
font.o: CFLAGS += -fno-builtin-sqrt
|
compose.o ibus.o: PROJECT_CPPFLAGS += $(IBUS_CFLAGS)
|
||||||
font.o: stb_truetype.h
|
font.o: PROJECT_CPPFLAGS += $(TEXT_CFLAGS)
|
||||||
wayland.o: input-method-unstable-v2-client-protocol.h \
|
win.o: PROJECT_CPPFLAGS += $(POPUP_CFLAGS)
|
||||||
virtual-keyboard-unstable-v1-client-protocol.h
|
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)
|
rm -f $(OBJS) $(PROG) $(PROTO) $(PROTOOBJ)
|
||||||
$(MAKE) -C tests clean
|
$(MAKE) -C tests clean
|
||||||
$(MAKE) -C xim/ clean
|
|
||||||
$(MAKE) -C gtk/ clean
|
$(MAKE) -C gtk/ clean
|
||||||
$(MAKE) -C bench/ clean
|
$(MAKE) -C bench/ clean
|
||||||
|
|
||||||
xim:
|
|
||||||
$(MAKE) -C xim/
|
|
||||||
|
|
||||||
gtk:
|
gtk:
|
||||||
$(MAKE) -C gtk/
|
$(MAKE) -C gtk/
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
$(MAKE) -C bench/
|
$(MAKE) -C bench/
|
||||||
|
|
||||||
docker: Dockerfile
|
docker-image: Dockerfile
|
||||||
docker build -t $(DOCKER_IMAGE) - < Dockerfile
|
docker build -t $(DOCKER_IMAGE) - < Dockerfile
|
||||||
docker run --rm --user "$$(id -u):$$(id -g)" \
|
|
||||||
-v "$(CURDIR):/src" $(DOCKER_IMAGE) \
|
|
||||||
sh -c 'make clean && make all'
|
|
||||||
|
|
||||||
docker-check: docker
|
docker-build:
|
||||||
docker run --rm --user "$$(id -u):$$(id -g)" \
|
$(DOCKER_RUN) sh -c 'make clean && make all'
|
||||||
-e TESTARGS="$(TESTARGS)" -v "$(CURDIR):/src" \
|
|
||||||
$(DOCKER_IMAGE) make check
|
|
||||||
|
|
||||||
check: verify-map test
|
docker-check:
|
||||||
|
$(DOCKER_RUN) sh -c 'make clean && \
|
||||||
|
make check check-live check-stress UNITARGS="$(UNITARGS)"'
|
||||||
|
|
||||||
test:
|
docker-check-live:
|
||||||
$(MAKE) -C tests check TESTARGS="$(TESTARGS)"
|
$(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:
|
verify-map:
|
||||||
|
test "$$(cut -f1 map/hira.map)" = "$$(cut -f1 map/kata.map)"
|
||||||
python3 map/mktelex.py | cmp - map/telex.map
|
python3 map/mktelex.py | cmp - map/telex.map
|
||||||
python3 -B map/mkemoji | cmp - map/emoji.dict
|
python3 map/mkemoji | cmp - map/emoji.dict
|
||||||
python3 -B tests/mkemoji_test.py
|
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 test verify-map clean xim gtk bench docker docker-check
|
.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
|
||||||
|
|||||||
260
README.md
260
README.md
@@ -1,118 +1,194 @@
|
|||||||
# strans
|
# strans
|
||||||
|
|
||||||
A small input method daemon for Japanese, Korean, Vietnamese, emoji, and
|
strans is a small, single-user input method for Korean, Japanese, English,
|
||||||
symbols on X11 and Wayland. Inspired by 9front's ktrans.
|
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.
|
||||||
|
|
||||||
## Build
|
## Modes
|
||||||
|
|
||||||
The recommended build needs only Docker and Make on the host:
|
| Key | Mode |
|
||||||
|
| --- | --- |
|
||||||
|
| `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 |
|
||||||
|
|
||||||
```sh
|
Only a plain `Ctrl` chord is a strans key: with `Shift`, `Alt` or `Super`
|
||||||
make docker
|
held it commits what is pending and goes to the application, so
|
||||||
```
|
`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.
|
||||||
|
|
||||||
This creates an Arch Linux build image, compiles all runtime components in one
|
## Composing
|
||||||
disposable container, and writes three artifacts into the source tree:
|
|
||||||
|
|
||||||
```text
|
Japanese composes a whole reading and then offers its Kanji with none
|
||||||
strans daemon, IBus and Wayland frontend
|
chosen. `Space` and `Tab` step through the candidates and wrap (`Shift`
|
||||||
xim/strans-xim XIM frontend
|
reverses), the reading in Katakana last, so a word the dictionary lacks
|
||||||
gtk/im-strans.so GTK 3 frontend
|
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.
|
||||||
|
|
||||||
The build starts clean so host and container object files are never mixed. It
|
Korean composes one syllable at a time. `Enter`, `Tab`, `Esc` and any key
|
||||||
removes existing build and test outputs; the `strans-build` dependency image is
|
that is not a jamo commit the syllable and go on to the application, so
|
||||||
kept as a cache. No development packages are required on the host.
|
`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.
|
||||||
|
|
||||||
Void Linux runtime dependencies:
|
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 ①②③.
|
||||||
|
|
||||||
```sh
|
Reaching back needs the application to hand over the text around its
|
||||||
sudo xbps-install -S dbus-libs wayland libxkbcommon libxcb xcb-imdkit gtk+3
|
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.
|
||||||
|
|
||||||
For a native Arch Linux build, use `Dockerfile` as the dependency and Plan 9
|
Dead keys and Compose sequences are composed by strans itself for the
|
||||||
setup checklist, then run `make`. Build the benchmark separately with
|
Wayland, XIM and IBus frontends, from `XCOMPOSEFILE` or the locale; the
|
||||||
`make bench`.
|
GTK 3 module leaves them to GtkIMContextSimple.
|
||||||
|
|
||||||
## Run
|
## Preedit and candidates
|
||||||
|
|
||||||
Start the daemon:
|
| 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 |
|
||||||
|
|
||||||
```sh
|
On Wayland the popup is a surface the compositor places at the text cursor,
|
||||||
./strans map font &
|
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.
|
||||||
|
|
||||||
For XIM applications:
|
## Build and test
|
||||||
|
|
||||||
```sh
|
|
||||||
./xim/strans-xim &
|
|
||||||
export XMODIFIERS=@im=strans
|
|
||||||
xterm
|
|
||||||
```
|
|
||||||
|
|
||||||
For GTK 3 applications:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo make -C gtk install
|
|
||||||
GTK_IM_MODULE=strans gedit
|
|
||||||
```
|
|
||||||
|
|
||||||
For IBus clients such as kitty:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
GLFW_IM_MODULE=ibus kitty
|
|
||||||
```
|
|
||||||
|
|
||||||
Wayland needs no environment variable. Start `strans` after a wlroots
|
|
||||||
compositor that supports input-method-v2. Neither ibus-daemon nor fcitx is
|
|
||||||
required.
|
|
||||||
|
|
||||||
## Keys
|
|
||||||
|
|
||||||
Input modes are selected with Ctrl and one letter:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Ctrl+N Hiragana
|
|
||||||
Ctrl+K Katakana
|
|
||||||
Ctrl+S Hangul
|
|
||||||
Ctrl+T English
|
|
||||||
Ctrl+V Vietnamese Telex
|
|
||||||
Ctrl+E Emoji and symbol search
|
|
||||||
Ctrl+P Toggle popup preedit
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `1`-`9` or the arrow keys to select a candidate. Tab moves through
|
|
||||||
candidates, Enter commits, and Escape cancels.
|
|
||||||
|
|
||||||
Emoji search is temporary and returns to the previous input mode:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Ctrl+E → smile → Enter → 😀
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
Tests use the sibling `cutest` repository as a submodule. Its relative URL
|
|
||||||
expects repository mirrors and forks to provide the same sibling. Initialize
|
|
||||||
it once, then run the checks in the build container:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
make docker-check
|
make docker-image
|
||||||
|
make docker-build
|
||||||
```
|
```
|
||||||
|
|
||||||
The native `make check` command needs plan9port and Python 3, but not the GUI
|
That leaves `strans`, the daemon, and `gtk/im-strans.so`, the GTK 3 module.
|
||||||
development packages. Use `make test TESTARGS=hangul` or
|
Tests come in three tiers:
|
||||||
`make docker-check TESTARGS=hangul` to filter unit tests. After editing
|
|
||||||
`map/emoji.src`, regenerate and verify the dictionary with:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
map/mkemoji > map/emoji.dict
|
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
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./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`,
|
||||||
|
`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
|
||||||
|
compositor offering `zwp_input_method_v2` gets the Wayland frontend, and
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
After changing an input source, regenerate and verify:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 map/mktelex.py >map/telex.map
|
||||||
|
map/mkemoji >map/emoji.dict
|
||||||
|
map/mkhanja >map/hanja.dict
|
||||||
make verify-map
|
make verify-map
|
||||||
```
|
```
|
||||||
|
|
||||||
## Design
|
[`map/README`](map/README) says where the emoji, Hanja and Japanese data
|
||||||
|
comes from.
|
||||||
|
|
||||||
`strans` owns the input engine and candidate window. The XIM and GTK frontends
|
## Benchmark
|
||||||
forward keys to it over a Unix socket; the IBus and Wayland frontends are built
|
|
||||||
into the daemon. Internal threads communicate through CSP channels.
|
`make bench` builds `bench/bench`. With the daemon stopped, `./bench.sh`
|
||||||
|
starts one, warms it up and records the workload with Linux `perf` into
|
||||||
|
`bench/perf.data`.
|
||||||
|
|
||||||
|
## Licensing
|
||||||
|
|
||||||
|
Third-party notices are in [`LICENSES`](LICENSES). The repository declares
|
||||||
|
no license for the strans source as a whole.
|
||||||
|
|||||||
74
bench.sh
74
bench.sh
@@ -1,23 +1,71 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
pkill strans
|
set -eu
|
||||||
pkill strans-xim
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
./strans map font &
|
cd "$(dirname "$0")" || exit 1
|
||||||
sleep 1
|
|
||||||
|
|
||||||
# warm up glyph cache
|
if test "$#" -ne 0; then
|
||||||
|
echo "usage: bench.sh" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! test -x ./bench/bench; then
|
||||||
|
echo "bench.sh: ./bench/bench is not executable; run make bench first" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
./bench/bench bench/bench.keys 100
|
||||||
echo "cache warmed up"
|
echo "renderer warmed up"
|
||||||
|
|
||||||
STRANS_PID=$(pgrep -x strans)
|
perf record -g -o bench/perf.data -p "$strans_pid" &
|
||||||
perf record -g -o bench/perf.data -p "$STRANS_PID" &
|
perf_pid=$!
|
||||||
PERF_PID=$!
|
|
||||||
sleep 1
|
sleep 1
|
||||||
|
kill -0 "$perf_pid"
|
||||||
|
|
||||||
./bench/bench bench/bench.keys 100
|
./bench/bench bench/bench.keys 100
|
||||||
|
|
||||||
kill -INT $PERF_PID
|
kill -INT "$perf_pid"
|
||||||
wait $PERF_PID 2>/dev/null
|
wait "$perf_pid" 2>/dev/null || :
|
||||||
pkill strans
|
perf_pid=
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
CC = cc
|
CC = cc
|
||||||
CFLAGS = -Wall -O2
|
CFLAGS ?= -O2
|
||||||
|
|
||||||
bench: main.c ../ipc.c ../ipc.h
|
bench: main.c ../ipc.c ../ipc.h
|
||||||
$(CC) $(CFLAGS) -I.. -o $@ main.c ../ipc.c
|
$(CC) $(CPPFLAGS) -I.. -Wall $(CFLAGS) $(LDFLAGS) -o $@ \
|
||||||
|
main.c ../ipc.c $(LDLIBS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f bench
|
rm -f bench
|
||||||
|
|||||||
75
bench/main.c
75
bench/main.c
@@ -1,12 +1,13 @@
|
|||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include <errno.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <sys/socket.h>
|
#include "ipc.h"
|
||||||
#include <sys/un.h>
|
|
||||||
#include "../ipc.h"
|
|
||||||
|
|
||||||
typedef struct Key Key;
|
typedef struct Key Key;
|
||||||
struct Key {
|
struct Key {
|
||||||
@@ -15,7 +16,7 @@ struct Key {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static Key *keys;
|
static Key *keys;
|
||||||
static int nkeys;
|
static size_t nkeys;
|
||||||
static int fd;
|
static int fd;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -28,13 +29,19 @@ die(char *msg)
|
|||||||
static void
|
static void
|
||||||
addkey(int k, int mod)
|
addkey(int k, int mod)
|
||||||
{
|
{
|
||||||
static int cap;
|
static size_t cap;
|
||||||
|
Key *p;
|
||||||
|
|
||||||
if(nkeys >= cap){
|
if(nkeys >= cap){
|
||||||
|
if(cap > SIZE_MAX / 2 / sizeof(Key)){
|
||||||
|
fprintf(stderr, "too many keys\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
cap = cap ? cap * 2 : 256;
|
cap = cap ? cap * 2 : 256;
|
||||||
keys = realloc(keys, cap * sizeof(Key));
|
p = realloc(keys, cap * sizeof(Key));
|
||||||
if(!keys)
|
if(!p)
|
||||||
die("realloc");
|
die("realloc");
|
||||||
|
keys = p;
|
||||||
}
|
}
|
||||||
keys[nkeys].k = k;
|
keys[nkeys].k = k;
|
||||||
keys[nkeys].mod = mod;
|
keys[nkeys].mod = mod;
|
||||||
@@ -76,20 +83,35 @@ loadkeys(char *file)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
if(nkeys == 0){
|
||||||
|
fprintf(stderr, "%s: no keys\n", file);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t
|
||||||
|
iterations(char *s)
|
||||||
|
{
|
||||||
|
uintmax_t n;
|
||||||
|
char *end;
|
||||||
|
|
||||||
|
if(*s == '\0' || *s == '-')
|
||||||
|
goto Bad;
|
||||||
|
errno = 0;
|
||||||
|
n = strtoumax(s, &end, 10);
|
||||||
|
if(errno != 0 || *end != '\0' || n == 0)
|
||||||
|
goto Bad;
|
||||||
|
return n;
|
||||||
|
Bad:
|
||||||
|
fprintf(stderr, "invalid iteration count: %s\n", s);
|
||||||
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dial(void)
|
dial(void)
|
||||||
{
|
{
|
||||||
struct sockaddr_un addr;
|
fd = ipcconnect();
|
||||||
|
|
||||||
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
||||||
if(fd < 0)
|
if(fd < 0)
|
||||||
die("socket");
|
|
||||||
memset(&addr, 0, sizeof(addr));
|
|
||||||
addr.sun_family = AF_UNIX;
|
|
||||||
snprintf(addr.sun_path, sizeof(addr.sun_path), IPCPATH, getuid());
|
|
||||||
if(connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
|
|
||||||
die("connect");
|
die("connect");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,10 +128,10 @@ sendkey(int key, int mod)
|
|||||||
static int
|
static int
|
||||||
readresp(void)
|
readresp(void)
|
||||||
{
|
{
|
||||||
char buf[Ipcfieldmax+1];
|
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
|
|
||||||
return ipcreadresp(fd, 0, buf, sizeof buf, NULL, 0, &resp);
|
return ipcreadresp(fd, 0, commit, preedit, &resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static double
|
static double
|
||||||
@@ -124,31 +146,38 @@ now(void)
|
|||||||
int
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int i, j, niter;
|
size_t j;
|
||||||
|
uint64_t i, niter, total;
|
||||||
double t0, t1, dt;
|
double t0, t1, dt;
|
||||||
|
|
||||||
if(argc < 2){
|
if(argc != 2 && argc != 3){
|
||||||
fprintf(stderr, "usage: bench file [niter]\n");
|
fprintf(stderr, "usage: bench file [niter]\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadkeys(argv[1]);
|
loadkeys(argv[1]);
|
||||||
niter = argc > 2 ? atoi(argv[2]) : 1000;
|
niter = argc == 3 ? iterations(argv[2]) : 1000;
|
||||||
|
if(niter > UINT64_MAX / nkeys){
|
||||||
|
fprintf(stderr, "iteration count overflows key total\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
total = niter * nkeys;
|
||||||
dial();
|
dial();
|
||||||
t0 = now();
|
t0 = now();
|
||||||
for(i = 0; i < niter; i++)
|
for(i = 0; i < niter; i++)
|
||||||
for(j = 0; j < nkeys; j++){
|
for(j = 0; j < nkeys; j++){
|
||||||
sendkey(keys[j].k, keys[j].mod);
|
sendkey(keys[j].k, keys[j].mod);
|
||||||
if(readresp() < 0){
|
if(readresp() < 0){
|
||||||
fprintf(stderr, "failed at iter %d key %d\n", i, j);
|
fprintf(stderr, "failed at iter %" PRIu64 " key %zu\n", i, j);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t1 = now();
|
t1 = now();
|
||||||
dt = t1 - t0;
|
dt = t1 - t0;
|
||||||
printf("%d iters x %d keys = %d keys\n", niter, nkeys, niter * nkeys);
|
printf("%" PRIu64 " iters x %zu keys = %" PRIu64 " keys\n",
|
||||||
|
niter, nkeys, total);
|
||||||
printf("%.3f ms total, %.3f us/key, %.3f us/iter\n",
|
printf("%.3f ms total, %.3f us/key, %.3f us/iter\n",
|
||||||
dt * 1000, dt * 1e6 / (niter * nkeys), dt * 1e6 / niter);
|
dt * 1000, dt * 1e6 / total, dt * 1e6 / niter);
|
||||||
close(fd);
|
close(fd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
bench/perf.data
BIN
bench/perf.data
Binary file not shown.
Binary file not shown.
82
compose.c
Normal file
82
compose.c
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
171
dat.h
171
dat.h
@@ -7,18 +7,24 @@
|
|||||||
#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,
|
||||||
|
|
||||||
Fontsz = 32,
|
|
||||||
Fontbase = 4,
|
|
||||||
Nglyphs = 0x20000,
|
|
||||||
Maxrunes = 64,
|
Maxrunes = 64,
|
||||||
Maxutf = Maxrunes * UTFmax + 1,
|
Maxutf = Maxrunes * UTFmax + 1,
|
||||||
};
|
};
|
||||||
@@ -26,16 +32,41 @@ enum
|
|||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Maxclients = 64,
|
Maxclients = 64,
|
||||||
Maxkouho = 32,
|
Ownerpoll = 200, /* ms between a frontend's owner checks while its preedit shows */
|
||||||
|
Maxkouho = 128,
|
||||||
Maxdisp = 9,
|
Maxdisp = 9,
|
||||||
Imgw = (Maxrunes + 3) * Fontsz,
|
|
||||||
Imgh = (Maxdisp + 1) * Fontsz,
|
/* 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,
|
||||||
Colsel = 0xcccccc,
|
Colsep = 0xd0d0d0,
|
||||||
|
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
|
||||||
{
|
{
|
||||||
@@ -43,53 +74,46 @@ struct Str
|
|||||||
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 dict;
|
Str raw;
|
||||||
};
|
|
||||||
|
|
||||||
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
|
||||||
{
|
{
|
||||||
int child;
|
|
||||||
int sibling;
|
|
||||||
char c;
|
|
||||||
char *val;
|
char *val;
|
||||||
int vlen;
|
int vlen;
|
||||||
|
int child;
|
||||||
|
int sibling;
|
||||||
|
Rune c;
|
||||||
};
|
};
|
||||||
|
|
||||||
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];
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Hmap Hmap;
|
enum
|
||||||
struct Hmap
|
|
||||||
{
|
{
|
||||||
int nbs;
|
TrieMiss,
|
||||||
int nsz;
|
TriePrefix,
|
||||||
int len;
|
TrieExact,
|
||||||
int cap;
|
|
||||||
uchar *nodes;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Lang Lang;
|
typedef struct Lang Lang;
|
||||||
@@ -101,67 +125,106 @@ 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;
|
||||||
Hmap *dict;
|
Trie *dict;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Im
|
struct Im
|
||||||
{
|
{
|
||||||
Lang *l;
|
Lang *l;
|
||||||
Str pre;
|
Str pre;
|
||||||
Str raw; /* physical keys for the current Telex preedit */
|
/* Telex history, or the short pending romaji in a Japanese mode. */
|
||||||
|
Str raw;
|
||||||
Str kouho[Maxkouho];
|
Str kouho[Maxkouho];
|
||||||
int nkouho;
|
int nkouho;
|
||||||
int sel;
|
int sel;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Drawcmd Drawcmd;
|
typedef struct Drawcmd Drawcmd;
|
||||||
|
typedef struct Caret Caret;
|
||||||
|
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;
|
typedef struct Keyres Keyres;
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Keypress,
|
||||||
|
Keyreset,
|
||||||
|
Keyrelease,
|
||||||
|
Keycaret,
|
||||||
|
Keycap,
|
||||||
|
};
|
||||||
struct Keyres
|
struct Keyres
|
||||||
{
|
{
|
||||||
int eaten;
|
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 commit;
|
||||||
Str preedit;
|
Str preedit;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Keyreq
|
struct Keyreq
|
||||||
{
|
{
|
||||||
|
void *owner; /* stable until the context's release is acknowledged */
|
||||||
|
int clientpre; /* the client draws the preedit; the popup does not */
|
||||||
|
int op;
|
||||||
u32int ks;
|
u32int ks;
|
||||||
u32int mod;
|
u32int mod;
|
||||||
int want; /* nonzero: include preedit in reply */
|
Caret caret;
|
||||||
|
Str surround; /* the client's own text just before the cursor */
|
||||||
Channel *reply;
|
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;
|
|
||||||
int lang;
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|||||||
157
dict.c
157
dict.c
@@ -1,107 +1,90 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
void
|
/* Appends the space-separated words of a node's entry to out[], up to max. */
|
||||||
dictlookup(Dictreq *req, Dictres *res)
|
static int
|
||||||
|
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;
|
||||||
|
|
||||||
res->key = req->pre;
|
if(nd->val == nil)
|
||||||
res->nkouho = 0;
|
return n;
|
||||||
res->lang = req->lang;
|
p = nd->val;
|
||||||
if(req->key.n == 0)
|
e = p + nd->vlen;
|
||||||
return;
|
while(n < max && p < e){
|
||||||
l = getlang(req->lang);
|
|
||||||
dict = l ? l->dict : nil;
|
|
||||||
if(dict == nil)
|
|
||||||
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){
|
|
||||||
while(p < e && *p == ' ')
|
while(p < e && *p == ' ')
|
||||||
p++;
|
p++;
|
||||||
if(p == e)
|
|
||||||
break;
|
|
||||||
sp = p;
|
sp = p;
|
||||||
while(p < e && *p != ' ')
|
while(p < e && *p != ' ')
|
||||||
p++;
|
p++;
|
||||||
sinit(&tmp, sp, p - sp);
|
if(sinit(&tmp, sp, p - sp) && tmp.n > 0)
|
||||||
if(scmp(&tmp, &req->key) != 0)
|
out[n++] = tmp;
|
||||||
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
|
||||||
dictthread(void*)
|
langinit(char *dir)
|
||||||
{
|
{
|
||||||
Dictreq req;
|
Lang *l;
|
||||||
Dictres res;
|
|
||||||
|
|
||||||
threadsetname("dict");
|
for(l = langs; l < langs + nlang; l++){
|
||||||
for(;;){
|
if(l->mapname != nil)
|
||||||
if(chanrecv(dictreqc, &req) < 0)
|
l->map = langopen(dir, l->mapname, "map");
|
||||||
break;
|
if(l->dictname != nil)
|
||||||
while(channbrecv(dictreqc, &req) > 0)
|
l->dict = langopen(dir, l->dictname, "dict");
|
||||||
;
|
|
||||||
dictlookup(&req, &res);
|
|
||||||
chansend(dictresc, &res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Hmap*
|
|
||||||
dictopen(char *path)
|
|
||||||
{
|
|
||||||
Hmap *h;
|
|
||||||
Biobuf *b;
|
|
||||||
Str key;
|
|
||||||
char *line, *tab;
|
|
||||||
int len;
|
|
||||||
|
|
||||||
b = Bopen(path, OREAD);
|
|
||||||
if(b == nil)
|
|
||||||
die("can't open: %s", path);
|
|
||||||
h = hmapalloc(4096);
|
|
||||||
while((line = Brdstr(b, '\n', 1)) != nil){
|
|
||||||
len = strlen(line);
|
|
||||||
if(len > 0 && line[len-1] == '\r')
|
|
||||||
line[--len] = '\0';
|
|
||||||
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);
|
|
||||||
hmapset(&h, &key, tab+1, len - (tab - line) - 1);
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
fn.h
45
fn.h
@@ -1,6 +1,7 @@
|
|||||||
void die(char*, ...);
|
void die(char*, ...);
|
||||||
|
|
||||||
void sinit(Str*, char*, int);
|
int 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*);
|
||||||
@@ -9,40 +10,52 @@ int scmp(Str*, Str*);
|
|||||||
int stoutf(Str*, char*, int);
|
int stoutf(Str*, char*, int);
|
||||||
Rune slastr(Str*);
|
Rune slastr(Str*);
|
||||||
|
|
||||||
Hmap* hmapalloc(int);
|
|
||||||
void hmapfree(Hmap*);
|
|
||||||
void hmapset(Hmap**, Str*, const char*, int);
|
|
||||||
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*);
|
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 mapinit(char*);
|
void langinit(char*);
|
||||||
void dictinit(char*);
|
|
||||||
|
|
||||||
void dictthread(void*);
|
int dictlookup(Trie*, Str*, Str*, int);
|
||||||
void dictlookup(Dictreq*, Dictres*);
|
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 backvi(Im*);
|
||||||
void dictsend(Im*, Str*);
|
|
||||||
|
|
||||||
int srvreadkey(int, Keyreq*);
|
void srvinit(void);
|
||||||
void srvthread(void*);
|
void srvthread(void*);
|
||||||
void ibusthread(void*);
|
void ibusthread(void*);
|
||||||
void waylandthread(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 fontinit(char*);
|
void textinit(void);
|
||||||
void putfont(u32int*, int, int, int, int, Rune);
|
void textclose(void);
|
||||||
|
int textwidth(Str*);
|
||||||
|
void textdraw(u32int*, int, int, int, int, int, u32int, Str*);
|
||||||
|
|||||||
231
font.c
231
font.c
@@ -1,137 +1,142 @@
|
|||||||
#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"
|
||||||
|
|
||||||
typedef struct Glyph Glyph;
|
static PangoFontMap *fontmap;
|
||||||
struct Glyph
|
static PangoContext *context;
|
||||||
|
static PangoLayout *layout;
|
||||||
|
|
||||||
|
void
|
||||||
|
textclose(void)
|
||||||
{
|
{
|
||||||
uchar *bmp;
|
if(layout != nil)
|
||||||
int w, h, ox, oy;
|
g_object_unref(layout);
|
||||||
};
|
if(context != nil)
|
||||||
|
g_object_unref(context);
|
||||||
enum { Maxfonts = 4 };
|
if(fontmap != nil)
|
||||||
|
g_object_unref(fontmap);
|
||||||
static stbtt_fontinfo fonts[Maxfonts];
|
layout = nil;
|
||||||
static uchar *fontdata[Maxfonts];
|
context = nil;
|
||||||
static float scale[Maxfonts];
|
fontmap = nil;
|
||||||
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
|
||||||
loadfont(char *path)
|
setfont(void)
|
||||||
{
|
{
|
||||||
int fd;
|
PangoFontDescription *font;
|
||||||
long sz, n;
|
PangoRectangle r;
|
||||||
|
int size;
|
||||||
|
|
||||||
if(nfonts >= Maxfonts)
|
font = pango_font_description_new();
|
||||||
die("too many fonts");
|
pango_font_description_set_family(font, "sans");
|
||||||
fd = open(path, OREAD);
|
pango_font_description_set_absolute_size(font, Fontsz * PANGO_SCALE);
|
||||||
if(fd < 0)
|
pango_layout_set_font_description(layout, font);
|
||||||
die("can't open font: %s", path);
|
pango_layout_set_text(layout, "Mg", -1);
|
||||||
sz = seek(fd, 0, 2);
|
pango_layout_get_pixel_extents(layout, nil, &r);
|
||||||
seek(fd, 0, 0);
|
if(r.height > 0){
|
||||||
fontdata[nfonts] = emalloc(sz);
|
/* Fontsz is the popup row height, not a point size. */
|
||||||
n = readn(fd, fontdata[nfonts], sz);
|
size = Fontsz * PANGO_SCALE * Fontsz / r.height;
|
||||||
close(fd);
|
pango_font_description_set_absolute_size(font, max(size, PANGO_SCALE));
|
||||||
if(n != sz)
|
pango_layout_set_font_description(layout, font);
|
||||||
die("can't read font: %s", path);
|
}
|
||||||
if(!stbtt_InitFont(&fonts[nfonts], fontdata[nfonts], stbtt_GetFontOffsetForIndex(fontdata[nfonts], 0)))
|
pango_font_description_free(font);
|
||||||
die("can't init font: %s", path);
|
}
|
||||||
scale[nfonts] = stbtt_ScaleForPixelHeight(&fonts[nfonts], Fontsz);
|
|
||||||
nfonts++;
|
void
|
||||||
|
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
|
||||||
isfont(char *name)
|
settext(Str *s)
|
||||||
{
|
{
|
||||||
char *p;
|
char utf[Maxutf];
|
||||||
|
int n;
|
||||||
|
|
||||||
p = strrchr(name, '.');
|
n = stoutf(s, utf, sizeof utf);
|
||||||
if(p == nil)
|
pango_layout_set_text(layout, utf, n);
|
||||||
|
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;
|
||||||
return strcmp(p, ".ttf") == 0 || strcmp(p, ".otf") == 0;
|
textextents(&r);
|
||||||
|
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
|
||||||
fontinit(char *dir)
|
textdraw(u32int *buf, int w, int h, int x, int y, int fit, u32int color,
|
||||||
|
Str *s)
|
||||||
{
|
{
|
||||||
int fd, n, i, a;
|
PangoRectangle r;
|
||||||
Dir *d;
|
cairo_surface_t *surface;
|
||||||
char path[256];
|
cairo_t *cr;
|
||||||
|
int b, g, red;
|
||||||
|
|
||||||
fd = open(dir, OREAD);
|
if(fit <= 0)
|
||||||
if(fd < 0)
|
return;
|
||||||
die("can't open font dir: %s", dir);
|
pango_layout_set_width(layout, fit * PANGO_SCALE);
|
||||||
n = dirreadall(fd, &d);
|
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
|
||||||
close(fd);
|
if(w <= 0 || h <= 0 || !settext(s))
|
||||||
if(n < 0)
|
return;
|
||||||
die("can't read font dir: %s", dir);
|
surface = cairo_image_surface_create_for_data((uchar*)buf,
|
||||||
for(i = 0; i < n; i++){
|
CAIRO_FORMAT_RGB24, w, h, w * sizeof buf[0]);
|
||||||
if(isfont(d[i].name)){
|
if(cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS){
|
||||||
snprint(path, sizeof path, "%s/%s", dir, d[i].name);
|
cairo_surface_destroy(surface);
|
||||||
loadfont(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(d);
|
|
||||||
if(nfonts == 0)
|
|
||||||
die("no fonts in %s", dir);
|
|
||||||
for(a = 0; a < 256; a++){
|
|
||||||
blendtab[0][a] = blend(Colbg, a);
|
|
||||||
blendtab[1][a] = blend(Colsel, a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
|
|
||||||
{
|
|
||||||
Glyph *g;
|
|
||||||
int i, j, a, sel, f;
|
|
||||||
int y0, j0, j1, x0, i0, i1;
|
|
||||||
u32int *p;
|
|
||||||
|
|
||||||
if(r >= Nglyphs)
|
|
||||||
return;
|
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;
|
|
||||||
}
|
}
|
||||||
|
cairo_surface_mark_dirty(surface);
|
||||||
y0 = py + g->oy + Fontsz - Fontbase;
|
cr = cairo_create(surface);
|
||||||
j0 = y0 < 0 ? -y0 : 0;
|
if(cairo_status(cr) == CAIRO_STATUS_SUCCESS){
|
||||||
j1 = y0 + g->h > h ? h - y0 : g->h;
|
pango_cairo_update_layout(cr, layout);
|
||||||
x0 = px + g->ox;
|
textextents(&r);
|
||||||
i0 = x0 < 0 ? -x0 : 0;
|
cairo_rectangle(cr, x, y, fit, Fontsz);
|
||||||
i1 = x0 + g->w > w ? w - x0 : g->w;
|
cairo_clip(cr);
|
||||||
for(j = j0; j < j1; j++){
|
cairo_move_to(cr, x - r.x, y + (Fontsz - r.height) / 2 - r.y);
|
||||||
for(i = i0; i < i1; i++){
|
red = color >> 16 & 0xff;
|
||||||
a = g->bmp[j * g->w + i];
|
g = color >> 8 & 0xff;
|
||||||
if(a > 0){
|
b = color & 0xff;
|
||||||
p = &buf[(y0 + j) * w + x0 + i];
|
cairo_set_source_rgb(cr, red / 255.0, g / 255.0, b / 255.0);
|
||||||
sel = (*p == Colsel) ? 1 : 0;
|
pango_cairo_show_layout(cr, layout);
|
||||||
*p = blendtab[sel][a];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
cairo_destroy(cr);
|
||||||
|
cairo_surface_flush(surface);
|
||||||
|
cairo_surface_destroy(surface);
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
41
gtk/Makefile
41
gtk/Makefile
@@ -1,24 +1,41 @@
|
|||||||
PROG = im-strans.so
|
PROG = im-strans.so
|
||||||
CFLAGS = -Wall -O2 -I.. $(shell pkg-config --cflags gtk+-3.0)
|
PKG_CONFIG ?= pkg-config
|
||||||
LDFLAGS = $(shell pkg-config --libs gtk+-3.0)
|
CFLAGS ?= -O2
|
||||||
DSTDIR ?= $(shell find /usr/lib* -type d \
|
GTK_CFLAGS = $(shell $(PKG_CONFIG) --cflags gtk+-3.0)
|
||||||
-path "*/gtk-3.0/*/immodules" 2>/dev/null | head -1)
|
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)
|
all: $(PROG)
|
||||||
|
|
||||||
$(PROG): main.c ../ipc.c ../ipc.h
|
$(PROG): main.c ../ipc.c ../ipc.h
|
||||||
$(CC) -shared -fPIC $(CFLAGS) -o $@ main.c ../ipc.c $(LDFLAGS)
|
$(CC) $(CPPFLAGS) -I.. $(GTK_CFLAGS) -Wall $(CFLAGS) $(LDFLAGS) \
|
||||||
|
-shared -fPIC -o $@ main.c ../ipc.c $(GTK_LIBS) $(LDLIBS)
|
||||||
|
|
||||||
|
CHECKDIR = test -n "$$module_dir" || { \
|
||||||
|
echo "cannot find GTK 3 module directory; set GTK_MODULE_DIR" >&2; \
|
||||||
|
exit 1; }
|
||||||
|
REFRESH = test -n "$(DESTDIR)" || { \
|
||||||
|
unset GTK_PATH GTK_IM_MODULE_FILE; $(GTK_QUERY_IMMODULES) --update-cache; }
|
||||||
|
|
||||||
install: $(PROG)
|
install: $(PROG)
|
||||||
test -n "$(DSTDIR)"
|
module_dir="$(GTK_MODULE_DIR)"; $(CHECKDIR); \
|
||||||
mkdir -p "$(DSTDIR)"
|
mkdir -p "$(DESTDIR)$$module_dir" && \
|
||||||
cp $(PROG) "$(DSTDIR)/"
|
cp "$(PROG)" "$(DESTDIR)$$module_dir/" && $(REFRESH)
|
||||||
gtk-query-immodules-3.0 --update-cache
|
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
test -n "$(DSTDIR)"
|
module_dir="$(GTK_MODULE_DIR)"; $(CHECKDIR); \
|
||||||
rm -f "$(DSTDIR)/$(PROG)"
|
rm -f "$(DESTDIR)$$module_dir/$(PROG)" && $(REFRESH)
|
||||||
gtk-query-immodules-3.0 --update-cache
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(PROG)
|
rm -f $(PROG)
|
||||||
|
|||||||
538
gtk/main.c
538
gtk/main.c
@@ -1,145 +1,314 @@
|
|||||||
#include <stdio.h>
|
#include <errno.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
|
||||||
{
|
{
|
||||||
GtkIMContext parent;
|
GtkIMContextSimple parent;
|
||||||
int fd;
|
int fd;
|
||||||
|
int usepreedit;
|
||||||
|
int ext;
|
||||||
|
int private;
|
||||||
|
int simpleactive;
|
||||||
|
int simpledone;
|
||||||
char pre[Ipcfieldmax+1];
|
char pre[Ipcfieldmax+1];
|
||||||
int prelen;
|
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
|
||||||
{
|
{
|
||||||
GtkIMContextClass parent;
|
GtkIMContextSimpleClass parent;
|
||||||
};
|
};
|
||||||
|
|
||||||
static GType imtype;
|
static GType imtype;
|
||||||
|
static GObjectClass *parentobject;
|
||||||
|
static GtkIMContextClass *parentim;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
srvconnect(Im *im)
|
setpreedit(Im *im, const char *s, int n)
|
||||||
{
|
{
|
||||||
struct sockaddr_un addr;
|
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)
|
if(im->fd >= 0)
|
||||||
return;
|
|
||||||
im->fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
||||||
if(im->fd < 0)
|
|
||||||
return;
|
|
||||||
memset(&addr, 0, sizeof(addr));
|
|
||||||
addr.sun_family = AF_UNIX;
|
|
||||||
snprintf(addr.sun_path, sizeof(addr.sun_path), IPCPATH, getuid());
|
|
||||||
if(connect(im->fd, (struct sockaddr*)&addr, sizeof(addr)) < 0){
|
|
||||||
close(im->fd);
|
close(im->fd);
|
||||||
im->fd = -1;
|
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
|
static void
|
||||||
srvclose(Im *im)
|
srvclose(Im *im)
|
||||||
{
|
{
|
||||||
int was;
|
srvdrop(im, 1);
|
||||||
|
}
|
||||||
|
|
||||||
if(im->fd >= 0)
|
/* Both buffers are char[Ipcfieldmax+1]. */
|
||||||
close(im->fd);
|
static int
|
||||||
im->fd = -1;
|
readresp(Im *im, int want, char *commit, char *pre, Ipcresp *resp)
|
||||||
was = im->prelen;
|
{
|
||||||
im->pre[0] = '\0';
|
if(ipcreadresp(im->fd, want, commit, pre, resp) < 0)
|
||||||
im->prelen = 0;
|
return -1;
|
||||||
if(was > 0){
|
if(!g_utf8_validate(commit, resp->commitlen, NULL) ||
|
||||||
g_signal_emit_by_name(im, "preedit-changed");
|
(want && !g_utf8_validate(pre, resp->preeditlen, NULL))){
|
||||||
g_signal_emit_by_name(im, "preedit-end");
|
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
|
static int
|
||||||
readresp(Im *im, char *buf, int bufsz)
|
sendcaret(Im *im)
|
||||||
{
|
{
|
||||||
Ipcresp resp;
|
unsigned char buf[Ipccaretsz];
|
||||||
int was;
|
int32_t x, y, h;
|
||||||
|
int valid;
|
||||||
|
|
||||||
was = im->prelen;
|
if(im->fd < 0 || !im->ext)
|
||||||
if(ipcreadresp(im->fd, 1, buf, bufsz, im->pre,
|
return 0;
|
||||||
sizeof(im->pre), &resp) < 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;
|
return -1;
|
||||||
im->prelen = resp.npreedit;
|
memcpy(im->sent, buf, sizeof buf);
|
||||||
if(was == 0 && im->prelen > 0)
|
im->caretsent = 1;
|
||||||
g_signal_emit_by_name(im, "preedit-start");
|
return 0;
|
||||||
if(was != 0 || im->prelen != 0)
|
|
||||||
g_signal_emit_by_name(im, "preedit-changed");
|
|
||||||
if(was > 0 && im->prelen == 0)
|
|
||||||
g_signal_emit_by_name(im, "preedit-end");
|
|
||||||
return resp.eaten;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t
|
/*
|
||||||
kget(uint32_t gdk)
|
* 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)
|
||||||
{
|
{
|
||||||
uint32_t u;
|
char *text, *p;
|
||||||
|
gint cursor;
|
||||||
|
int start;
|
||||||
|
|
||||||
u = gdk_keyval_to_unicode(gdk);
|
im->nsurround = 0;
|
||||||
if((gdk & 0xff000000) == 0x01000000 && u != 0)
|
if(!gtk_im_context_get_surrounding(ctx, &text, &cursor))
|
||||||
return u;
|
return;
|
||||||
if(gdk >= 0xff00 && gdk <= 0xffff)
|
if(cursor < 0 || cursor > (gint)strlen(text)){
|
||||||
return Kspec + (gdk - 0xff00);
|
g_free(text);
|
||||||
return u;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t
|
/* Sent only when it changes, as the caret is. */
|
||||||
mget(uint32_t state)
|
static int
|
||||||
|
sendsurround(Im *im, GtkIMContext *ctx)
|
||||||
{
|
{
|
||||||
uint32_t m;
|
unsigned char hdr[Ipcreqsz];
|
||||||
|
char was[Ipcfieldmax];
|
||||||
|
int nwas;
|
||||||
|
|
||||||
m = 0;
|
if(im->fd < 0 || !im->ext)
|
||||||
if(state & GDK_SHIFT_MASK)
|
return 0;
|
||||||
m |= Mshift;
|
nwas = im->nsurround;
|
||||||
if(state & GDK_CONTROL_MASK)
|
memcpy(was, im->surround, nwas);
|
||||||
m |= Mctrl;
|
readsurround(im, ctx);
|
||||||
if(state & GDK_MOD1_MASK)
|
if(im->surroundsent && nwas == im->nsurround &&
|
||||||
m |= Malt;
|
memcmp(was, im->surround, nwas) == 0)
|
||||||
if(state & GDK_SUPER_MASK)
|
return 0;
|
||||||
m |= Msuper;
|
ipcpacksurround(hdr, im->nsurround);
|
||||||
return m;
|
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)
|
||||||
|
{
|
||||||
|
unsigned char buf[Ipcreqsz];
|
||||||
|
char commit[Ipcfieldmax+1];
|
||||||
|
char pre[Ipcfieldmax+1];
|
||||||
|
Ipcresp resp;
|
||||||
|
|
||||||
|
if(im->fd >= 0)
|
||||||
|
return 0;
|
||||||
|
im->fd = ipcconnect();
|
||||||
|
if(im->fd < 0)
|
||||||
|
return -1;
|
||||||
|
/* Marker zero is an old daemon's harmless key-zero response. */
|
||||||
|
ipcpackcap(buf, im->usepreedit);
|
||||||
|
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||||
|
readresp(im, im->usepreedit, commit, pre, &resp) < 0){
|
||||||
|
srvclose(im);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
im->ext = resp.eaten != 0;
|
||||||
|
if(sendcaret(im) < 0){
|
||||||
|
srvclose(im);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
simplecommit(GtkIMContext *ctx, const char *s, Im *im)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
(void)s;
|
||||||
|
im->simpledone = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
simpleend(GtkIMContext *ctx, Im *im)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
im->simpledone = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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[Ipcreqsz];
|
||||||
char resp[Ipcfieldmax+1];
|
char commit[Ipcfieldmax+1];
|
||||||
|
char pre[Ipcfieldmax+1];
|
||||||
|
Ipcresp resp;
|
||||||
|
|
||||||
if(im->fd < 0){
|
if(im->fd < 0){
|
||||||
|
setpreedit(im, "", 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ipcpackreset(buf, im->usepreedit);
|
||||||
|
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||||
|
readresp(im, im->usepreedit, commit, pre, &resp) < 0){
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ipcpackreq(buf, 1, 0, Kesc);
|
setpreedit(im, "", 0);
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
if(commit[0] != '\0')
|
||||||
readresp(im, resp, sizeof(resp)) < 0)
|
g_signal_emit_by_name(im, "commit", commit);
|
||||||
srvclose(im);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
/* A password entry hidden the older way sets no purpose and signals nothing. */
|
||||||
plaincommit(GtkIMContext *ctx, uint32_t key, uint32_t mod)
|
static int
|
||||||
|
ishidden(Im *im)
|
||||||
{
|
{
|
||||||
char u[8];
|
GtkWidget *w;
|
||||||
int n;
|
|
||||||
|
|
||||||
if(mod & (Mctrl|Malt|Msuper))
|
if(im->win == NULL)
|
||||||
return FALSE;
|
return 0;
|
||||||
if(key < 0x20 || key >= Kspec)
|
gdk_window_get_user_data(im->win, (gpointer*)&w);
|
||||||
return FALSE;
|
return GTK_IS_ENTRY(w) && !gtk_entry_get_visibility(GTK_ENTRY(w));
|
||||||
n = g_unichar_to_utf8(key, u);
|
|
||||||
u[n] = '\0';
|
|
||||||
g_signal_emit_by_name(ctx, "commit", u);
|
|
||||||
return TRUE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
@@ -147,41 +316,56 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
|||||||
{
|
{
|
||||||
Im *im;
|
Im *im;
|
||||||
unsigned char buf[Ipcreqsz];
|
unsigned char buf[Ipcreqsz];
|
||||||
char resp[Ipcfieldmax+1];
|
char commit[Ipcfieldmax+1];
|
||||||
|
char pre[Ipcfieldmax+1];
|
||||||
uint32_t key, mod;
|
uint32_t key, mod;
|
||||||
int r;
|
Ipcresp resp;
|
||||||
GtkInputPurpose purpose;
|
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;
|
||||||
key = kget(ev->keyval);
|
|
||||||
if(key == 0)
|
|
||||||
return FALSE;
|
|
||||||
mod = mget(ev->state);
|
|
||||||
g_object_get(ctx, "input-purpose", &purpose, NULL);
|
|
||||||
if(purpose == GTK_INPUT_PURPOSE_PASSWORD || purpose == GTK_INPUT_PURPOSE_PIN)
|
|
||||||
return plaincommit(ctx, key, mod);
|
|
||||||
srvconnect(im);
|
|
||||||
if(im->fd < 0){
|
|
||||||
srvclose(im);
|
|
||||||
return plaincommit(ctx, key, mod);
|
|
||||||
}
|
}
|
||||||
ipcpackreq(buf, 1, mod, key);
|
if(im->simpleactive)
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0){
|
return simplefilter(ctx, ev, 0);
|
||||||
srvclose(im);
|
key = ipckeysym(ev->keyval, gdk_keyval_to_unicode(ev->keyval));
|
||||||
return plaincommit(ctx, key, mod);
|
mod = ipcmod(ev->state);
|
||||||
|
if(im->private || ishidden(im) || key == 0){
|
||||||
|
r = simplefilter(ctx, ev, 0);
|
||||||
|
/* A dead key starts a compose: pending text was typed first. */
|
||||||
|
if(im->simpleactive)
|
||||||
|
sendreset(im);
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
r = readresp(im, resp, sizeof(resp));
|
if(srvconnect(im) < 0)
|
||||||
if(r < 0){
|
return simplefilter(ctx, ev, 0);
|
||||||
|
/* A retained GDK window may have moved since the last cursor report. */
|
||||||
|
if(sendcaret(im) < 0 || sendsurround(im, ctx) < 0){
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
return plaincommit(ctx, key, mod);
|
return simplefilter(ctx, ev, 0);
|
||||||
}
|
}
|
||||||
if(resp[0] != '\0')
|
ipcpackreq(buf, im->usepreedit, mod, key);
|
||||||
g_signal_emit_by_name(ctx, "commit", resp);
|
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||||
if(r != 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 TRUE;
|
||||||
return plaincommit(ctx, key, mod);
|
return simplefilter(ctx, ev, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -192,6 +376,10 @@ getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs,
|
|||||||
PangoAttribute *u;
|
PangoAttribute *u;
|
||||||
|
|
||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
|
if(im->simpleactive){
|
||||||
|
parentim->get_preedit_string(ctx, str, attrs, cursor_pos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(str)
|
if(str)
|
||||||
*str = g_strdup(im->pre);
|
*str = g_strdup(im->pre);
|
||||||
if(attrs){
|
if(attrs){
|
||||||
@@ -210,32 +398,148 @@ getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs,
|
|||||||
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)
|
||||||
{
|
{
|
||||||
sendreset((Im*)ctx);
|
Im *im;
|
||||||
|
|
||||||
|
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
|
||||||
finalize(GObject *obj)
|
setusepreedit(GtkIMContext *ctx, gboolean use)
|
||||||
|
{
|
||||||
|
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;
|
||||||
if(im->fd >= 0)
|
srvdrop(im, 0);
|
||||||
close(im->fd);
|
dropwindow(im);
|
||||||
G_OBJECT_CLASS(g_type_class_peek_parent(G_OBJECT_GET_CLASS(obj)))->finalize(obj);
|
parentobject->dispose(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';
|
im->pre[0] = '\0';
|
||||||
im->prelen = 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
|
||||||
@@ -246,11 +550,16 @@ 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->get_preedit_string = getpreedit;
|
||||||
ic->reset = reset;
|
ic->reset = reset;
|
||||||
ic->focus_out = focusout;
|
ic->focus_out = focusout;
|
||||||
oc->finalize = finalize;
|
ic->set_use_preedit = setusepreedit;
|
||||||
|
ic->set_client_window = setclientwindow;
|
||||||
|
ic->set_cursor_location = setcursorlocation;
|
||||||
|
oc->dispose = dispose;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const GtkIMContextInfo info = {
|
static const GtkIMContextInfo info = {
|
||||||
@@ -275,7 +584,8 @@ im_module_init(GTypeModule *mod)
|
|||||||
0,
|
0,
|
||||||
(GInstanceInitFunc)init,
|
(GInstanceInitFunc)init,
|
||||||
};
|
};
|
||||||
imtype = g_type_module_register_type(mod, GTK_TYPE_IM_CONTEXT, "strans-gtk", &ti, 0);
|
imtype = g_type_module_register_type(mod, GTK_TYPE_IM_CONTEXT_SIMPLE,
|
||||||
|
"strans-gtk", &ti, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
G_MODULE_EXPORT void
|
G_MODULE_EXPORT void
|
||||||
|
|||||||
162
hash.c
162
hash.c
@@ -1,162 +0,0 @@
|
|||||||
#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)
|
|
||||||
{
|
|
||||||
void *store;
|
|
||||||
Hmap *h;
|
|
||||||
int nsz;
|
|
||||||
|
|
||||||
if(nbuckets < 1)
|
|
||||||
die("hmapalloc: no buckets");
|
|
||||||
nsz = Tagsize;
|
|
||||||
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[Maxutf];
|
|
||||||
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[Maxutf];
|
|
||||||
char *p;
|
|
||||||
int n;
|
|
||||||
|
|
||||||
n = stoutf(s, buf, sizeof(buf));
|
|
||||||
p = emalloc(n + 1);
|
|
||||||
memmove(p, buf, n);
|
|
||||||
p[n] = '\0';
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char*
|
|
||||||
memdup(const char *src, int n)
|
|
||||||
{
|
|
||||||
char *p;
|
|
||||||
|
|
||||||
if(n == 0)
|
|
||||||
return nil;
|
|
||||||
p = emalloc(n + 1);
|
|
||||||
memmove(p, src, n);
|
|
||||||
p[n] = '\0';
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
hmapfree(Hmap *h)
|
|
||||||
{
|
|
||||||
Hnode *n;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if(h == nil)
|
|
||||||
return;
|
|
||||||
for(i = 0; i < h->len; i++){
|
|
||||||
n = (Hnode*)(h->nodes + i * h->nsz);
|
|
||||||
if(!n->filled)
|
|
||||||
continue;
|
|
||||||
free(n->key);
|
|
||||||
free(n->val);
|
|
||||||
}
|
|
||||||
free(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
hmapset(Hmap **store, Str *key, const char *val, int vlen)
|
|
||||||
{
|
|
||||||
char *newval;
|
|
||||||
Hnode *n;
|
|
||||||
uchar *v;
|
|
||||||
Hmap *h;
|
|
||||||
int next;
|
|
||||||
vlong diff;
|
|
||||||
|
|
||||||
newval = memdup(val, vlen);
|
|
||||||
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;
|
|
||||||
free(n->val);
|
|
||||||
n->val = newval;
|
|
||||||
n->vlen = vlen;
|
|
||||||
}
|
|
||||||
@@ -1,955 +0,0 @@
|
|||||||
/* Generated by wayland-scanner 1.21.0 */
|
|
||||||
|
|
||||||
#ifndef INPUT_METHOD_UNSTABLE_V2_CLIENT_PROTOCOL_H
|
|
||||||
#define INPUT_METHOD_UNSTABLE_V2_CLIENT_PROTOCOL_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include "wayland-client.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @page page_input_method_unstable_v2 The input_method_unstable_v2 protocol
|
|
||||||
* Protocol for creating input methods
|
|
||||||
*
|
|
||||||
* @section page_desc_input_method_unstable_v2 Description
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @section page_ifaces_input_method_unstable_v2 Interfaces
|
|
||||||
* - @subpage page_iface_zwp_input_method_v2 - input method
|
|
||||||
* - @subpage page_iface_zwp_input_popup_surface_v2 - popup surface
|
|
||||||
* - @subpage page_iface_zwp_input_method_keyboard_grab_v2 - keyboard grab
|
|
||||||
* - @subpage page_iface_zwp_input_method_manager_v2 - input method manager
|
|
||||||
* @section page_copyright_input_method_unstable_v2 Copyright
|
|
||||||
* <pre>
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
struct wl_seat;
|
|
||||||
struct wl_surface;
|
|
||||||
struct zwp_input_method_keyboard_grab_v2;
|
|
||||||
struct zwp_input_method_manager_v2;
|
|
||||||
struct zwp_input_method_v2;
|
|
||||||
struct zwp_input_popup_surface_v2;
|
|
||||||
|
|
||||||
#ifndef ZWP_INPUT_METHOD_V2_INTERFACE
|
|
||||||
#define ZWP_INPUT_METHOD_V2_INTERFACE
|
|
||||||
/**
|
|
||||||
* @page page_iface_zwp_input_method_v2 zwp_input_method_v2
|
|
||||||
* @section page_iface_zwp_input_method_v2_desc Description
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* @section page_iface_zwp_input_method_v2_api API
|
|
||||||
* See @ref iface_zwp_input_method_v2.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @defgroup iface_zwp_input_method_v2 The zwp_input_method_v2 interface
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
extern const struct wl_interface zwp_input_method_v2_interface;
|
|
||||||
#endif
|
|
||||||
#ifndef ZWP_INPUT_POPUP_SURFACE_V2_INTERFACE
|
|
||||||
#define ZWP_INPUT_POPUP_SURFACE_V2_INTERFACE
|
|
||||||
/**
|
|
||||||
* @page page_iface_zwp_input_popup_surface_v2 zwp_input_popup_surface_v2
|
|
||||||
* @section page_iface_zwp_input_popup_surface_v2_desc Description
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* @section page_iface_zwp_input_popup_surface_v2_api API
|
|
||||||
* See @ref iface_zwp_input_popup_surface_v2.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @defgroup iface_zwp_input_popup_surface_v2 The zwp_input_popup_surface_v2 interface
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
extern const struct wl_interface zwp_input_popup_surface_v2_interface;
|
|
||||||
#endif
|
|
||||||
#ifndef ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_INTERFACE
|
|
||||||
#define ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_INTERFACE
|
|
||||||
/**
|
|
||||||
* @page page_iface_zwp_input_method_keyboard_grab_v2 zwp_input_method_keyboard_grab_v2
|
|
||||||
* @section page_iface_zwp_input_method_keyboard_grab_v2_desc Description
|
|
||||||
*
|
|
||||||
* The zwp_input_method_keyboard_grab_v2 interface represents an exclusive
|
|
||||||
* grab of the wl_keyboard interface associated with the seat.
|
|
||||||
* @section page_iface_zwp_input_method_keyboard_grab_v2_api API
|
|
||||||
* See @ref iface_zwp_input_method_keyboard_grab_v2.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @defgroup iface_zwp_input_method_keyboard_grab_v2 The zwp_input_method_keyboard_grab_v2 interface
|
|
||||||
*
|
|
||||||
* The zwp_input_method_keyboard_grab_v2 interface represents an exclusive
|
|
||||||
* grab of the wl_keyboard interface associated with the seat.
|
|
||||||
*/
|
|
||||||
extern const struct wl_interface zwp_input_method_keyboard_grab_v2_interface;
|
|
||||||
#endif
|
|
||||||
#ifndef ZWP_INPUT_METHOD_MANAGER_V2_INTERFACE
|
|
||||||
#define ZWP_INPUT_METHOD_MANAGER_V2_INTERFACE
|
|
||||||
/**
|
|
||||||
* @page page_iface_zwp_input_method_manager_v2 zwp_input_method_manager_v2
|
|
||||||
* @section page_iface_zwp_input_method_manager_v2_desc Description
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* @section page_iface_zwp_input_method_manager_v2_api API
|
|
||||||
* See @ref iface_zwp_input_method_manager_v2.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @defgroup iface_zwp_input_method_manager_v2 The zwp_input_method_manager_v2 interface
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
extern const struct wl_interface zwp_input_method_manager_v2_interface;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef ZWP_INPUT_METHOD_V2_ERROR_ENUM
|
|
||||||
#define ZWP_INPUT_METHOD_V2_ERROR_ENUM
|
|
||||||
enum zwp_input_method_v2_error {
|
|
||||||
/**
|
|
||||||
* wl_surface has another role
|
|
||||||
*/
|
|
||||||
ZWP_INPUT_METHOD_V2_ERROR_ROLE = 0,
|
|
||||||
};
|
|
||||||
#endif /* ZWP_INPUT_METHOD_V2_ERROR_ENUM */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
* @struct zwp_input_method_v2_listener
|
|
||||||
*/
|
|
||||||
struct zwp_input_method_v2_listener {
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*activate)(void *data,
|
|
||||||
struct zwp_input_method_v2 *zwp_input_method_v2);
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*deactivate)(void *data,
|
|
||||||
struct zwp_input_method_v2 *zwp_input_method_v2);
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*surrounding_text)(void *data,
|
|
||||||
struct zwp_input_method_v2 *zwp_input_method_v2,
|
|
||||||
const char *text,
|
|
||||||
uint32_t cursor,
|
|
||||||
uint32_t anchor);
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*text_change_cause)(void *data,
|
|
||||||
struct zwp_input_method_v2 *zwp_input_method_v2,
|
|
||||||
uint32_t cause);
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*content_type)(void *data,
|
|
||||||
struct zwp_input_method_v2 *zwp_input_method_v2,
|
|
||||||
uint32_t hint,
|
|
||||||
uint32_t purpose);
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*done)(void *data,
|
|
||||||
struct zwp_input_method_v2 *zwp_input_method_v2);
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*unavailable)(void *data,
|
|
||||||
struct zwp_input_method_v2 *zwp_input_method_v2);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
static inline int
|
|
||||||
zwp_input_method_v2_add_listener(struct zwp_input_method_v2 *zwp_input_method_v2,
|
|
||||||
const struct zwp_input_method_v2_listener *listener, void *data)
|
|
||||||
{
|
|
||||||
return wl_proxy_add_listener((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
(void (**)(void)) listener, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ZWP_INPUT_METHOD_V2_COMMIT_STRING 0
|
|
||||||
#define ZWP_INPUT_METHOD_V2_SET_PREEDIT_STRING 1
|
|
||||||
#define ZWP_INPUT_METHOD_V2_DELETE_SURROUNDING_TEXT 2
|
|
||||||
#define ZWP_INPUT_METHOD_V2_COMMIT 3
|
|
||||||
#define ZWP_INPUT_METHOD_V2_GET_INPUT_POPUP_SURFACE 4
|
|
||||||
#define ZWP_INPUT_METHOD_V2_GRAB_KEYBOARD 5
|
|
||||||
#define ZWP_INPUT_METHOD_V2_DESTROY 6
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_ACTIVATE_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_DEACTIVATE_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_SURROUNDING_TEXT_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_TEXT_CHANGE_CAUSE_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_CONTENT_TYPE_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_DONE_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_UNAVAILABLE_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_COMMIT_STRING_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_SET_PREEDIT_STRING_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_DELETE_SURROUNDING_TEXT_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_COMMIT_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_GET_INPUT_POPUP_SURFACE_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_GRAB_KEYBOARD_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_DESTROY_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_method_v2 */
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_v2_set_user_data(struct zwp_input_method_v2 *zwp_input_method_v2, void *user_data)
|
|
||||||
{
|
|
||||||
wl_proxy_set_user_data((struct wl_proxy *) zwp_input_method_v2, user_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_method_v2 */
|
|
||||||
static inline void *
|
|
||||||
zwp_input_method_v2_get_user_data(struct zwp_input_method_v2 *zwp_input_method_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_user_data((struct wl_proxy *) zwp_input_method_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t
|
|
||||||
zwp_input_method_v2_get_version(struct zwp_input_method_v2 *zwp_input_method_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_v2_commit_string(struct zwp_input_method_v2 *zwp_input_method_v2, const char *text)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
ZWP_INPUT_METHOD_V2_COMMIT_STRING, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2), 0, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_v2_set_preedit_string(struct zwp_input_method_v2 *zwp_input_method_v2, const char *text, int32_t cursor_begin, int32_t cursor_end)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
ZWP_INPUT_METHOD_V2_SET_PREEDIT_STRING, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2), 0, text, cursor_begin, cursor_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_v2_delete_surrounding_text(struct zwp_input_method_v2 *zwp_input_method_v2, uint32_t before_length, uint32_t after_length)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
ZWP_INPUT_METHOD_V2_DELETE_SURROUNDING_TEXT, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2), 0, before_length, after_length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_v2_commit(struct zwp_input_method_v2 *zwp_input_method_v2, uint32_t serial)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
ZWP_INPUT_METHOD_V2_COMMIT, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2), 0, serial);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline struct zwp_input_popup_surface_v2 *
|
|
||||||
zwp_input_method_v2_get_input_popup_surface(struct zwp_input_method_v2 *zwp_input_method_v2, struct wl_surface *surface)
|
|
||||||
{
|
|
||||||
struct wl_proxy *id;
|
|
||||||
|
|
||||||
id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
ZWP_INPUT_METHOD_V2_GET_INPUT_POPUP_SURFACE, &zwp_input_popup_surface_v2_interface, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2), 0, NULL, surface);
|
|
||||||
|
|
||||||
return (struct zwp_input_popup_surface_v2 *) id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline struct zwp_input_method_keyboard_grab_v2 *
|
|
||||||
zwp_input_method_v2_grab_keyboard(struct zwp_input_method_v2 *zwp_input_method_v2)
|
|
||||||
{
|
|
||||||
struct wl_proxy *keyboard;
|
|
||||||
|
|
||||||
keyboard = wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
ZWP_INPUT_METHOD_V2_GRAB_KEYBOARD, &zwp_input_method_keyboard_grab_v2_interface, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2), 0, NULL);
|
|
||||||
|
|
||||||
return (struct zwp_input_method_keyboard_grab_v2 *) keyboard;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_v2_destroy(struct zwp_input_method_v2 *zwp_input_method_v2)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
ZWP_INPUT_METHOD_V2_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2), WL_MARSHAL_FLAG_DESTROY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_popup_surface_v2
|
|
||||||
* @struct zwp_input_popup_surface_v2_listener
|
|
||||||
*/
|
|
||||||
struct zwp_input_popup_surface_v2_listener {
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*text_input_rectangle)(void *data,
|
|
||||||
struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2,
|
|
||||||
int32_t x,
|
|
||||||
int32_t y,
|
|
||||||
int32_t width,
|
|
||||||
int32_t height);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_popup_surface_v2
|
|
||||||
*/
|
|
||||||
static inline int
|
|
||||||
zwp_input_popup_surface_v2_add_listener(struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2,
|
|
||||||
const struct zwp_input_popup_surface_v2_listener *listener, void *data)
|
|
||||||
{
|
|
||||||
return wl_proxy_add_listener((struct wl_proxy *) zwp_input_popup_surface_v2,
|
|
||||||
(void (**)(void)) listener, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ZWP_INPUT_POPUP_SURFACE_V2_DESTROY 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_popup_surface_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_POPUP_SURFACE_V2_TEXT_INPUT_RECTANGLE_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_popup_surface_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_POPUP_SURFACE_V2_DESTROY_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_popup_surface_v2 */
|
|
||||||
static inline void
|
|
||||||
zwp_input_popup_surface_v2_set_user_data(struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2, void *user_data)
|
|
||||||
{
|
|
||||||
wl_proxy_set_user_data((struct wl_proxy *) zwp_input_popup_surface_v2, user_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_popup_surface_v2 */
|
|
||||||
static inline void *
|
|
||||||
zwp_input_popup_surface_v2_get_user_data(struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_user_data((struct wl_proxy *) zwp_input_popup_surface_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t
|
|
||||||
zwp_input_popup_surface_v2_get_version(struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_version((struct wl_proxy *) zwp_input_popup_surface_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_popup_surface_v2
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_popup_surface_v2_destroy(struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_popup_surface_v2,
|
|
||||||
ZWP_INPUT_POPUP_SURFACE_V2_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_popup_surface_v2), WL_MARSHAL_FLAG_DESTROY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
* @struct zwp_input_method_keyboard_grab_v2_listener
|
|
||||||
*/
|
|
||||||
struct zwp_input_method_keyboard_grab_v2_listener {
|
|
||||||
/**
|
|
||||||
* keyboard mapping
|
|
||||||
*
|
|
||||||
* This event provides a file descriptor to the client which can
|
|
||||||
* be memory-mapped to provide a keyboard mapping description.
|
|
||||||
* @param format keymap format
|
|
||||||
* @param fd keymap file descriptor
|
|
||||||
* @param size keymap size, in bytes
|
|
||||||
*/
|
|
||||||
void (*keymap)(void *data,
|
|
||||||
struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2,
|
|
||||||
uint32_t format,
|
|
||||||
int32_t fd,
|
|
||||||
uint32_t size);
|
|
||||||
/**
|
|
||||||
* key event
|
|
||||||
*
|
|
||||||
* A key was pressed or released. The time argument is a
|
|
||||||
* timestamp with millisecond granularity, with an undefined base.
|
|
||||||
* @param serial serial number of the key event
|
|
||||||
* @param time timestamp with millisecond granularity
|
|
||||||
* @param key key that produced the event
|
|
||||||
* @param state physical state of the key
|
|
||||||
*/
|
|
||||||
void (*key)(void *data,
|
|
||||||
struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2,
|
|
||||||
uint32_t serial,
|
|
||||||
uint32_t time,
|
|
||||||
uint32_t key,
|
|
||||||
uint32_t state);
|
|
||||||
/**
|
|
||||||
* modifier and group state
|
|
||||||
*
|
|
||||||
* Notifies clients that the modifier and/or group state has
|
|
||||||
* changed, and it should update its local state.
|
|
||||||
* @param serial serial number of the modifiers event
|
|
||||||
* @param mods_depressed depressed modifiers
|
|
||||||
* @param mods_latched latched modifiers
|
|
||||||
* @param mods_locked locked modifiers
|
|
||||||
* @param group keyboard layout
|
|
||||||
*/
|
|
||||||
void (*modifiers)(void *data,
|
|
||||||
struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2,
|
|
||||||
uint32_t serial,
|
|
||||||
uint32_t mods_depressed,
|
|
||||||
uint32_t mods_latched,
|
|
||||||
uint32_t mods_locked,
|
|
||||||
uint32_t group);
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
* @param rate the rate of repeating keys in characters per second
|
|
||||||
* @param delay delay in milliseconds since key down until repeating starts
|
|
||||||
*/
|
|
||||||
void (*repeat_info)(void *data,
|
|
||||||
struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2,
|
|
||||||
int32_t rate,
|
|
||||||
int32_t delay);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
*/
|
|
||||||
static inline int
|
|
||||||
zwp_input_method_keyboard_grab_v2_add_listener(struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2,
|
|
||||||
const struct zwp_input_method_keyboard_grab_v2_listener *listener, void *data)
|
|
||||||
{
|
|
||||||
return wl_proxy_add_listener((struct wl_proxy *) zwp_input_method_keyboard_grab_v2,
|
|
||||||
(void (**)(void)) listener, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_RELEASE 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_KEYMAP_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_KEY_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_MODIFIERS_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_REPEAT_INFO_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_RELEASE_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_method_keyboard_grab_v2 */
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_keyboard_grab_v2_set_user_data(struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2, void *user_data)
|
|
||||||
{
|
|
||||||
wl_proxy_set_user_data((struct wl_proxy *) zwp_input_method_keyboard_grab_v2, user_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_method_keyboard_grab_v2 */
|
|
||||||
static inline void *
|
|
||||||
zwp_input_method_keyboard_grab_v2_get_user_data(struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_user_data((struct wl_proxy *) zwp_input_method_keyboard_grab_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t
|
|
||||||
zwp_input_method_keyboard_grab_v2_get_version(struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_version((struct wl_proxy *) zwp_input_method_keyboard_grab_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_method_keyboard_grab_v2 */
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_keyboard_grab_v2_destroy(struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2)
|
|
||||||
{
|
|
||||||
wl_proxy_destroy((struct wl_proxy *) zwp_input_method_keyboard_grab_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_keyboard_grab_v2_release(struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_keyboard_grab_v2,
|
|
||||||
ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_keyboard_grab_v2), WL_MARSHAL_FLAG_DESTROY);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ZWP_INPUT_METHOD_MANAGER_V2_GET_INPUT_METHOD 0
|
|
||||||
#define ZWP_INPUT_METHOD_MANAGER_V2_DESTROY 1
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_manager_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_MANAGER_V2_GET_INPUT_METHOD_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_manager_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_MANAGER_V2_DESTROY_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_method_manager_v2 */
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_manager_v2_set_user_data(struct zwp_input_method_manager_v2 *zwp_input_method_manager_v2, void *user_data)
|
|
||||||
{
|
|
||||||
wl_proxy_set_user_data((struct wl_proxy *) zwp_input_method_manager_v2, user_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_method_manager_v2 */
|
|
||||||
static inline void *
|
|
||||||
zwp_input_method_manager_v2_get_user_data(struct zwp_input_method_manager_v2 *zwp_input_method_manager_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_user_data((struct wl_proxy *) zwp_input_method_manager_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t
|
|
||||||
zwp_input_method_manager_v2_get_version(struct zwp_input_method_manager_v2 *zwp_input_method_manager_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_version((struct wl_proxy *) zwp_input_method_manager_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_manager_v2
|
|
||||||
*
|
|
||||||
* Request a new input zwp_input_method_v2 object associated with a given
|
|
||||||
* seat.
|
|
||||||
*/
|
|
||||||
static inline struct zwp_input_method_v2 *
|
|
||||||
zwp_input_method_manager_v2_get_input_method(struct zwp_input_method_manager_v2 *zwp_input_method_manager_v2, struct wl_seat *seat)
|
|
||||||
{
|
|
||||||
struct wl_proxy *input_method;
|
|
||||||
|
|
||||||
input_method = wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_manager_v2,
|
|
||||||
ZWP_INPUT_METHOD_MANAGER_V2_GET_INPUT_METHOD, &zwp_input_method_v2_interface, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_manager_v2), 0, seat, NULL);
|
|
||||||
|
|
||||||
return (struct zwp_input_method_v2 *) input_method;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_manager_v2
|
|
||||||
*
|
|
||||||
* Destroys the zwp_input_method_manager_v2 object.
|
|
||||||
*
|
|
||||||
* The zwp_input_method_v2 objects originating from it remain valid.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_manager_v2_destroy(struct zwp_input_method_manager_v2 *zwp_input_method_manager_v2)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_manager_v2,
|
|
||||||
ZWP_INPUT_METHOD_MANAGER_V2_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_manager_v2), WL_MARSHAL_FLAG_DESTROY);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
/* Generated by wayland-scanner 1.21.0 */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "wayland-util.h"
|
|
||||||
|
|
||||||
#ifndef __has_attribute
|
|
||||||
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
|
|
||||||
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
|
|
||||||
#else
|
|
||||||
#define WL_PRIVATE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern const struct wl_interface wl_seat_interface;
|
|
||||||
extern const struct wl_interface wl_surface_interface;
|
|
||||||
extern const struct wl_interface zwp_input_method_keyboard_grab_v2_interface;
|
|
||||||
extern const struct wl_interface zwp_input_method_v2_interface;
|
|
||||||
extern const struct wl_interface zwp_input_popup_surface_v2_interface;
|
|
||||||
|
|
||||||
static const struct wl_interface *input_method_unstable_v2_types[] = {
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
&zwp_input_popup_surface_v2_interface,
|
|
||||||
&wl_surface_interface,
|
|
||||||
&zwp_input_method_keyboard_grab_v2_interface,
|
|
||||||
&wl_seat_interface,
|
|
||||||
&zwp_input_method_v2_interface,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_input_method_v2_requests[] = {
|
|
||||||
{ "commit_string", "s", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "set_preedit_string", "sii", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "delete_surrounding_text", "uu", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "commit", "u", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "get_input_popup_surface", "no", input_method_unstable_v2_types + 5 },
|
|
||||||
{ "grab_keyboard", "n", input_method_unstable_v2_types + 7 },
|
|
||||||
{ "destroy", "", input_method_unstable_v2_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_input_method_v2_events[] = {
|
|
||||||
{ "activate", "", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "deactivate", "", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "surrounding_text", "suu", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "text_change_cause", "u", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "content_type", "uu", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "done", "", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "unavailable", "", input_method_unstable_v2_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
WL_PRIVATE const struct wl_interface zwp_input_method_v2_interface = {
|
|
||||||
"zwp_input_method_v2", 1,
|
|
||||||
7, zwp_input_method_v2_requests,
|
|
||||||
7, zwp_input_method_v2_events,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_input_popup_surface_v2_requests[] = {
|
|
||||||
{ "destroy", "", input_method_unstable_v2_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_input_popup_surface_v2_events[] = {
|
|
||||||
{ "text_input_rectangle", "iiii", input_method_unstable_v2_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
WL_PRIVATE const struct wl_interface zwp_input_popup_surface_v2_interface = {
|
|
||||||
"zwp_input_popup_surface_v2", 1,
|
|
||||||
1, zwp_input_popup_surface_v2_requests,
|
|
||||||
1, zwp_input_popup_surface_v2_events,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_input_method_keyboard_grab_v2_requests[] = {
|
|
||||||
{ "release", "", input_method_unstable_v2_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_input_method_keyboard_grab_v2_events[] = {
|
|
||||||
{ "keymap", "uhu", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "key", "uuuu", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "modifiers", "uuuuu", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "repeat_info", "ii", input_method_unstable_v2_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
WL_PRIVATE const struct wl_interface zwp_input_method_keyboard_grab_v2_interface = {
|
|
||||||
"zwp_input_method_keyboard_grab_v2", 1,
|
|
||||||
1, zwp_input_method_keyboard_grab_v2_requests,
|
|
||||||
4, zwp_input_method_keyboard_grab_v2_events,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_input_method_manager_v2_requests[] = {
|
|
||||||
{ "get_input_method", "on", input_method_unstable_v2_types + 8 },
|
|
||||||
{ "destroy", "", input_method_unstable_v2_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
WL_PRIVATE const struct wl_interface zwp_input_method_manager_v2_interface = {
|
|
||||||
"zwp_input_method_manager_v2", 1,
|
|
||||||
2, zwp_input_method_manager_v2_requests,
|
|
||||||
0, NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
429
ipc.c
429
ipc.c
@@ -1,12 +1,86 @@
|
|||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
#include "ipc.h"
|
#include "ipc.h"
|
||||||
|
|
||||||
#if !defined(MSG_NOSIGNAL) && !defined(SO_NOSIGPIPE)
|
static int64_t
|
||||||
#error "ipcsend requires MSG_NOSIGNAL or SO_NOSIGPIPE"
|
nowms(void)
|
||||||
#endif
|
{
|
||||||
|
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
|
static void
|
||||||
putlen(unsigned char p[Ipclensz], size_t n)
|
putlen(unsigned char p[Ipclensz], size_t n)
|
||||||
@@ -21,11 +95,123 @@ getlen(const unsigned char p[Ipclensz])
|
|||||||
return p[0] | (p[1] << 8);
|
return p[0] | (p[1] << 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
ipcpackreq(unsigned char req[Ipcreqsz], int want, unsigned int mod,
|
put32(unsigned char *p, int32_t v)
|
||||||
unsigned int key)
|
|
||||||
{
|
{
|
||||||
req[0] = want != 0;
|
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[1] = mod;
|
||||||
req[2] = key;
|
req[2] = key;
|
||||||
req[3] = key >> 8;
|
req[3] = key >> 8;
|
||||||
@@ -34,41 +220,131 @@ ipcpackreq(unsigned char req[Ipcreqsz], int want, unsigned int mod,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, unsigned int *mod,
|
ipcpackreset(unsigned char req[Ipcreqsz], int want)
|
||||||
unsigned int *key)
|
|
||||||
{
|
{
|
||||||
*want = req[0] != 0;
|
memset(req, 0, Ipcreqsz);
|
||||||
|
req[0] = Ipcreqreset | (want ? Ipcreqwant : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
*mod = req[1] & Mmask;
|
||||||
*key = (unsigned int)req[2] |
|
*key = (uint32_t)req[2] |
|
||||||
((unsigned int)req[3] << 8) |
|
((uint32_t)req[3] << 8) |
|
||||||
((unsigned int)req[4] << 16) |
|
((uint32_t)req[4] << 16) |
|
||||||
((unsigned int)req[5] << 24);
|
((uint32_t)req[5] << 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
ipcpackresp(unsigned char *dst, size_t cap, int eaten,
|
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,
|
const char *commit, size_t ncommit, const char *preedit, size_t npreedit,
|
||||||
int want)
|
int want)
|
||||||
{
|
{
|
||||||
size_t n;
|
size_t n;
|
||||||
|
|
||||||
if(ncommit > Ipcfieldmax || npreedit > Ipcfieldmax)
|
if(ncommit > Ipcfieldmax || npreedit > Ipcfieldmax ||
|
||||||
return -1;
|
del < 0 || del > 0xff)
|
||||||
if((ncommit > 0 && commit == NULL) ||
|
|
||||||
(want && npreedit > 0 && preedit == NULL))
|
|
||||||
return -1;
|
return -1;
|
||||||
n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0);
|
n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0);
|
||||||
if(dst == NULL || cap < n)
|
if(cap < n)
|
||||||
return -1;
|
return -1;
|
||||||
dst[0] = eaten != 0;
|
dst[0] = eaten != 0;
|
||||||
putlen(dst + 1, ncommit);
|
dst[1] = del;
|
||||||
if(ncommit > 0)
|
putlen(dst + 2, ncommit);
|
||||||
memcpy(dst + Ipcresphdrsz, commit, ncommit);
|
memcpy(dst + Ipcresphdrsz, commit, ncommit);
|
||||||
if(want){
|
if(want){
|
||||||
putlen(dst + Ipcresphdrsz + ncommit, npreedit);
|
putlen(dst + Ipcresphdrsz + ncommit, npreedit);
|
||||||
if(npreedit > 0)
|
memcpy(dst + Ipcresphdrsz + ncommit + Ipclensz, preedit, npreedit);
|
||||||
memcpy(dst + Ipcresphdrsz + ncommit + Ipclensz,
|
|
||||||
preedit, npreedit);
|
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
@@ -97,90 +373,79 @@ ipcsend(int fd, const void *buf, size_t n)
|
|||||||
{
|
{
|
||||||
const unsigned char *p;
|
const unsigned char *p;
|
||||||
ssize_t r;
|
ssize_t r;
|
||||||
int flags;
|
int64_t until;
|
||||||
#if !defined(MSG_NOSIGNAL) && defined(SO_NOSIGPIPE)
|
|
||||||
int one, sr;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
p = buf;
|
p = buf;
|
||||||
#ifdef MSG_NOSIGNAL
|
until = deadline();
|
||||||
flags = MSG_NOSIGNAL;
|
|
||||||
#else
|
|
||||||
flags = 0;
|
|
||||||
#ifdef SO_NOSIGPIPE
|
|
||||||
one = 1;
|
|
||||||
do
|
|
||||||
sr = setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof one);
|
|
||||||
while(sr < 0 && errno == EINTR);
|
|
||||||
if(sr < 0)
|
|
||||||
return -1;
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
while(n > 0){
|
while(n > 0){
|
||||||
r = send(fd, p, n, flags);
|
r = send(fd, p, n, MSG_NOSIGNAL|MSG_DONTWAIT);
|
||||||
if(r < 0 && errno == EINTR)
|
if(r < 0 && errno == EINTR)
|
||||||
continue;
|
continue;
|
||||||
if(r <= 0)
|
if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){
|
||||||
|
if(waitfd(fd, POLLOUT, until) < 0)
|
||||||
|
return -1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(r < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
if(r == 0){
|
||||||
|
errno = EPIPE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
p += r;
|
p += r;
|
||||||
n -= r;
|
n -= r;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Reads a length-prefixed field into dst[Ipcfieldmax+1]. */
|
||||||
static int
|
static int
|
||||||
readfield(int fd, size_t n, char *dst, size_t cap)
|
readfield(int fd, char *dst, int64_t until)
|
||||||
{
|
{
|
||||||
unsigned char discard[128];
|
unsigned char len[Ipclensz];
|
||||||
size_t keep, part;
|
size_t n;
|
||||||
|
|
||||||
if(cap > 0 && dst == NULL)
|
if(readwait(fd, len, sizeof len, until) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
keep = 0;
|
n = getlen(len);
|
||||||
if(cap > 0)
|
if(n > Ipcfieldmax){
|
||||||
keep = n >= cap ? cap - 1 : n;
|
errno = EPROTO;
|
||||||
if(keep > 0 && ipcreadn(fd, dst, keep) < 0)
|
|
||||||
return -1;
|
return -1;
|
||||||
if(cap > 0)
|
|
||||||
dst[keep] = '\0';
|
|
||||||
n -= keep;
|
|
||||||
while(n > 0){
|
|
||||||
part = n < sizeof discard ? n : sizeof discard;
|
|
||||||
if(ipcreadn(fd, discard, part) < 0)
|
|
||||||
return -1;
|
|
||||||
n -= part;
|
|
||||||
}
|
}
|
||||||
return 0;
|
if(readwait(fd, dst, n, until) < 0)
|
||||||
|
return -1;
|
||||||
|
dst[n] = '\0';
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
ipcreadresp(int fd, int want, char *commit, size_t ccap,
|
ipcreadresp(int fd, int want, char *commit, char *preedit, Ipcresp *resp)
|
||||||
char *preedit, size_t pcap, Ipcresp *resp)
|
|
||||||
{
|
{
|
||||||
unsigned char hdr[Ipcresphdrsz], npreedit[Ipclensz];
|
unsigned char head[2];
|
||||||
|
int64_t until;
|
||||||
|
int n;
|
||||||
|
|
||||||
if(resp == NULL || (ccap > 0 && commit == NULL) ||
|
commit[0] = '\0';
|
||||||
(pcap > 0 && preedit == NULL))
|
preedit[0] = '\0';
|
||||||
return -1;
|
|
||||||
if(ccap > 0)
|
|
||||||
commit[0] = '\0';
|
|
||||||
if(pcap > 0)
|
|
||||||
preedit[0] = '\0';
|
|
||||||
memset(resp, 0, sizeof *resp);
|
memset(resp, 0, sizeof *resp);
|
||||||
if(ipcreadn(fd, hdr, sizeof hdr) < 0)
|
until = deadline();
|
||||||
|
if(readwait(fd, head, sizeof head, until) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
resp->eaten = hdr[0] != 0;
|
if(head[0] > 1){
|
||||||
resp->ncommit = getlen(hdr + 1);
|
errno = EPROTO;
|
||||||
if(resp->ncommit > Ipcfieldmax)
|
|
||||||
return -1;
|
return -1;
|
||||||
if(readfield(fd, resp->ncommit, commit, ccap) < 0)
|
}
|
||||||
|
resp->eaten = head[0];
|
||||||
|
resp->del = head[1];
|
||||||
|
n = readfield(fd, commit, until);
|
||||||
|
if(n < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
resp->commitlen = n;
|
||||||
if(!want)
|
if(!want)
|
||||||
return 0;
|
return 0;
|
||||||
if(ipcreadn(fd, npreedit, sizeof npreedit) < 0)
|
n = readfield(fd, preedit, until);
|
||||||
|
if(n < 0)
|
||||||
return -1;
|
return -1;
|
||||||
resp->npreedit = getlen(npreedit);
|
resp->preeditlen = n;
|
||||||
if(resp->npreedit > Ipcfieldmax)
|
return 0;
|
||||||
return -1;
|
|
||||||
return readfield(fd, resp->npreedit, preedit, pcap);
|
|
||||||
}
|
}
|
||||||
|
|||||||
92
ipc.h
92
ipc.h
@@ -1,21 +1,42 @@
|
|||||||
#ifndef STRANS_IPC_H
|
|
||||||
#define STRANS_IPC_H
|
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
#define IPCPATH "/tmp/strans.%d"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Request: [want-preedit, modifiers, key byte 0, ..., key byte 3].
|
* 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.
|
* Only Mmask modifier bits are retained.
|
||||||
* Response: [eaten, commit-length-low, commit-length-high, commit...],
|
* Response: [eaten, take-back, commit-length-low, commit-length-high,
|
||||||
* followed, when requested, by
|
* commit...], followed, when requested, by
|
||||||
* [preedit-length-low, preedit-length-high, preedit...].
|
* [preedit-length-low, preedit-length-high, preedit...].
|
||||||
* Lengths are little-endian byte counts; fields are at most Ipcfieldmax bytes.
|
* 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,
|
||||||
|
Ipcreqreset = 1<<1,
|
||||||
|
Ipcext = 1<<7,
|
||||||
|
Ipcversion = 2,
|
||||||
|
Ipcopcap = 0,
|
||||||
|
Ipcopcaret = 1,
|
||||||
|
Ipcopsurround = 2,
|
||||||
|
|
||||||
Kspec = 0x110000,
|
Kspec = 0x110000,
|
||||||
Kback = Kspec|0x08,
|
Kback = Kspec|0x08,
|
||||||
Ktab = Kspec|0x09,
|
Ktab = Kspec|0x09,
|
||||||
@@ -23,35 +44,66 @@ enum
|
|||||||
Kesc = Kspec|0x1b,
|
Kesc = Kspec|0x1b,
|
||||||
Kup = Kspec|0x52,
|
Kup = Kspec|0x52,
|
||||||
Kdown = Kspec|0x54,
|
Kdown = Kspec|0x54,
|
||||||
Kmodfirst = Kspec|0xe1,
|
Kpgup = Kspec|0x55,
|
||||||
Kmodlast = Kspec|0xee,
|
Kpgdown = Kspec|0x56,
|
||||||
|
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,
|
Mmask = Mshift|Mctrl|Malt|Msuper,
|
||||||
|
Msupervirt = 1<<26,
|
||||||
|
|
||||||
Ipcreqsz = 6,
|
Ipcreqsz = 6,
|
||||||
|
Ipccaretsz = 16,
|
||||||
Ipclensz = 2,
|
Ipclensz = 2,
|
||||||
Ipcresphdrsz = 1 + Ipclensz,
|
Ipcresphdrsz = 2 + Ipclensz,
|
||||||
Ipcfieldmax = 256,
|
Ipcfieldmax = 256,
|
||||||
Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax,
|
Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax,
|
||||||
|
Ipcwaitms = 250,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Ipcunknown = -1,
|
||||||
|
Ipckey,
|
||||||
|
Ipccap,
|
||||||
|
Ipccaret,
|
||||||
|
Ipcsurround,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Ipcresp Ipcresp;
|
typedef struct Ipcresp Ipcresp;
|
||||||
struct Ipcresp
|
struct Ipcresp
|
||||||
{
|
{
|
||||||
int eaten;
|
int eaten;
|
||||||
size_t ncommit;
|
int del; /* runes of the client's own text to take back first */
|
||||||
size_t npreedit;
|
/* Bytes copied to each caller buffer, excluding its trailing NUL. */
|
||||||
|
size_t commitlen;
|
||||||
|
size_t preeditlen;
|
||||||
};
|
};
|
||||||
|
|
||||||
void ipcpackreq(unsigned char[Ipcreqsz], int, unsigned int, unsigned int);
|
uint32_t ipckeysym(uint32_t, uint32_t);
|
||||||
void ipcunpackreq(const unsigned char[Ipcreqsz], int*, unsigned int*, unsigned int*);
|
uint32_t ipcmod(uint32_t);
|
||||||
int ipcpackresp(unsigned char*, size_t, int, const char*, size_t, const char*, size_t, int);
|
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 ipcreadn(int, void*, size_t);
|
||||||
int ipcsend(int, const void*, size_t);
|
int ipcsend(int, const void*, size_t);
|
||||||
int ipcreadresp(int, int, char*, size_t, char*, size_t, Ipcresp*);
|
int ipcreadresp(int, int, char*, char*, Ipcresp*);
|
||||||
|
int ipcpath(char*, size_t);
|
||||||
#endif
|
int ipcconnect(void);
|
||||||
|
|||||||
95
ko.c
95
ko.c
@@ -4,8 +4,6 @@
|
|||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Sbase = 0xAC00,
|
Sbase = 0xAC00,
|
||||||
Jbase = 0x3131,
|
|
||||||
Jrange = 51,
|
|
||||||
Ncho = 19,
|
Ncho = 19,
|
||||||
Njung = 21,
|
Njung = 21,
|
||||||
Njong = 28,
|
Njong = 28,
|
||||||
@@ -35,41 +33,21 @@ static Rune jong[] = {
|
|||||||
L'ㅌ', L'ㅍ', L'ㅎ',
|
L'ㅌ', L'ㅍ', L'ㅎ',
|
||||||
};
|
};
|
||||||
|
|
||||||
static int choidx[Jrange] = {
|
/* A jamo's index in cho, jung, or jong, or -1; jong[0] is no jong. */
|
||||||
[L'ㄱ'-Jbase] = 1, [L'ㄲ'-Jbase] = 2, [L'ㄴ'-Jbase] = 3,
|
static int
|
||||||
[L'ㄷ'-Jbase] = 4, [L'ㄸ'-Jbase] = 5, [L'ㄹ'-Jbase] = 6,
|
idx(Rune *t, int n, Rune r)
|
||||||
[L'ㅁ'-Jbase] = 7, [L'ㅂ'-Jbase] = 8, [L'ㅃ'-Jbase] = 9,
|
{
|
||||||
[L'ㅅ'-Jbase] = 10, [L'ㅆ'-Jbase] = 11, [L'ㅇ'-Jbase] = 12,
|
int i;
|
||||||
[L'ㅈ'-Jbase] = 13, [L'ㅉ'-Jbase] = 14, [L'ㅊ'-Jbase] = 15,
|
|
||||||
[L'ㅋ'-Jbase] = 16, [L'ㅌ'-Jbase] = 17, [L'ㅍ'-Jbase] = 18,
|
|
||||||
[L'ㅎ'-Jbase] = 19,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int jungidx[Jrange] = {
|
for(i = 0; i < n; i++)
|
||||||
[L'ㅏ'-Jbase] = 1, [L'ㅐ'-Jbase] = 2, [L'ㅑ'-Jbase] = 3,
|
if(t[i] == r)
|
||||||
[L'ㅒ'-Jbase] = 4, [L'ㅓ'-Jbase] = 5, [L'ㅔ'-Jbase] = 6,
|
return i;
|
||||||
[L'ㅕ'-Jbase] = 7, [L'ㅖ'-Jbase] = 8, [L'ㅗ'-Jbase] = 9,
|
return -1;
|
||||||
[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,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int jongidx[Jrange] = {
|
#define Choidx(r) idx(cho, Ncho, r)
|
||||||
[L'ㄱ'-Jbase] = 2, [L'ㄲ'-Jbase] = 3, [L'ㄳ'-Jbase] = 4,
|
#define Jungidx(r) idx(jung, Njung, r)
|
||||||
[L'ㄴ'-Jbase] = 5, [L'ㄵ'-Jbase] = 6, [L'ㄶ'-Jbase] = 7,
|
#define Jongidx(r) idx(jong, Njong, r)
|
||||||
[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'ㄲ',
|
||||||
@@ -189,6 +167,10 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -199,10 +181,7 @@ 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){
|
||||||
if(im->pre.n > 0){
|
e.s = im->pre;
|
||||||
e.s = im->pre;
|
|
||||||
sclear(&im->pre);
|
|
||||||
}
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,17 +196,26 @@ transko(Im *im, Rune c)
|
|||||||
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;
|
||||||
}
|
}
|
||||||
if(Jungidx(last) >= 0 && ji >= 0){
|
/* A vowel typed first is reordered under its consonant. */
|
||||||
comb = combine(cvow, nelem(cvow), last, jm);
|
if(Jungidx(last) >= 0 && Choidx(jm) >= 0){
|
||||||
if(comb){
|
sputr(&e.next, compose(Choidx(jm), Jungidx(last), 0));
|
||||||
spopr(&im->pre);
|
return e;
|
||||||
sputr(&e.next, comb);
|
}
|
||||||
return e;
|
/* Two vowels or two consonants may be one jamo, ㅘ or ㄳ. */
|
||||||
}
|
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);
|
||||||
@@ -239,7 +227,6 @@ transko(Im *im, Rune c)
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@@ -248,7 +235,6 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -260,7 +246,6 @@ transko(Im *im, Rune c)
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@@ -271,14 +256,12 @@ 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;
|
||||||
@@ -300,12 +283,10 @@ backko(Im *im)
|
|||||||
|
|
||||||
last = slastr(&im->pre);
|
last = slastr(&im->pre);
|
||||||
if(!issyl(last)){
|
if(!issyl(last)){
|
||||||
if(splitpair(cvow, nelem(cvow), last, &stay, &next)){
|
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&im->pre, stay);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
spopr(&im->pre);
|
spopr(&im->pre);
|
||||||
|
if(splitpair(cvow, nelem(cvow), last, &stay, &next) ||
|
||||||
|
splitpair(cjong, nelem(cjong), last, &stay, &next))
|
||||||
|
sputr(&im->pre, stay);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
decompose(last, &c, &j, &jo);
|
decompose(last, &c, &j, &jo);
|
||||||
|
|||||||
47
main.c
47
main.c
@@ -3,20 +3,11 @@
|
|||||||
|
|
||||||
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 fontdir\n");
|
fprint(2, "usage: strans mapdir\n");
|
||||||
threadexitsall("usage");
|
threadexitsall("usage");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,10 +29,9 @@ emalloc(ulong n)
|
|||||||
{
|
{
|
||||||
void *p;
|
void *p;
|
||||||
|
|
||||||
p = malloc(n);
|
p = mallocz(n, 1);
|
||||||
if(p == nil)
|
if(p == nil)
|
||||||
die("out of memory");
|
die("out of memory");
|
||||||
memset(p, 0, n);
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,22 +47,31 @@ erealloc(void *p, ulong n)
|
|||||||
void
|
void
|
||||||
threadmain(int argc, char **argv)
|
threadmain(int argc, char **argv)
|
||||||
{
|
{
|
||||||
if(argc != 3)
|
char *display, *scale;
|
||||||
|
|
||||||
|
if(argc != 2)
|
||||||
usage();
|
usage();
|
||||||
|
|
||||||
fontdir = argv[2];
|
/* Whichever popup runs is sized from it, and setfont reads Fontsz. */
|
||||||
|
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);
|
||||||
dictreqc = chancreate(sizeof(Dictreq), 4);
|
langinit(argv[1]);
|
||||||
dictresc = chancreate(sizeof(Dictres), 0);
|
composeinit();
|
||||||
mapinit(argv[1]);
|
srvinit();
|
||||||
dictinit(argv[1]);
|
|
||||||
proccreate(drawthread, nil, 16384);
|
|
||||||
proccreate(srvthread, nil, 16384);
|
proccreate(srvthread, nil, 16384);
|
||||||
proccreate(ibusthread, nil, 32768);
|
proccreate(ibusthread, nil, 32768);
|
||||||
proccreate(waylandthread, nil, 32768);
|
/* One popup per session: the Wayland frontend draws its own, so the
|
||||||
threadcreate(dictthread, nil, 16384);
|
* X11 one and the XIM it serves stand down. */
|
||||||
threadcreate(imthread, nil, 16384);
|
if(getenv("WAYLAND_DISPLAY") != nil && wlinit())
|
||||||
|
proccreate(wlthread, nil, 32768);
|
||||||
threadexits(nil);
|
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,12 +1,130 @@
|
|||||||
#
|
# Dictionary data
|
||||||
# The following are a set of tools to obtain and process dictionaries from the SKK project in order to use them with ktrans(1).
|
|
||||||
#
|
|
||||||
# grabskkdicts pulls the skk kana-kanji conversion dictionaries from the skk-dev/dict repo.
|
|
||||||
# skk2ktrans takes an skk dictionary and converts it into a kanji jisho suitable to be used with ktrans(1).
|
|
||||||
#
|
|
||||||
# You can fetch and convert all the dictionaries by running this file.
|
|
||||||
#
|
|
||||||
|
|
||||||
grabskkdicts
|
## Korean Hanja and symbol data
|
||||||
for(d in skkdicts/SKK-JISYO.*)
|
|
||||||
<$d skk2ktrans >$d.jisho
|
`hanja.src` and `mssymbol.src` are the tracked, reviewable sources for what
|
||||||
|
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.
|
||||||
|
|||||||
52
map/cldr2emoji
Executable file
52
map/cldr2emoji
Executable file
@@ -0,0 +1,52 @@
|
|||||||
|
#!/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())
|
||||||
19068
map/emoji.dict
19068
map/emoji.dict
File diff suppressed because it is too large
Load Diff
2026
map/emoji.src
2026
map/emoji.src
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/rc
|
|
||||||
git/clone https://github.com/skk-dev/dict skkdicts
|
|
||||||
187357
map/hanja.dict
Normal file
187357
map/hanja.dict
Normal file
File diff suppressed because it is too large
Load Diff
266539
map/hanja.src
Normal file
266539
map/hanja.src
Normal file
File diff suppressed because it is too large
Load Diff
367
map/hira.map
367
map/hira.map
@@ -1,170 +1,321 @@
|
|||||||
a あ
|
a あ
|
||||||
- ー
|
|
||||||
i い
|
i い
|
||||||
u う
|
u う
|
||||||
e え
|
e え
|
||||||
o お
|
o お
|
||||||
ka か
|
|
||||||
ga が
|
|
||||||
ki き
|
|
||||||
kya きゃ
|
kya きゃ
|
||||||
|
kyi きぃ
|
||||||
kyu きゅ
|
kyu きゅ
|
||||||
|
kye きぇ
|
||||||
kyo きょ
|
kyo きょ
|
||||||
gi ぎ
|
ka か
|
||||||
gya ぎゃ
|
ki き
|
||||||
gyu ぎゅ
|
|
||||||
gyo ぎょ
|
|
||||||
ku く
|
ku く
|
||||||
gu ぐ
|
|
||||||
ke け
|
ke け
|
||||||
ge げ
|
|
||||||
ko こ
|
ko こ
|
||||||
|
kwa くぁ
|
||||||
|
kwi くぃ
|
||||||
|
kwu くぅ
|
||||||
|
kwe くぇ
|
||||||
|
kwo くぉ
|
||||||
|
gya ぎゃ
|
||||||
|
gyi ぎぃ
|
||||||
|
gyu ぎゅ
|
||||||
|
gye ぎぇ
|
||||||
|
gyo ぎょ
|
||||||
|
ga が
|
||||||
|
gi ぎ
|
||||||
|
gu ぐ
|
||||||
|
ge げ
|
||||||
go ご
|
go ご
|
||||||
sa さ
|
gwa ぐぁ
|
||||||
za ざ
|
gwi ぐぃ
|
||||||
si し
|
gwu ぐぅ
|
||||||
shi し
|
gwe ぐぇ
|
||||||
|
gwo ぐぉ
|
||||||
|
sya しゃ
|
||||||
|
syi しぃ
|
||||||
|
syu しゅ
|
||||||
|
sye しぇ
|
||||||
|
syo しょ
|
||||||
sha しゃ
|
sha しゃ
|
||||||
|
shi し
|
||||||
shu しゅ
|
shu しゅ
|
||||||
she しぇ
|
she しぇ
|
||||||
sho しょ
|
sho しょ
|
||||||
syo しょ
|
sa さ
|
||||||
zi ぢ
|
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 ざ
|
||||||
|
zi じ
|
||||||
|
zu ず
|
||||||
|
ze ぜ
|
||||||
|
zo ぞ
|
||||||
ja じゃ
|
ja じゃ
|
||||||
|
ji じ
|
||||||
ju じゅ
|
ju じゅ
|
||||||
je じぇ
|
je じぇ
|
||||||
jo じょ
|
jo じょ
|
||||||
su す
|
jya じゃ
|
||||||
zu ず
|
jyi じぃ
|
||||||
se せ
|
jyu じゅ
|
||||||
ze ぜ
|
jye じぇ
|
||||||
so そ
|
jyo じょ
|
||||||
zo ぞ
|
tya ちゃ
|
||||||
ta た
|
tyi ちぃ
|
||||||
da だ
|
|
||||||
ti ち
|
|
||||||
chi ち
|
|
||||||
cha ちゃ
|
|
||||||
chu ちゅ
|
|
||||||
tyu ちゅ
|
tyu ちゅ
|
||||||
cho ちょ
|
tye ちぇ
|
||||||
ji じ
|
tyo ちょ
|
||||||
|
tsa つぁ
|
||||||
|
tsi つぃ
|
||||||
|
tse つぇ
|
||||||
|
tso つぉ
|
||||||
|
tha てゃ
|
||||||
|
thi てぃ
|
||||||
|
t'i てぃ
|
||||||
|
thu てゅ
|
||||||
|
the てぇ
|
||||||
|
tho てょ
|
||||||
|
t'yu てゅ
|
||||||
|
twa とぁ
|
||||||
|
twi とぃ
|
||||||
|
twu とぅ
|
||||||
|
twe とぇ
|
||||||
|
two とぉ
|
||||||
|
t'u とぅ
|
||||||
|
ta た
|
||||||
|
ti ち
|
||||||
tu つ
|
tu つ
|
||||||
tsu つ
|
tsu つ
|
||||||
du づ
|
|
||||||
te て
|
te て
|
||||||
de で
|
|
||||||
to と
|
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 づ
|
||||||
|
de で
|
||||||
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 の
|
||||||
ha は
|
|
||||||
ba ば
|
|
||||||
va ば
|
|
||||||
pa ぱ
|
|
||||||
hi ひ
|
|
||||||
hya ひゃ
|
hya ひゃ
|
||||||
|
hyi ひぃ
|
||||||
hyu ひゅ
|
hyu ひゅ
|
||||||
|
hye ひぇ
|
||||||
hyo ひょ
|
hyo ひょ
|
||||||
bi び
|
hwa ふぁ
|
||||||
bya びゃ
|
hwi ふぃ
|
||||||
byu びゅ
|
hwe ふぇ
|
||||||
byo びょ
|
hwo ふぉ
|
||||||
vi び
|
hwyu ふゅ
|
||||||
pi ぴ
|
ha は
|
||||||
pya ぴゃ
|
hi ひ
|
||||||
pyu ぴゅ
|
|
||||||
pyo ぴょ
|
|
||||||
hu ふ
|
hu ふ
|
||||||
fu ふ
|
|
||||||
bu ぶ
|
|
||||||
vu ぶ
|
|
||||||
pu ぷ
|
|
||||||
he へ
|
he へ
|
||||||
be べ
|
|
||||||
ve べ
|
|
||||||
pe ぺ
|
|
||||||
ho ほ
|
ho ほ
|
||||||
|
fa ふぁ
|
||||||
|
fi ふぃ
|
||||||
|
fu ふ
|
||||||
|
fe ふぇ
|
||||||
|
fo ふぉ
|
||||||
|
fya ふゃ
|
||||||
|
fyu ふゅ
|
||||||
|
fyo ふょ
|
||||||
|
bya びゃ
|
||||||
|
byi びぃ
|
||||||
|
byu びゅ
|
||||||
|
bye びぇ
|
||||||
|
byo びょ
|
||||||
|
ba ば
|
||||||
|
bi び
|
||||||
|
bu ぶ
|
||||||
|
be べ
|
||||||
bo ぼ
|
bo ぼ
|
||||||
vo ぼ
|
pya ぴゃ
|
||||||
|
pyi ぴぃ
|
||||||
|
pyu ぴゅ
|
||||||
|
pye ぴぇ
|
||||||
|
pyo ぴょ
|
||||||
|
pa ぱ
|
||||||
|
pi ぴ
|
||||||
|
pu ぷ
|
||||||
|
pe ぺ
|
||||||
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 を
|
||||||
n ん
|
wha うぁ
|
||||||
|
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 ぉ
|
||||||
kka っか
|
xyi ぃ
|
||||||
kki っき
|
xye ぇ
|
||||||
kkya っきゃ
|
xka ヵ
|
||||||
kkyu っきゅ
|
xke ヶ
|
||||||
kkyo っきょ
|
xtu っ
|
||||||
kku っく
|
xtsu っ
|
||||||
kke っけ
|
xya ゃ
|
||||||
kko っこ
|
xyu ゅ
|
||||||
ssa っさ
|
xyo ょ
|
||||||
ssi っし
|
xwa ゎ
|
||||||
sshi っし
|
la ぁ
|
||||||
ssha っしゃ
|
li ぃ
|
||||||
sshu っしゅ
|
lu ぅ
|
||||||
sshe っしぇ
|
le ぇ
|
||||||
ssho っしょ
|
lo ぉ
|
||||||
ssyo っしょ
|
lyi ぃ
|
||||||
ssu っす
|
lye ぇ
|
||||||
sse っせ
|
lka ヵ
|
||||||
sso っそ
|
lke ヶ
|
||||||
tta った
|
ltu っ
|
||||||
dda っだ
|
ltsu っ
|
||||||
tti っち
|
lya ゃ
|
||||||
cchi っち
|
lyu ゅ
|
||||||
ccha っちゃ
|
lyo ょ
|
||||||
cchu っちゅ
|
lwa ゎ
|
||||||
ttyu っちゅ
|
qq っ
|
||||||
ccho っちょ
|
vv っ
|
||||||
ttu っつ
|
ll っ
|
||||||
ttsu っつ
|
xx っ
|
||||||
ddu っづ
|
kk っ
|
||||||
tte って
|
gg っ
|
||||||
dde っで
|
ss っ
|
||||||
tto っと
|
zz っ
|
||||||
ddo っど
|
jj っ
|
||||||
ppa っぱ
|
tt っ
|
||||||
ppi っぴ
|
tch っ
|
||||||
ppya っぴゃ
|
dd っ
|
||||||
ppyu っぴゅ
|
hh っ
|
||||||
ppyo っぴょ
|
ff っ
|
||||||
ppu っぷ
|
bb っ
|
||||||
ppe っぺ
|
pp っ
|
||||||
ppo っぽ
|
mm っ
|
||||||
|
yy っ
|
||||||
|
rr っ
|
||||||
|
ww っ
|
||||||
|
cc っ
|
||||||
|
- ー
|
||||||
|
~ 〜
|
||||||
. 。
|
. 。
|
||||||
, 、
|
, 、
|
||||||
|
[ 「
|
||||||
|
] 」
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
|
|
||||||
, ,
|
|
||||||
. 。
|
|
||||||
< 《
|
|
||||||
> 》
|
|
||||||
/ /
|
|
||||||
? ?
|
|
||||||
; ;
|
|
||||||
: :
|
|
||||||
\ 、
|
|
||||||
| ・
|
|
||||||
` `
|
|
||||||
~ 〜
|
|
||||||
! !
|
|
||||||
@ @
|
|
||||||
# #
|
|
||||||
$ ¥
|
|
||||||
& &
|
|
||||||
* *
|
|
||||||
( (
|
|
||||||
) )
|
|
||||||
- -
|
|
||||||
+ +
|
|
||||||
= =
|
|
||||||
[ 「
|
|
||||||
] 」
|
|
||||||
{ 『
|
|
||||||
} 』
|
|
||||||
@@ -60,7 +60,6 @@
|
|||||||
あたらs 新
|
あたらs 新
|
||||||
あつi 熱 暑 厚
|
あつi 熱 暑 厚
|
||||||
あつk 厚 熱 暑
|
あつk 厚 熱 暑
|
||||||
あつk 厚 熱 暑
|
|
||||||
あつs 暑
|
あつs 暑
|
||||||
あつm 集 厚
|
あつm 集 厚
|
||||||
あつかe 扱
|
あつかe 扱
|
||||||
@@ -350,7 +349,6 @@
|
|||||||
かわいr 可愛
|
かわいr 可愛
|
||||||
かんj 感
|
かんj 感
|
||||||
かんs 関
|
かんs 関
|
||||||
かんj 感
|
|
||||||
かんz 感
|
かんz 感
|
||||||
かんがe 考
|
かんがe 考
|
||||||
かんしゃs 感謝
|
かんしゃs 感謝
|
||||||
@@ -455,7 +453,7 @@
|
|||||||
こu 乞 請
|
こu 乞 請
|
||||||
こy 来
|
こy 来
|
||||||
こうj 高
|
こうj 高
|
||||||
こうしょう 鉱床 高尚
|
こうしょう 鉱床 高尚 交渉
|
||||||
こうぶつ 鉱物 好物
|
こうぶつ 鉱物 好物
|
||||||
こうりょs 考慮
|
こうりょs 考慮
|
||||||
こえt 超
|
こえt 超
|
||||||
@@ -665,8 +663,6 @@
|
|||||||
ただc 直
|
ただc 直
|
||||||
ただt 直
|
ただt 直
|
||||||
たとe 例
|
たとe 例
|
||||||
たおr 倒
|
|
||||||
たおr 倒
|
|
||||||
たのm 頼
|
たのm 頼
|
||||||
たのn 頼
|
たのn 頼
|
||||||
たのs 楽
|
たのs 楽
|
||||||
@@ -742,7 +738,6 @@
|
|||||||
つみあげr 積み上げ
|
つみあげr 積み上げ
|
||||||
つみあげt 積み上げ
|
つみあげt 積み上げ
|
||||||
つめt 冷
|
つめt 冷
|
||||||
つづr 綴
|
|
||||||
つよi 強
|
つよi 強
|
||||||
つよk 強
|
つよk 強
|
||||||
つらi 辛
|
つらi 辛
|
||||||
@@ -793,7 +788,6 @@
|
|||||||
とt 取 撮 採
|
とt 取 撮 採
|
||||||
とu 問
|
とu 問
|
||||||
とw 問
|
とw 問
|
||||||
とj 閉
|
|
||||||
といあw 問い合
|
といあw 問い合
|
||||||
とうj 投
|
とうj 投
|
||||||
とおi 遠
|
とおi 遠
|
||||||
@@ -913,13 +907,12 @@
|
|||||||
ねらt 狙
|
ねらt 狙
|
||||||
ねらu 狙
|
ねらu 狙
|
||||||
のb 述 伸 延
|
のb 述 伸 延
|
||||||
のk 退
|
のk 退 乗
|
||||||
のm 飲 呑
|
のm 飲 呑
|
||||||
のn 飲
|
のn 飲
|
||||||
のr 乗 載
|
のr 乗 載
|
||||||
のs 載 乗
|
のs 載 乗
|
||||||
のt 乗 載
|
のt 乗 載
|
||||||
のk 乗
|
|
||||||
のこr 残
|
のこr 残
|
||||||
のこs 残
|
のこs 残
|
||||||
のこt 残
|
のこt 残
|
||||||
@@ -1059,12 +1052,11 @@
|
|||||||
ぼうえいs 防衛
|
ぼうえいs 防衛
|
||||||
まc 待
|
まc 待
|
||||||
まi 舞
|
まi 舞
|
||||||
まj 交
|
まj 交 混
|
||||||
まk 負 巻
|
まk 負 巻
|
||||||
まs 増
|
まs 増
|
||||||
まt 待
|
まt 待
|
||||||
まu 舞
|
まu 舞
|
||||||
まj 混
|
|
||||||
まz 混 先
|
まz 混 先
|
||||||
まいr 参 詣
|
まいr 参 詣
|
||||||
まいt 参 詣
|
まいt 参 詣
|
||||||
@@ -1241,8 +1233,6 @@
|
|||||||
よわm 弱
|
よわm 弱
|
||||||
よわs 弱
|
よわs 弱
|
||||||
よわt 弱
|
よわt 弱
|
||||||
よろこb 喜 慶
|
|
||||||
よろこb 喜 慶
|
|
||||||
ろんj 論
|
ろんj 論
|
||||||
ろんz 論
|
ろんz 論
|
||||||
わk 分 湧
|
わk 分 湧
|
||||||
@@ -2267,7 +2257,6 @@
|
|||||||
がくわり 学割
|
がくわり 学割
|
||||||
がけ 崖
|
がけ 崖
|
||||||
がし 樫
|
がし 樫
|
||||||
かせい 火星
|
|
||||||
がそ 画素
|
がそ 画素
|
||||||
がそかん 画素間
|
がそかん 画素間
|
||||||
がそごと 画素毎
|
がそごと 画素毎
|
||||||
@@ -2993,7 +2982,6 @@
|
|||||||
こうしゃ 後者 公社 校舎
|
こうしゃ 後者 公社 校舎
|
||||||
こうしゅう 講習 公衆
|
こうしゅう 講習 公衆
|
||||||
こうしゅうかい 講習会
|
こうしゅうかい 講習会
|
||||||
こうしょう 交渉 鉱床
|
|
||||||
こうしん 更新 交信 後身
|
こうしん 更新 交信 後身
|
||||||
こうじ 麹 公示 高次 工事 孝二
|
こうじ 麹 公示 高次 工事 孝二
|
||||||
こうじつ 口実
|
こうじつ 口実
|
||||||
@@ -4802,7 +4790,7 @@
|
|||||||
たいきゅうりょく 耐久力
|
たいきゅうりょく 耐久力
|
||||||
たいきょく 対極
|
たいきょく 対極
|
||||||
たいきん 大金
|
たいきん 大金
|
||||||
たいく 体躯
|
たいく 体躯 体育
|
||||||
たいくつ 退屈
|
たいくつ 退屈
|
||||||
たいけい 体系 体型
|
たいけい 体系 体型
|
||||||
たいけん 体験
|
たいけん 体験
|
||||||
@@ -4949,8 +4937,6 @@
|
|||||||
たんらく 短絡
|
たんらく 短絡
|
||||||
だ 騨 駄 陀 楕 舵 柁 打 惰 妥 堕 唾 田 朶
|
だ 騨 駄 陀 楕 舵 柁 打 惰 妥 堕 唾 田 朶
|
||||||
だい 大 第 内 代 台 題 醍
|
だい 大 第 内 代 台 題 醍
|
||||||
たいく 体育
|
|
||||||
たいいく 体育
|
|
||||||
だいいち 第一
|
だいいち 第一
|
||||||
だいがく 大学
|
だいがく 大学
|
||||||
だいがくいん 大学院
|
だいがくいん 大学院
|
||||||
@@ -7314,7 +7300,6 @@
|
|||||||
よこう 予行 予稿
|
よこう 予行 予稿
|
||||||
よこうち 横内
|
よこうち 横内
|
||||||
よこく 予告
|
よこく 予告
|
||||||
ようたつ
|
|
||||||
よこて 横手
|
よこて 横手
|
||||||
よこはま 横浜
|
よこはま 横浜
|
||||||
よこもじ 横文字
|
よこもじ 横文字
|
||||||
@@ -7426,7 +7411,7 @@
|
|||||||
りゅうど 粒度
|
りゅうど 粒度
|
||||||
りゆう 理由
|
りゆう 理由
|
||||||
りょ 虜 旅 慮 侶
|
りょ 虜 旅 慮 侶
|
||||||
りょう 量 寮 両 領 陵 遼 諒 良 糧 稜 瞭 療 猟 涼 梁 料 凌 僚 亮 了 漁 糧 粮 霊
|
りょう 量 寮 両 領 陵 遼 諒 良 糧 稜 瞭 療 猟 涼 梁 料 凌 僚 亮 了 漁 粮 霊
|
||||||
りょうあし 両足
|
りょうあし 両足
|
||||||
りょういき 領域
|
りょういき 領域
|
||||||
りょうか 量化
|
りょうか 量化
|
||||||
|
|||||||
349
map/kata.map
349
map/kata.map
@@ -1,142 +1,321 @@
|
|||||||
a ア
|
a ア
|
||||||
- ー
|
|
||||||
i イ
|
i イ
|
||||||
u ウ
|
u ウ
|
||||||
e エ
|
e エ
|
||||||
o オ
|
o オ
|
||||||
ka カ
|
|
||||||
ga ガ
|
|
||||||
ki キ
|
|
||||||
kya キャ
|
kya キャ
|
||||||
|
kyi キィ
|
||||||
kyu キュ
|
kyu キュ
|
||||||
|
kye キェ
|
||||||
kyo キョ
|
kyo キョ
|
||||||
gi ギ
|
ka カ
|
||||||
gya ギャ
|
ki キ
|
||||||
gyu ギュ
|
|
||||||
gyo ギョ
|
|
||||||
ku ク
|
ku ク
|
||||||
gu グ
|
|
||||||
ke ケ
|
ke ケ
|
||||||
ge ゲ
|
|
||||||
ko コ
|
ko コ
|
||||||
|
kwa クァ
|
||||||
|
kwi クィ
|
||||||
|
kwu クゥ
|
||||||
|
kwe クェ
|
||||||
|
kwo クォ
|
||||||
|
gya ギャ
|
||||||
|
gyi ギィ
|
||||||
|
gyu ギュ
|
||||||
|
gye ギェ
|
||||||
|
gyo ギョ
|
||||||
|
ga ガ
|
||||||
|
gi ギ
|
||||||
|
gu グ
|
||||||
|
ge ゲ
|
||||||
go ゴ
|
go ゴ
|
||||||
sa サ
|
gwa グァ
|
||||||
za ザ
|
gwi グィ
|
||||||
si シ
|
gwu グゥ
|
||||||
shi シ
|
gwe グェ
|
||||||
ji ジ
|
gwo グォ
|
||||||
sha シャ
|
sya シャ
|
||||||
she シェ
|
syi シィ
|
||||||
shu シュ
|
syu シュ
|
||||||
je ジェ
|
sye シェ
|
||||||
sho ショ
|
|
||||||
syo ショ
|
syo ショ
|
||||||
|
sha シャ
|
||||||
|
shi シ
|
||||||
|
shu シュ
|
||||||
|
she シェ
|
||||||
|
sho ショ
|
||||||
|
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 ザ
|
||||||
|
zi ジ
|
||||||
|
zu ズ
|
||||||
|
ze ゼ
|
||||||
|
zo ゾ
|
||||||
ja ジャ
|
ja ジャ
|
||||||
|
ji ジ
|
||||||
ju ジュ
|
ju ジュ
|
||||||
je ジェ
|
je ジェ
|
||||||
jo ジョ
|
jo ジョ
|
||||||
su ス
|
jya ジャ
|
||||||
zu ズ
|
jyi ジィ
|
||||||
se セ
|
jyu ジュ
|
||||||
ze ゼ
|
jye ジェ
|
||||||
so ソ
|
jyo ジョ
|
||||||
zo ゾ
|
tya チャ
|
||||||
ta タ
|
tyi チィ
|
||||||
da ダ
|
|
||||||
ti ティ
|
|
||||||
chi チ
|
|
||||||
zi ヂ
|
|
||||||
cha チャ
|
|
||||||
chu チュ
|
|
||||||
tyu チュ
|
tyu チュ
|
||||||
che チェ
|
tye チェ
|
||||||
cho チョ
|
tyo チョ
|
||||||
|
tsa ツァ
|
||||||
|
tsi ツィ
|
||||||
|
tse ツェ
|
||||||
|
tso ツォ
|
||||||
|
tha テャ
|
||||||
|
thi ティ
|
||||||
|
t'i ティ
|
||||||
|
thu テュ
|
||||||
|
the テェ
|
||||||
|
tho テョ
|
||||||
|
t'yu テュ
|
||||||
|
twa トァ
|
||||||
|
twi トィ
|
||||||
|
twu トゥ
|
||||||
|
twe トェ
|
||||||
|
two トォ
|
||||||
|
t'u トゥ
|
||||||
|
ta タ
|
||||||
|
ti チ
|
||||||
tu ツ
|
tu ツ
|
||||||
tsu ツ
|
tsu ツ
|
||||||
du ヅ
|
|
||||||
te テ
|
te テ
|
||||||
de デ
|
|
||||||
to ト
|
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 ヅ
|
||||||
|
de デ
|
||||||
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 ノ
|
||||||
ha ハ
|
|
||||||
ba バ
|
|
||||||
pa パ
|
|
||||||
hi ヒ
|
|
||||||
hya ヒャ
|
hya ヒャ
|
||||||
|
hyi ヒィ
|
||||||
hyu ヒュ
|
hyu ヒュ
|
||||||
|
hye ヒェ
|
||||||
hyo ヒョ
|
hyo ヒョ
|
||||||
bi ビ
|
hwa ファ
|
||||||
bya ビャ
|
hwi フィ
|
||||||
byu ビュ
|
hwe フェ
|
||||||
byo ビョ
|
hwo フォ
|
||||||
pi ピ
|
hwyu フュ
|
||||||
pya ピャ
|
ha ハ
|
||||||
pyu ピュ
|
hi ヒ
|
||||||
pyo ピョ
|
|
||||||
hu フ
|
hu フ
|
||||||
fu フ
|
|
||||||
bu ブ
|
|
||||||
pu プ
|
|
||||||
he ヘ
|
he ヘ
|
||||||
be ベ
|
|
||||||
pe ペ
|
|
||||||
ho ホ
|
ho ホ
|
||||||
|
fa ファ
|
||||||
|
fi フィ
|
||||||
|
fu フ
|
||||||
|
fe フェ
|
||||||
|
fo フォ
|
||||||
|
fya フャ
|
||||||
|
fyu フュ
|
||||||
|
fyo フョ
|
||||||
|
bya ビャ
|
||||||
|
byi ビィ
|
||||||
|
byu ビュ
|
||||||
|
bye ビェ
|
||||||
|
byo ビョ
|
||||||
|
ba バ
|
||||||
|
bi ビ
|
||||||
|
bu ブ
|
||||||
|
be ベ
|
||||||
bo ボ
|
bo ボ
|
||||||
|
pya ピャ
|
||||||
|
pyi ピィ
|
||||||
|
pyu ピュ
|
||||||
|
pye ピェ
|
||||||
|
pyo ピョ
|
||||||
|
pa パ
|
||||||
|
pi ピ
|
||||||
|
pu プ
|
||||||
|
pe ペ
|
||||||
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 ヲ
|
||||||
n ン
|
wha ウァ
|
||||||
xn ン
|
whi ウィ
|
||||||
v ヴ
|
whu ウ
|
||||||
xa ァ
|
whe ウェ
|
||||||
xi ィ
|
who ウォ
|
||||||
xe ェ
|
|
||||||
xo ォ
|
|
||||||
cc ッ
|
|
||||||
dd ッ
|
|
||||||
kk ッ
|
|
||||||
pp ッ
|
|
||||||
tt ッ
|
|
||||||
tch ッ
|
|
||||||
ss ッ
|
|
||||||
xn ン
|
|
||||||
di ディ
|
|
||||||
fa ファ
|
|
||||||
fi フィ
|
|
||||||
fe フェ
|
|
||||||
fo フォ
|
|
||||||
va ヴァ
|
va ヴァ
|
||||||
vi ヴィ
|
vi ヴィ
|
||||||
|
vu ヴ
|
||||||
ve ヴェ
|
ve ヴェ
|
||||||
vo ヴォ
|
vo ヴォ
|
||||||
|
vya ヴャ
|
||||||
|
vyi ヴィ
|
||||||
|
vyu ヴュ
|
||||||
|
vye ヴェ
|
||||||
|
vyo ヴョ
|
||||||
|
qa クァ
|
||||||
|
qi クィ
|
||||||
|
qu ク
|
||||||
|
qe クェ
|
||||||
|
qo クォ
|
||||||
|
xn ン
|
||||||
|
xa ァ
|
||||||
|
xi ィ
|
||||||
|
xu ゥ
|
||||||
|
xe ェ
|
||||||
|
xo ォ
|
||||||
|
xyi ィ
|
||||||
|
xye ェ
|
||||||
|
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 ッ
|
||||||
|
gg ッ
|
||||||
|
ss ッ
|
||||||
|
zz ッ
|
||||||
|
jj ッ
|
||||||
|
tt ッ
|
||||||
|
tch ッ
|
||||||
|
dd ッ
|
||||||
|
hh ッ
|
||||||
|
ff ッ
|
||||||
|
bb ッ
|
||||||
|
pp ッ
|
||||||
|
mm ッ
|
||||||
|
yy ッ
|
||||||
|
rr ッ
|
||||||
|
ww ッ
|
||||||
|
cc ッ
|
||||||
|
- ー
|
||||||
|
~ 〜
|
||||||
. 。
|
. 。
|
||||||
, 、
|
, 、
|
||||||
|
[ 「
|
||||||
|
] 」
|
||||||
|
|||||||
98
map/libhangul2hanja
Executable file
98
map/libhangul2hanja
Executable file
@@ -0,0 +1,98 @@
|
|||||||
|
#!/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())
|
||||||
52
map/mkemoji
52
map/mkemoji
@@ -1,13 +1,19 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Write emoji.dict to stdout from a result-first UTF-8 TSV file."""
|
"""Write emoji.dict to stdout from result-first UTF-8 TSV files."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
SOURCE = Path(__file__).with_name("emoji.src")
|
SOURCES = [Path(__file__).with_name(name)
|
||||||
|
for name in ("symbol.src", "emoji.src")]
|
||||||
MAXRUNES = 64
|
MAXRUNES = 64
|
||||||
|
MAXCANDIDATES = 128
|
||||||
|
|
||||||
|
|
||||||
|
HIRA = {c: c + 0x60 for c in range(0x3041, 0x3097)}
|
||||||
|
KATA = {c: c - 0x60 for c in range(0x30A1, 0x30F7)}
|
||||||
|
|
||||||
|
|
||||||
def fold(s):
|
def fold(s):
|
||||||
@@ -15,6 +21,12 @@ def fold(s):
|
|||||||
return unicodedata.normalize("NFC", s)
|
return unicodedata.normalize("NFC", s)
|
||||||
|
|
||||||
|
|
||||||
|
def kana(alias):
|
||||||
|
"""The alias, and in the other kana where it has any: a query typed
|
||||||
|
in either Japanese mode finds it."""
|
||||||
|
return {alias, alias.translate(HIRA), alias.translate(KATA)}
|
||||||
|
|
||||||
|
|
||||||
def hascontrol(s):
|
def hascontrol(s):
|
||||||
return any(unicodedata.category(c) == "Cc" for c in s)
|
return any(unicodedata.category(c) == "Cc" for c in s)
|
||||||
|
|
||||||
@@ -38,37 +50,31 @@ def read(path):
|
|||||||
for field in fields[1:]:
|
for field in fields[1:]:
|
||||||
alias = fold(field)
|
alias = fold(field)
|
||||||
if (not alias or len(alias) > MAXRUNES or hascontrol(alias)
|
if (not alias or len(alias) > MAXRUNES or hascontrol(alias)
|
||||||
or alias != alias.strip()):
|
or alias != alias.strip() or alias.startswith(";")):
|
||||||
raise ValueError(f"{path}:{lineno}: bad alias")
|
raise ValueError(f"{path}:{lineno}: bad alias")
|
||||||
entries.append((result, alias))
|
entries.extend((result, a) for a in sorted(kana(alias)))
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
|
|
||||||
def add(table, key, result):
|
|
||||||
values = table.setdefault(key, [])
|
|
||||||
if result not in values:
|
|
||||||
values.append(result)
|
|
||||||
|
|
||||||
|
|
||||||
def build(entries):
|
def build(entries):
|
||||||
exact = {}
|
"""One row per alias, in order of first appearance; the engine searches
|
||||||
prefix = {}
|
the dictionary by prefix, so no prefix rows are needed."""
|
||||||
|
table = {}
|
||||||
for result, alias in entries:
|
for result, alias in entries:
|
||||||
for n in range(1, len(alias) + 1):
|
values = table.setdefault(alias, [])
|
||||||
add(exact if n == len(alias) else prefix, alias[:n], result)
|
if result not in values:
|
||||||
for key in sorted(exact.keys() | prefix.keys()):
|
values.append(result)
|
||||||
values = exact.get(key, []) + prefix.get(key, [])
|
for alias, values in table.items():
|
||||||
values = list(dict.fromkeys(values))
|
yield f"{alias}\t{' '.join(values[:MAXCANDIDATES])}"
|
||||||
yield f"{key}\t{' '.join(values)}"
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) > 2:
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
print(f"usage: {sys.argv[0]} [emoji.src]", file=sys.stderr)
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
return 2
|
paths = [Path(arg) for arg in sys.argv[1:]] or SOURCES
|
||||||
path = Path(sys.argv[1]) if len(sys.argv) == 2 else SOURCE
|
|
||||||
try:
|
try:
|
||||||
for line in build(read(path)):
|
entries = [entry for path in paths for entry in read(path)]
|
||||||
|
for line in build(entries):
|
||||||
print(line)
|
print(line)
|
||||||
except (OSError, UnicodeError, ValueError) as error:
|
except (OSError, UnicodeError, ValueError) as error:
|
||||||
print(error, file=sys.stderr)
|
print(error, file=sys.stderr)
|
||||||
|
|||||||
96
map/mkhanja
Executable file
96
map/mkhanja
Executable file
@@ -0,0 +1,96 @@
|
|||||||
|
#!/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())
|
||||||
@@ -55,11 +55,15 @@ 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"),
|
||||||
("eu", "eu", "e"), ("iu", "iu", "i"), ("oi", "oi", "o"),
|
("eo", "eo", "e"), ("eu", "eu", "e"), ("ia", "ia", "i"),
|
||||||
|
("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ă", "ă"),
|
||||||
("uwa", "ưa", "a"), ("uwow", "ươ", "ơ"),
|
("uwow", "ươ", "ơ"),
|
||||||
]
|
]
|
||||||
for i, o, v in tab:
|
for i, o, v in tab:
|
||||||
emit(i, o)
|
emit(i, o)
|
||||||
@@ -68,21 +72,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", "ơ"),
|
||||||
("oai", "oai", "a"), ("oay", "oay", "a"),
|
("uowu", "ươu", "ơ"), ("uwowi", "ươi", "ơ"),
|
||||||
("uyee", "uyê", "ê"),
|
("uwowu", "ươu", "ơ"), ("uaay", "uây", "â"),
|
||||||
|
("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:
|
||||||
@@ -132,10 +136,11 @@ 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", "ươ", "ơ"), ("uaa", "uâ", "â"), ("oaw", "oă", "ă"),
|
("uow", "ươ", "ơ"), ("uwow", "ươ", "ơ"), ("uyee", "uyê", "ê"),
|
||||||
|
("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"), ("oi", "oi", "o"),
|
("ay", "ay", "a"), ("oa", "oa", "a"), ("oe", "oe", "e"),
|
||||||
("ui", "ui", "u"), ("uy", "uy", "y"),
|
("oi", "oi", "o"), ("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:
|
||||||
|
|||||||
1012
map/mssymbol.src
Normal file
1012
map/mssymbol.src
Normal file
File diff suppressed because it is too large
Load Diff
28294
map/pinyin.dict
28294
map/pinyin.dict
File diff suppressed because it is too large
Load Diff
@@ -1,2 +1,69 @@
|
|||||||
#!/bin/rc
|
#!/bin/bash
|
||||||
tcs -sf ujis | awk '$1 !~ /;;/ {gsub("(^\/|\/$)", "", $2); gsub(" ", " "); gsub("\/", " ", $2);} {print}'
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
transcode()
|
||||||
|
{
|
||||||
|
local file
|
||||||
|
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
iconv -f EUC-JP -t UTF-8
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
for file in "$@"; do
|
||||||
|
iconv -f EUC-JP -t UTF-8 "$file"
|
||||||
|
printf '\n'
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
transcode "$@" | awk '
|
||||||
|
function fail(s) {
|
||||||
|
print "skk2ktrans: " FILENAME ":" FNR ": " s >"/dev/stderr"
|
||||||
|
bad = 1
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
function add(k, v, id) {
|
||||||
|
sub(/;.*/, "", v)
|
||||||
|
if(v == "" || v ~ /^[([#]/ || v ~ /[[:space:]]/)
|
||||||
|
return
|
||||||
|
id = k SUBSEP v
|
||||||
|
if(seen[id])
|
||||||
|
return
|
||||||
|
seen[id] = 1
|
||||||
|
if(!(k in row))
|
||||||
|
order[++nkey] = k
|
||||||
|
else
|
||||||
|
row[k] = row[k] " "
|
||||||
|
row[k] = row[k] v
|
||||||
|
}
|
||||||
|
/^;;/ {
|
||||||
|
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
Normal file
103
map/symbol.src
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# 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
|
||||||
768
map/telex.map
768
map/telex.map
@@ -142,6 +142,18 @@ ayx ãy
|
|||||||
Ayx Ãy
|
Ayx Ãy
|
||||||
ayj ạy
|
ayj ạy
|
||||||
Ayj Ạy
|
Ayj Ạy
|
||||||
|
eo eo
|
||||||
|
Eo Eo
|
||||||
|
eos éo
|
||||||
|
Eos Éo
|
||||||
|
eof èo
|
||||||
|
Eof Èo
|
||||||
|
eor ẻo
|
||||||
|
Eor Ẻo
|
||||||
|
eox ẽo
|
||||||
|
Eox Ẽo
|
||||||
|
eoj ẹo
|
||||||
|
Eoj Ẹo
|
||||||
eu eu
|
eu eu
|
||||||
Eu Eu
|
Eu Eu
|
||||||
eus éu
|
eus éu
|
||||||
@@ -154,6 +166,18 @@ eux ẽu
|
|||||||
Eux Ẽu
|
Eux Ẽu
|
||||||
euj ẹu
|
euj ẹu
|
||||||
Euj Ẹu
|
Euj Ẹu
|
||||||
|
ia ia
|
||||||
|
Ia Ia
|
||||||
|
ias ía
|
||||||
|
Ias Ía
|
||||||
|
iaf ìa
|
||||||
|
Iaf Ìa
|
||||||
|
iar ỉa
|
||||||
|
Iar Ỉa
|
||||||
|
iax ĩa
|
||||||
|
Iax Ĩa
|
||||||
|
iaj ịa
|
||||||
|
Iaj Ịa
|
||||||
iu iu
|
iu iu
|
||||||
Iu Iu
|
Iu Iu
|
||||||
ius íu
|
ius íu
|
||||||
@@ -178,6 +202,18 @@ oix õi
|
|||||||
Oix Õi
|
Oix Õi
|
||||||
oij ọi
|
oij ọi
|
||||||
Oij Ọi
|
Oij Ọi
|
||||||
|
ua ua
|
||||||
|
Ua Ua
|
||||||
|
uas úa
|
||||||
|
Uas Úa
|
||||||
|
uaf ùa
|
||||||
|
Uaf Ùa
|
||||||
|
uar ủa
|
||||||
|
Uar Ủa
|
||||||
|
uax ũa
|
||||||
|
Uax Ũa
|
||||||
|
uaj ụa
|
||||||
|
Uaj Ụa
|
||||||
ui ui
|
ui ui
|
||||||
Ui Ui
|
Ui Ui
|
||||||
uis úi
|
uis úi
|
||||||
@@ -202,6 +238,114 @@ uyx uỹ
|
|||||||
Uyx Uỹ
|
Uyx Uỹ
|
||||||
uyj uỵ
|
uyj uỵ
|
||||||
Uyj Uỵ
|
Uyj Uỵ
|
||||||
|
aau âu
|
||||||
|
Aau Âu
|
||||||
|
aaus ấu
|
||||||
|
Aaus Ấu
|
||||||
|
aauf ầu
|
||||||
|
Aauf Ầu
|
||||||
|
aaur ẩu
|
||||||
|
Aaur Ẩu
|
||||||
|
aaux ẫu
|
||||||
|
Aaux Ẫu
|
||||||
|
aauj ậu
|
||||||
|
Aauj Ậu
|
||||||
|
aay ây
|
||||||
|
Aay Ây
|
||||||
|
aays ấy
|
||||||
|
Aays Ấy
|
||||||
|
aayf ầy
|
||||||
|
Aayf Ầy
|
||||||
|
aayr ẩy
|
||||||
|
Aayr Ẩy
|
||||||
|
aayx ẫy
|
||||||
|
Aayx Ẫy
|
||||||
|
aayj ậy
|
||||||
|
Aayj Ậy
|
||||||
|
eeu êu
|
||||||
|
Eeu Êu
|
||||||
|
eeus ếu
|
||||||
|
Eeus Ếu
|
||||||
|
eeuf ều
|
||||||
|
Eeuf Ều
|
||||||
|
eeur ểu
|
||||||
|
Eeur Ểu
|
||||||
|
eeux ễu
|
||||||
|
Eeux Ễu
|
||||||
|
eeuj ệu
|
||||||
|
Eeuj Ệu
|
||||||
|
ooi ôi
|
||||||
|
Ooi Ôi
|
||||||
|
oois ối
|
||||||
|
Oois Ối
|
||||||
|
ooif ồi
|
||||||
|
Ooif Ồi
|
||||||
|
ooir ổi
|
||||||
|
Ooir Ổi
|
||||||
|
ooix ỗi
|
||||||
|
Ooix Ỗi
|
||||||
|
ooij ội
|
||||||
|
Ooij Ội
|
||||||
|
owi ơi
|
||||||
|
Owi Ơi
|
||||||
|
owis ới
|
||||||
|
Owis Ới
|
||||||
|
owif ời
|
||||||
|
Owif Ời
|
||||||
|
owir ởi
|
||||||
|
Owir Ởi
|
||||||
|
owix ỡi
|
||||||
|
Owix Ỡi
|
||||||
|
owij ợi
|
||||||
|
Owij Ợi
|
||||||
|
uwi ưi
|
||||||
|
Uwi Ưi
|
||||||
|
uwis ứi
|
||||||
|
Uwis Ứi
|
||||||
|
uwif ừi
|
||||||
|
Uwif Ừi
|
||||||
|
uwir ửi
|
||||||
|
Uwir Ửi
|
||||||
|
uwix ữi
|
||||||
|
Uwix Ữi
|
||||||
|
uwij ựi
|
||||||
|
Uwij Ựi
|
||||||
|
uwu ưu
|
||||||
|
Uwu Ưu
|
||||||
|
uwus ứu
|
||||||
|
Uwus Ứu
|
||||||
|
uwuf ừu
|
||||||
|
Uwuf Ừu
|
||||||
|
uwur ửu
|
||||||
|
Uwur Ửu
|
||||||
|
uwux ữu
|
||||||
|
Uwux Ữu
|
||||||
|
uwuj ựu
|
||||||
|
Uwuj Ựu
|
||||||
|
uwa ưa
|
||||||
|
Uwa Ưa
|
||||||
|
uwas ứa
|
||||||
|
Uwas Ứa
|
||||||
|
uwaf ừa
|
||||||
|
Uwaf Ừa
|
||||||
|
uwar ửa
|
||||||
|
Uwar Ửa
|
||||||
|
uwax ữa
|
||||||
|
Uwax Ữa
|
||||||
|
uwaj ựa
|
||||||
|
Uwaj Ựa
|
||||||
|
uee uê
|
||||||
|
Uee Uê
|
||||||
|
uees uế
|
||||||
|
Uees Uế
|
||||||
|
ueef uề
|
||||||
|
Ueef Uề
|
||||||
|
ueer uể
|
||||||
|
Ueer Uể
|
||||||
|
ueex uễ
|
||||||
|
Ueex Uễ
|
||||||
|
ueej uệ
|
||||||
|
Ueej Uệ
|
||||||
iee iê
|
iee iê
|
||||||
Iee Iê
|
Iee Iê
|
||||||
iees iế
|
iees iế
|
||||||
@@ -274,18 +418,6 @@ oawx oẵ
|
|||||||
Oawx Oẵ
|
Oawx Oẵ
|
||||||
oawj oặ
|
oawj oặ
|
||||||
Oawj Oặ
|
Oawj Oặ
|
||||||
uwa ưa
|
|
||||||
Uwa Ưa
|
|
||||||
uwas ưá
|
|
||||||
Uwas Ưá
|
|
||||||
uwaf ưà
|
|
||||||
Uwaf Ưà
|
|
||||||
uwar ưả
|
|
||||||
Uwar Ưả
|
|
||||||
uwax ưã
|
|
||||||
Uwax Ưã
|
|
||||||
uwaj ưạ
|
|
||||||
Uwaj Ưạ
|
|
||||||
uwow ươ
|
uwow ươ
|
||||||
Uwow Ươ
|
Uwow Ươ
|
||||||
uwows ướ
|
uwows ướ
|
||||||
@@ -304,8 +436,6 @@ ye ye
|
|||||||
Ye Ye
|
Ye Ye
|
||||||
uo uo
|
uo uo
|
||||||
Uo Uo
|
Uo Uo
|
||||||
ua ua
|
|
||||||
Ua Ua
|
|
||||||
ieeu iêu
|
ieeu iêu
|
||||||
Ieeu Iêu
|
Ieeu Iêu
|
||||||
ieeus iếu
|
ieeus iếu
|
||||||
@@ -354,6 +484,54 @@ uowix ưỡi
|
|||||||
Uowix Ưỡi
|
Uowix Ưỡi
|
||||||
uowij ượi
|
uowij ượi
|
||||||
Uowij Ượi
|
Uowij Ượi
|
||||||
|
uowu ươu
|
||||||
|
Uowu Ươu
|
||||||
|
uowus ướu
|
||||||
|
Uowus Ướu
|
||||||
|
uowuf ườu
|
||||||
|
Uowuf Ườu
|
||||||
|
uowur ưởu
|
||||||
|
Uowur Ưởu
|
||||||
|
uowux ưỡu
|
||||||
|
Uowux Ưỡu
|
||||||
|
uowuj ượu
|
||||||
|
Uowuj Ượu
|
||||||
|
uwowi ươi
|
||||||
|
Uwowi Ươi
|
||||||
|
uwowis ưới
|
||||||
|
Uwowis Ưới
|
||||||
|
uwowif ười
|
||||||
|
Uwowif Ười
|
||||||
|
uwowir ưởi
|
||||||
|
Uwowir Ưởi
|
||||||
|
uwowix ưỡi
|
||||||
|
Uwowix Ưỡi
|
||||||
|
uwowij ượi
|
||||||
|
Uwowij Ượi
|
||||||
|
uwowu ươu
|
||||||
|
Uwowu Ươu
|
||||||
|
uwowus ướu
|
||||||
|
Uwowus Ướu
|
||||||
|
uwowuf ườu
|
||||||
|
Uwowuf Ườu
|
||||||
|
uwowur ưởu
|
||||||
|
Uwowur Ưởu
|
||||||
|
uwowux ưỡu
|
||||||
|
Uwowux Ưỡu
|
||||||
|
uwowuj ượu
|
||||||
|
Uwowuj Ượu
|
||||||
|
uaay uây
|
||||||
|
Uaay Uây
|
||||||
|
uaays uấy
|
||||||
|
Uaays Uấy
|
||||||
|
uaayf uầy
|
||||||
|
Uaayf Uầy
|
||||||
|
uaayr uẩy
|
||||||
|
Uaayr Uẩy
|
||||||
|
uaayx uẫy
|
||||||
|
Uaayx Uẫy
|
||||||
|
uaayj uậy
|
||||||
|
Uaayj Uậy
|
||||||
oai oai
|
oai oai
|
||||||
Oai Oai
|
Oai Oai
|
||||||
oais oái
|
oais oái
|
||||||
@@ -378,6 +556,42 @@ oayx oãy
|
|||||||
Oayx Oãy
|
Oayx Oãy
|
||||||
oayj oạy
|
oayj oạy
|
||||||
Oayj Oạy
|
Oayj Oạy
|
||||||
|
oeo oeo
|
||||||
|
Oeo Oeo
|
||||||
|
oeos oéo
|
||||||
|
Oeos Oéo
|
||||||
|
oeof oèo
|
||||||
|
Oeof Oèo
|
||||||
|
oeor oẻo
|
||||||
|
Oeor Oẻo
|
||||||
|
oeox oẽo
|
||||||
|
Oeox Oẽo
|
||||||
|
oeoj oẹo
|
||||||
|
Oeoj Oẹo
|
||||||
|
uya uya
|
||||||
|
Uya Uya
|
||||||
|
uyas uýa
|
||||||
|
Uyas Uýa
|
||||||
|
uyaf uỳa
|
||||||
|
Uyaf Uỳa
|
||||||
|
uyar uỷa
|
||||||
|
Uyar Uỷa
|
||||||
|
uyax uỹa
|
||||||
|
Uyax Uỹa
|
||||||
|
uyaj uỵa
|
||||||
|
Uyaj Uỵa
|
||||||
|
uyu uyu
|
||||||
|
Uyu Uyu
|
||||||
|
uyus uýu
|
||||||
|
Uyus Uýu
|
||||||
|
uyuf uỳu
|
||||||
|
Uyuf Uỳu
|
||||||
|
uyur uỷu
|
||||||
|
Uyur Uỷu
|
||||||
|
uyux uỹu
|
||||||
|
Uyux Uỹu
|
||||||
|
uyuj uỵu
|
||||||
|
Uyuj Uỵu
|
||||||
uyee uyê
|
uyee uyê
|
||||||
Uyee Uyê
|
Uyee Uyê
|
||||||
uyees uyế
|
uyees uyế
|
||||||
@@ -390,8 +604,6 @@ uyeex uyễ
|
|||||||
Uyeex Uyễ
|
Uyeex Uyễ
|
||||||
uyeej uyệ
|
uyeej uyệ
|
||||||
Uyeej Uyệ
|
Uyeej Uyệ
|
||||||
uya uya
|
|
||||||
Uya Uya
|
|
||||||
aw ă
|
aw ă
|
||||||
Aw Ă
|
Aw Ă
|
||||||
aa â
|
aa â
|
||||||
@@ -2146,6 +2358,198 @@ uownhx ưỡnh
|
|||||||
Uownhx Ưỡnh
|
Uownhx Ưỡnh
|
||||||
uownhj ượnh
|
uownhj ượnh
|
||||||
Uownhj Ượnh
|
Uownhj Ượnh
|
||||||
|
uwowc ươc
|
||||||
|
Uwowc Ươc
|
||||||
|
uwowcs ước
|
||||||
|
Uwowcs Ước
|
||||||
|
uwowcf ườc
|
||||||
|
Uwowcf Ườc
|
||||||
|
uwowcr ưởc
|
||||||
|
Uwowcr Ưởc
|
||||||
|
uwowcx ưỡc
|
||||||
|
Uwowcx Ưỡc
|
||||||
|
uwowcj ược
|
||||||
|
Uwowcj Ược
|
||||||
|
uwowm ươm
|
||||||
|
Uwowm Ươm
|
||||||
|
uwowms ướm
|
||||||
|
Uwowms Ướm
|
||||||
|
uwowmf ườm
|
||||||
|
Uwowmf Ườm
|
||||||
|
uwowmr ưởm
|
||||||
|
Uwowmr Ưởm
|
||||||
|
uwowmx ưỡm
|
||||||
|
Uwowmx Ưỡm
|
||||||
|
uwowmj ượm
|
||||||
|
Uwowmj Ượm
|
||||||
|
uwown ươn
|
||||||
|
Uwown Ươn
|
||||||
|
uwowns ướn
|
||||||
|
Uwowns Ướn
|
||||||
|
uwownf ườn
|
||||||
|
Uwownf Ườn
|
||||||
|
uwownr ưởn
|
||||||
|
Uwownr Ưởn
|
||||||
|
uwownx ưỡn
|
||||||
|
Uwownx Ưỡn
|
||||||
|
uwownj ượn
|
||||||
|
Uwownj Ượn
|
||||||
|
uwowp ươp
|
||||||
|
Uwowp Ươp
|
||||||
|
uwowps ướp
|
||||||
|
Uwowps Ướp
|
||||||
|
uwowpf ườp
|
||||||
|
Uwowpf Ườp
|
||||||
|
uwowpr ưởp
|
||||||
|
Uwowpr Ưởp
|
||||||
|
uwowpx ưỡp
|
||||||
|
Uwowpx Ưỡp
|
||||||
|
uwowpj ượp
|
||||||
|
Uwowpj Ượp
|
||||||
|
uwowt ươt
|
||||||
|
Uwowt Ươt
|
||||||
|
uwowts ướt
|
||||||
|
Uwowts Ướt
|
||||||
|
uwowtf ườt
|
||||||
|
Uwowtf Ườt
|
||||||
|
uwowtr ưởt
|
||||||
|
Uwowtr Ưởt
|
||||||
|
uwowtx ưỡt
|
||||||
|
Uwowtx Ưỡt
|
||||||
|
uwowtj ượt
|
||||||
|
Uwowtj Ượt
|
||||||
|
uwowch ươch
|
||||||
|
Uwowch Ươch
|
||||||
|
uwowchs ướch
|
||||||
|
Uwowchs Ướch
|
||||||
|
uwowchf ườch
|
||||||
|
Uwowchf Ườch
|
||||||
|
uwowchr ưởch
|
||||||
|
Uwowchr Ưởch
|
||||||
|
uwowchx ưỡch
|
||||||
|
Uwowchx Ưỡch
|
||||||
|
uwowchj ượch
|
||||||
|
Uwowchj Ượch
|
||||||
|
uwowng ương
|
||||||
|
Uwowng Ương
|
||||||
|
uwowngs ướng
|
||||||
|
Uwowngs Ướng
|
||||||
|
uwowngf ường
|
||||||
|
Uwowngf Ường
|
||||||
|
uwowngr ưởng
|
||||||
|
Uwowngr Ưởng
|
||||||
|
uwowngx ưỡng
|
||||||
|
Uwowngx Ưỡng
|
||||||
|
uwowngj ượng
|
||||||
|
Uwowngj Ượng
|
||||||
|
uwownh ươnh
|
||||||
|
Uwownh Ươnh
|
||||||
|
uwownhs ướnh
|
||||||
|
Uwownhs Ướnh
|
||||||
|
uwownhf ườnh
|
||||||
|
Uwownhf Ườnh
|
||||||
|
uwownhr ưởnh
|
||||||
|
Uwownhr Ưởnh
|
||||||
|
uwownhx ưỡnh
|
||||||
|
Uwownhx Ưỡnh
|
||||||
|
uwownhj ượnh
|
||||||
|
Uwownhj Ượnh
|
||||||
|
uyeec uyêc
|
||||||
|
Uyeec Uyêc
|
||||||
|
uyeecs uyếc
|
||||||
|
Uyeecs Uyếc
|
||||||
|
uyeecf uyềc
|
||||||
|
Uyeecf Uyềc
|
||||||
|
uyeecr uyểc
|
||||||
|
Uyeecr Uyểc
|
||||||
|
uyeecx uyễc
|
||||||
|
Uyeecx Uyễc
|
||||||
|
uyeecj uyệc
|
||||||
|
Uyeecj Uyệc
|
||||||
|
uyeem uyêm
|
||||||
|
Uyeem Uyêm
|
||||||
|
uyeems uyếm
|
||||||
|
Uyeems Uyếm
|
||||||
|
uyeemf uyềm
|
||||||
|
Uyeemf Uyềm
|
||||||
|
uyeemr uyểm
|
||||||
|
Uyeemr Uyểm
|
||||||
|
uyeemx uyễm
|
||||||
|
Uyeemx Uyễm
|
||||||
|
uyeemj uyệm
|
||||||
|
Uyeemj Uyệm
|
||||||
|
uyeen uyên
|
||||||
|
Uyeen Uyên
|
||||||
|
uyeens uyến
|
||||||
|
Uyeens Uyến
|
||||||
|
uyeenf uyền
|
||||||
|
Uyeenf Uyền
|
||||||
|
uyeenr uyển
|
||||||
|
Uyeenr Uyển
|
||||||
|
uyeenx uyễn
|
||||||
|
Uyeenx Uyễn
|
||||||
|
uyeenj uyện
|
||||||
|
Uyeenj Uyện
|
||||||
|
uyeep uyêp
|
||||||
|
Uyeep Uyêp
|
||||||
|
uyeeps uyếp
|
||||||
|
Uyeeps Uyếp
|
||||||
|
uyeepf uyềp
|
||||||
|
Uyeepf Uyềp
|
||||||
|
uyeepr uyểp
|
||||||
|
Uyeepr Uyểp
|
||||||
|
uyeepx uyễp
|
||||||
|
Uyeepx Uyễp
|
||||||
|
uyeepj uyệp
|
||||||
|
Uyeepj Uyệp
|
||||||
|
uyeet uyêt
|
||||||
|
Uyeet Uyêt
|
||||||
|
uyeets uyết
|
||||||
|
Uyeets Uyết
|
||||||
|
uyeetf uyềt
|
||||||
|
Uyeetf Uyềt
|
||||||
|
uyeetr uyểt
|
||||||
|
Uyeetr Uyểt
|
||||||
|
uyeetx uyễt
|
||||||
|
Uyeetx Uyễt
|
||||||
|
uyeetj uyệt
|
||||||
|
Uyeetj Uyệt
|
||||||
|
uyeech uyêch
|
||||||
|
Uyeech Uyêch
|
||||||
|
uyeechs uyếch
|
||||||
|
Uyeechs Uyếch
|
||||||
|
uyeechf uyềch
|
||||||
|
Uyeechf Uyềch
|
||||||
|
uyeechr uyểch
|
||||||
|
Uyeechr Uyểch
|
||||||
|
uyeechx uyễch
|
||||||
|
Uyeechx Uyễch
|
||||||
|
uyeechj uyệch
|
||||||
|
Uyeechj Uyệch
|
||||||
|
uyeeng uyêng
|
||||||
|
Uyeeng Uyêng
|
||||||
|
uyeengs uyếng
|
||||||
|
Uyeengs Uyếng
|
||||||
|
uyeengf uyềng
|
||||||
|
Uyeengf Uyềng
|
||||||
|
uyeengr uyểng
|
||||||
|
Uyeengr Uyểng
|
||||||
|
uyeengx uyễng
|
||||||
|
Uyeengx Uyễng
|
||||||
|
uyeengj uyệng
|
||||||
|
Uyeengj Uyệng
|
||||||
|
uyeenh uyênh
|
||||||
|
Uyeenh Uyênh
|
||||||
|
uyeenhs uyếnh
|
||||||
|
Uyeenhs Uyếnh
|
||||||
|
uyeenhf uyềnh
|
||||||
|
Uyeenhf Uyềnh
|
||||||
|
uyeenhr uyểnh
|
||||||
|
Uyeenhr Uyểnh
|
||||||
|
uyeenhx uyễnh
|
||||||
|
Uyeenhx Uyễnh
|
||||||
|
uyeenhj uyệnh
|
||||||
|
Uyeenhj Uyệnh
|
||||||
uaac uâc
|
uaac uâc
|
||||||
Uaac Uâc
|
Uaac Uâc
|
||||||
uaacs uấc
|
uaacs uấc
|
||||||
@@ -2242,6 +2646,102 @@ uaanhx uẫnh
|
|||||||
Uaanhx Uẫnh
|
Uaanhx Uẫnh
|
||||||
uaanhj uậnh
|
uaanhj uậnh
|
||||||
Uaanhj Uậnh
|
Uaanhj Uậnh
|
||||||
|
ueec uêc
|
||||||
|
Ueec Uêc
|
||||||
|
ueecs uếc
|
||||||
|
Ueecs Uếc
|
||||||
|
ueecf uềc
|
||||||
|
Ueecf Uềc
|
||||||
|
ueecr uểc
|
||||||
|
Ueecr Uểc
|
||||||
|
ueecx uễc
|
||||||
|
Ueecx Uễc
|
||||||
|
ueecj uệc
|
||||||
|
Ueecj Uệc
|
||||||
|
ueem uêm
|
||||||
|
Ueem Uêm
|
||||||
|
ueems uếm
|
||||||
|
Ueems Uếm
|
||||||
|
ueemf uềm
|
||||||
|
Ueemf Uềm
|
||||||
|
ueemr uểm
|
||||||
|
Ueemr Uểm
|
||||||
|
ueemx uễm
|
||||||
|
Ueemx Uễm
|
||||||
|
ueemj uệm
|
||||||
|
Ueemj Uệm
|
||||||
|
ueen uên
|
||||||
|
Ueen Uên
|
||||||
|
ueens uến
|
||||||
|
Ueens Uến
|
||||||
|
ueenf uền
|
||||||
|
Ueenf Uền
|
||||||
|
ueenr uển
|
||||||
|
Ueenr Uển
|
||||||
|
ueenx uễn
|
||||||
|
Ueenx Uễn
|
||||||
|
ueenj uện
|
||||||
|
Ueenj Uện
|
||||||
|
ueep uêp
|
||||||
|
Ueep Uêp
|
||||||
|
ueeps uếp
|
||||||
|
Ueeps Uếp
|
||||||
|
ueepf uềp
|
||||||
|
Ueepf Uềp
|
||||||
|
ueepr uểp
|
||||||
|
Ueepr Uểp
|
||||||
|
ueepx uễp
|
||||||
|
Ueepx Uễp
|
||||||
|
ueepj uệp
|
||||||
|
Ueepj Uệp
|
||||||
|
ueet uêt
|
||||||
|
Ueet Uêt
|
||||||
|
ueets uết
|
||||||
|
Ueets Uết
|
||||||
|
ueetf uềt
|
||||||
|
Ueetf Uềt
|
||||||
|
ueetr uểt
|
||||||
|
Ueetr Uểt
|
||||||
|
ueetx uễt
|
||||||
|
Ueetx Uễt
|
||||||
|
ueetj uệt
|
||||||
|
Ueetj Uệt
|
||||||
|
ueech uêch
|
||||||
|
Ueech Uêch
|
||||||
|
ueechs uếch
|
||||||
|
Ueechs Uếch
|
||||||
|
ueechf uềch
|
||||||
|
Ueechf Uềch
|
||||||
|
ueechr uểch
|
||||||
|
Ueechr Uểch
|
||||||
|
ueechx uễch
|
||||||
|
Ueechx Uễch
|
||||||
|
ueechj uệch
|
||||||
|
Ueechj Uệch
|
||||||
|
ueeng uêng
|
||||||
|
Ueeng Uêng
|
||||||
|
ueengs uếng
|
||||||
|
Ueengs Uếng
|
||||||
|
ueengf uềng
|
||||||
|
Ueengf Uềng
|
||||||
|
ueengr uểng
|
||||||
|
Ueengr Uểng
|
||||||
|
ueengx uễng
|
||||||
|
Ueengx Uễng
|
||||||
|
ueengj uệng
|
||||||
|
Ueengj Uệng
|
||||||
|
ueenh uênh
|
||||||
|
Ueenh Uênh
|
||||||
|
ueenhs uếnh
|
||||||
|
Ueenhs Uếnh
|
||||||
|
ueenhf uềnh
|
||||||
|
Ueenhf Uềnh
|
||||||
|
ueenhr uểnh
|
||||||
|
Ueenhr Uểnh
|
||||||
|
ueenhx uễnh
|
||||||
|
Ueenhx Uễnh
|
||||||
|
ueenhj uệnh
|
||||||
|
Ueenhj Uệnh
|
||||||
oawc oăc
|
oawc oăc
|
||||||
Oawc Oăc
|
Oawc Oăc
|
||||||
oawcs oắc
|
oawcs oắc
|
||||||
@@ -2818,6 +3318,102 @@ oanhx oãnh
|
|||||||
Oanhx Oãnh
|
Oanhx Oãnh
|
||||||
oanhj oạnh
|
oanhj oạnh
|
||||||
Oanhj Oạnh
|
Oanhj Oạnh
|
||||||
|
oec oec
|
||||||
|
Oec Oec
|
||||||
|
oecs oéc
|
||||||
|
Oecs Oéc
|
||||||
|
oecf oèc
|
||||||
|
Oecf Oèc
|
||||||
|
oecr oẻc
|
||||||
|
Oecr Oẻc
|
||||||
|
oecx oẽc
|
||||||
|
Oecx Oẽc
|
||||||
|
oecj oẹc
|
||||||
|
Oecj Oẹc
|
||||||
|
oem oem
|
||||||
|
Oem Oem
|
||||||
|
oems oém
|
||||||
|
Oems Oém
|
||||||
|
oemf oèm
|
||||||
|
Oemf Oèm
|
||||||
|
oemr oẻm
|
||||||
|
Oemr Oẻm
|
||||||
|
oemx oẽm
|
||||||
|
Oemx Oẽm
|
||||||
|
oemj oẹm
|
||||||
|
Oemj Oẹm
|
||||||
|
oen oen
|
||||||
|
Oen Oen
|
||||||
|
oens oén
|
||||||
|
Oens Oén
|
||||||
|
oenf oèn
|
||||||
|
Oenf Oèn
|
||||||
|
oenr oẻn
|
||||||
|
Oenr Oẻn
|
||||||
|
oenx oẽn
|
||||||
|
Oenx Oẽn
|
||||||
|
oenj oẹn
|
||||||
|
Oenj Oẹn
|
||||||
|
oep oep
|
||||||
|
Oep Oep
|
||||||
|
oeps oép
|
||||||
|
Oeps Oép
|
||||||
|
oepf oèp
|
||||||
|
Oepf Oèp
|
||||||
|
oepr oẻp
|
||||||
|
Oepr Oẻp
|
||||||
|
oepx oẽp
|
||||||
|
Oepx Oẽp
|
||||||
|
oepj oẹp
|
||||||
|
Oepj Oẹp
|
||||||
|
oet oet
|
||||||
|
Oet Oet
|
||||||
|
oets oét
|
||||||
|
Oets Oét
|
||||||
|
oetf oèt
|
||||||
|
Oetf Oèt
|
||||||
|
oetr oẻt
|
||||||
|
Oetr Oẻt
|
||||||
|
oetx oẽt
|
||||||
|
Oetx Oẽt
|
||||||
|
oetj oẹt
|
||||||
|
Oetj Oẹt
|
||||||
|
oech oech
|
||||||
|
Oech Oech
|
||||||
|
oechs oéch
|
||||||
|
Oechs Oéch
|
||||||
|
oechf oèch
|
||||||
|
Oechf Oèch
|
||||||
|
oechr oẻch
|
||||||
|
Oechr Oẻch
|
||||||
|
oechx oẽch
|
||||||
|
Oechx Oẽch
|
||||||
|
oechj oẹch
|
||||||
|
Oechj Oẹch
|
||||||
|
oeng oeng
|
||||||
|
Oeng Oeng
|
||||||
|
oengs oéng
|
||||||
|
Oengs Oéng
|
||||||
|
oengf oèng
|
||||||
|
Oengf Oèng
|
||||||
|
oengr oẻng
|
||||||
|
Oengr Oẻng
|
||||||
|
oengx oẽng
|
||||||
|
Oengx Oẽng
|
||||||
|
oengj oẹng
|
||||||
|
Oengj Oẹng
|
||||||
|
oenh oenh
|
||||||
|
Oenh Oenh
|
||||||
|
oenhs oénh
|
||||||
|
Oenhs Oénh
|
||||||
|
oenhf oènh
|
||||||
|
Oenhf Oènh
|
||||||
|
oenhr oẻnh
|
||||||
|
Oenhr Oẻnh
|
||||||
|
oenhx oẽnh
|
||||||
|
Oenhx Oẽnh
|
||||||
|
oenhj oẹnh
|
||||||
|
Oenhj Oẹnh
|
||||||
oic oic
|
oic oic
|
||||||
Oic Oic
|
Oic Oic
|
||||||
oics óic
|
oics óic
|
||||||
@@ -3153,10 +3749,18 @@ quay quay
|
|||||||
Quay Quay
|
Quay Quay
|
||||||
giay giay
|
giay giay
|
||||||
Giay Giay
|
Giay Giay
|
||||||
|
queo queo
|
||||||
|
Queo Queo
|
||||||
|
gieo gieo
|
||||||
|
Gieo Gieo
|
||||||
queu queu
|
queu queu
|
||||||
Queu Queu
|
Queu Queu
|
||||||
gieu gieu
|
gieu gieu
|
||||||
Gieu Gieu
|
Gieu Gieu
|
||||||
|
quia quia
|
||||||
|
Quia Quia
|
||||||
|
giia giia
|
||||||
|
Giia Giia
|
||||||
quiu quiu
|
quiu quiu
|
||||||
Quiu Quiu
|
Quiu Quiu
|
||||||
giiu giiu
|
giiu giiu
|
||||||
@@ -3165,10 +3769,40 @@ quoi quoi
|
|||||||
Quoi Quoi
|
Quoi Quoi
|
||||||
gioi gioi
|
gioi gioi
|
||||||
Gioi Gioi
|
Gioi Gioi
|
||||||
|
giua giua
|
||||||
|
Giua Giua
|
||||||
giui giui
|
giui giui
|
||||||
Giui Giui
|
Giui Giui
|
||||||
giuy giuy
|
giuy giuy
|
||||||
Giuy Giuy
|
Giuy Giuy
|
||||||
|
quaau quâu
|
||||||
|
Quaau Quâu
|
||||||
|
giaau giâu
|
||||||
|
Giaau Giâu
|
||||||
|
quaay quây
|
||||||
|
Quaay Quây
|
||||||
|
giaay giây
|
||||||
|
Giaay Giây
|
||||||
|
queeu quêu
|
||||||
|
Queeu Quêu
|
||||||
|
gieeu giêu
|
||||||
|
Gieeu Giêu
|
||||||
|
quooi quôi
|
||||||
|
Quooi Quôi
|
||||||
|
giooi giôi
|
||||||
|
Giooi Giôi
|
||||||
|
quowi quơi
|
||||||
|
Quowi Quơi
|
||||||
|
giowi giơi
|
||||||
|
Giowi Giơi
|
||||||
|
giuwi giưi
|
||||||
|
Giuwi Giưi
|
||||||
|
giuwu giưu
|
||||||
|
Giuwu Giưu
|
||||||
|
giuwa giưa
|
||||||
|
Giuwa Giưa
|
||||||
|
giuee giuê
|
||||||
|
Giuee Giuê
|
||||||
quiee quiê
|
quiee quiê
|
||||||
Quiee Quiê
|
Quiee Quiê
|
||||||
giiee giiê
|
giiee giiê
|
||||||
@@ -3187,8 +3821,6 @@ quoaw quoă
|
|||||||
Quoaw Quoă
|
Quoaw Quoă
|
||||||
gioaw gioă
|
gioaw gioă
|
||||||
Gioaw Gioă
|
Gioaw Gioă
|
||||||
giuwa giưa
|
|
||||||
Giuwa Giưa
|
|
||||||
giuwow giươ
|
giuwow giươ
|
||||||
Giuwow Giươ
|
Giuwow Giươ
|
||||||
quie quie
|
quie quie
|
||||||
@@ -3201,8 +3833,6 @@ giye giye
|
|||||||
Giye Giye
|
Giye Giye
|
||||||
giuo giuo
|
giuo giuo
|
||||||
Giuo Giuo
|
Giuo Giuo
|
||||||
giua giua
|
|
||||||
Giua Giua
|
|
||||||
quieeu quiêu
|
quieeu quiêu
|
||||||
Quieeu Quiêu
|
Quieeu Quiêu
|
||||||
giieeu giiêu
|
giieeu giiêu
|
||||||
@@ -3215,6 +3845,14 @@ giuooi giuôi
|
|||||||
Giuooi Giuôi
|
Giuooi Giuôi
|
||||||
giuowi giươi
|
giuowi giươi
|
||||||
Giuowi Giươi
|
Giuowi Giươi
|
||||||
|
giuowu giươu
|
||||||
|
Giuowu Giươu
|
||||||
|
giuwowi giươi
|
||||||
|
Giuwowi Giươi
|
||||||
|
giuwowu giươu
|
||||||
|
Giuwowu Giươu
|
||||||
|
giuaay giuây
|
||||||
|
Giuaay Giuây
|
||||||
quoai quoai
|
quoai quoai
|
||||||
Quoai Quoai
|
Quoai Quoai
|
||||||
gioai gioai
|
gioai gioai
|
||||||
@@ -3223,10 +3861,16 @@ quoay quoay
|
|||||||
Quoay Quoay
|
Quoay Quoay
|
||||||
gioay gioay
|
gioay gioay
|
||||||
Gioay Gioay
|
Gioay Gioay
|
||||||
giuyee giuyê
|
quoeo quoeo
|
||||||
Giuyee Giuyê
|
Quoeo Quoeo
|
||||||
|
gioeo gioeo
|
||||||
|
Gioeo Gioeo
|
||||||
giuya giuya
|
giuya giuya
|
||||||
Giuya Giuya
|
Giuya Giuya
|
||||||
|
giuyu giuyu
|
||||||
|
Giuyu Giuyu
|
||||||
|
giuyee giuyê
|
||||||
|
Giuyee Giuyê
|
||||||
quaw quă
|
quaw quă
|
||||||
Quaw Quă
|
Quaw Quă
|
||||||
giaw giă
|
giaw giă
|
||||||
@@ -3719,6 +4363,38 @@ giuowng giương
|
|||||||
Giuowng Giương
|
Giuowng Giương
|
||||||
giuownh giươnh
|
giuownh giươnh
|
||||||
Giuownh Giươnh
|
Giuownh Giươnh
|
||||||
|
giuwowc giươc
|
||||||
|
Giuwowc Giươc
|
||||||
|
giuwowm giươm
|
||||||
|
Giuwowm Giươm
|
||||||
|
giuwown giươn
|
||||||
|
Giuwown Giươn
|
||||||
|
giuwowp giươp
|
||||||
|
Giuwowp Giươp
|
||||||
|
giuwowt giươt
|
||||||
|
Giuwowt Giươt
|
||||||
|
giuwowch giươch
|
||||||
|
Giuwowch Giươch
|
||||||
|
giuwowng giương
|
||||||
|
Giuwowng Giương
|
||||||
|
giuwownh giươnh
|
||||||
|
Giuwownh Giươnh
|
||||||
|
giuyeec giuyêc
|
||||||
|
Giuyeec Giuyêc
|
||||||
|
giuyeem giuyêm
|
||||||
|
Giuyeem Giuyêm
|
||||||
|
giuyeen giuyên
|
||||||
|
Giuyeen Giuyên
|
||||||
|
giuyeep giuyêp
|
||||||
|
Giuyeep Giuyêp
|
||||||
|
giuyeet giuyêt
|
||||||
|
Giuyeet Giuyêt
|
||||||
|
giuyeech giuyêch
|
||||||
|
Giuyeech Giuyêch
|
||||||
|
giuyeeng giuyêng
|
||||||
|
Giuyeeng Giuyêng
|
||||||
|
giuyeenh giuyênh
|
||||||
|
Giuyeenh Giuyênh
|
||||||
giuaac giuâc
|
giuaac giuâc
|
||||||
Giuaac Giuâc
|
Giuaac Giuâc
|
||||||
giuaam giuâm
|
giuaam giuâm
|
||||||
@@ -3735,6 +4411,22 @@ giuaang giuâng
|
|||||||
Giuaang Giuâng
|
Giuaang Giuâng
|
||||||
giuaanh giuânh
|
giuaanh giuânh
|
||||||
Giuaanh Giuânh
|
Giuaanh Giuânh
|
||||||
|
giueec giuêc
|
||||||
|
Giueec Giuêc
|
||||||
|
giueem giuêm
|
||||||
|
Giueem Giuêm
|
||||||
|
giueen giuên
|
||||||
|
Giueen Giuên
|
||||||
|
giueep giuêp
|
||||||
|
Giueep Giuêp
|
||||||
|
giueet giuêt
|
||||||
|
Giueet Giuêt
|
||||||
|
giueech giuêch
|
||||||
|
Giueech Giuêch
|
||||||
|
giueeng giuêng
|
||||||
|
Giueeng Giuêng
|
||||||
|
giueenh giuênh
|
||||||
|
Giueenh Giuênh
|
||||||
quoawc quoăc
|
quoawc quoăc
|
||||||
Quoawc Quoăc
|
Quoawc Quoăc
|
||||||
gioawc gioăc
|
gioawc gioăc
|
||||||
@@ -3927,6 +4619,38 @@ quoanh quoanh
|
|||||||
Quoanh Quoanh
|
Quoanh Quoanh
|
||||||
gioanh gioanh
|
gioanh gioanh
|
||||||
Gioanh Gioanh
|
Gioanh Gioanh
|
||||||
|
quoec quoec
|
||||||
|
Quoec Quoec
|
||||||
|
gioec gioec
|
||||||
|
Gioec Gioec
|
||||||
|
quoem quoem
|
||||||
|
Quoem Quoem
|
||||||
|
gioem gioem
|
||||||
|
Gioem Gioem
|
||||||
|
quoen quoen
|
||||||
|
Quoen Quoen
|
||||||
|
gioen gioen
|
||||||
|
Gioen Gioen
|
||||||
|
quoep quoep
|
||||||
|
Quoep Quoep
|
||||||
|
gioep gioep
|
||||||
|
Gioep Gioep
|
||||||
|
quoet quoet
|
||||||
|
Quoet Quoet
|
||||||
|
gioet gioet
|
||||||
|
Gioet Gioet
|
||||||
|
quoech quoech
|
||||||
|
Quoech Quoech
|
||||||
|
gioech gioech
|
||||||
|
Gioech Gioech
|
||||||
|
quoeng quoeng
|
||||||
|
Quoeng Quoeng
|
||||||
|
gioeng gioeng
|
||||||
|
Gioeng Gioeng
|
||||||
|
quoenh quoenh
|
||||||
|
Quoenh Quoenh
|
||||||
|
gioenh gioenh
|
||||||
|
Gioenh Gioenh
|
||||||
quoic quoic
|
quoic quoic
|
||||||
Quoic Quoic
|
Quoic Quoic
|
||||||
gioic gioic
|
gioic gioic
|
||||||
|
|||||||
73
map/verifymap.py
Executable file
73
map/verifymap.py
Executable file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Verify the text maps and dictionaries consumed by strans."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def verify(path):
|
||||||
|
errors = []
|
||||||
|
try:
|
||||||
|
data = path.read_bytes()
|
||||||
|
except OSError as error:
|
||||||
|
return [f"{path}: {error.strerror}"]
|
||||||
|
try:
|
||||||
|
text = data.decode("utf-8")
|
||||||
|
except UnicodeDecodeError as error:
|
||||||
|
return [f"{path}: invalid UTF-8: {error}"]
|
||||||
|
|
||||||
|
keys = {}
|
||||||
|
for lineno, line in enumerate(text.split("\n"), 1):
|
||||||
|
where = f"{path}:{lineno}"
|
||||||
|
if "\r" in line:
|
||||||
|
errors.append(f"{where}: carriage return is not canonical")
|
||||||
|
line = line.replace("\r", "")
|
||||||
|
if not line or line.startswith(";"):
|
||||||
|
continue
|
||||||
|
if "\0" in line:
|
||||||
|
errors.append(f"{where}: embedded NUL")
|
||||||
|
if line.count("\t") != 1:
|
||||||
|
errors.append(f"{where}: expected exactly one tab")
|
||||||
|
continue
|
||||||
|
key, value = line.split("\t")
|
||||||
|
if not key:
|
||||||
|
errors.append(f"{where}: empty key")
|
||||||
|
if len(key) > 64:
|
||||||
|
errors.append(f"{where}: key has {len(key)} runes; maximum is 64")
|
||||||
|
if key in keys:
|
||||||
|
errors.append(f"{where}: duplicate key; first defined on line {keys[key]}")
|
||||||
|
else:
|
||||||
|
keys[key] = lineno
|
||||||
|
if not value:
|
||||||
|
errors.append(f"{where}: empty value")
|
||||||
|
continue
|
||||||
|
if value != " ".join(value.split(" ")):
|
||||||
|
errors.append(f"{where}: noncanonical candidate spacing")
|
||||||
|
values = value.split(" ")
|
||||||
|
if path.name.endswith(".map"):
|
||||||
|
if len(value) > 64:
|
||||||
|
errors.append(f"{where}: value exceeds 64 runes")
|
||||||
|
else:
|
||||||
|
for candidate in values:
|
||||||
|
if len(candidate) > 64:
|
||||||
|
errors.append(f"{where}: candidate exceeds 64 runes")
|
||||||
|
if len(values) != len(set(values)):
|
||||||
|
errors.append(f"{where}: duplicate candidate")
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("file", nargs="+", type=Path)
|
||||||
|
args = parser.parse_args()
|
||||||
|
errors = []
|
||||||
|
for path in args.file:
|
||||||
|
errors.extend(verify(path))
|
||||||
|
for error in errors:
|
||||||
|
print(error, file=sys.stderr)
|
||||||
|
return bool(errors)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
83100
map/wubi.dict
83100
map/wubi.dict
File diff suppressed because it is too large
Load Diff
257
popup_layout.c
Normal file
257
popup_layout.c
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
#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));
|
||||||
|
}
|
||||||
494
proto/input-method-unstable-v2.xml
Normal file
494
proto/input-method-unstable-v2.xml
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
<?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>
|
||||||
112
proto/virtual-keyboard-unstable-v1.xml
Normal file
112
proto/virtual-keyboard-unstable-v1.xml
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<?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>
|
||||||
28
run.sh
28
run.sh
@@ -1,8 +1,24 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")" || exit 1
|
||||||
pkill strans
|
|
||||||
sleep 1
|
if test "$#" -ne 0; then
|
||||||
./strans map font &
|
echo "usage: run.sh" >&2
|
||||||
sleep 1
|
exit 1
|
||||||
xim/strans-xim &
|
fi
|
||||||
|
|
||||||
|
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,65 +1,150 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
static char adir[40];
|
#include <sys/stat.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
|
static char adir[256];
|
||||||
|
static char sockpath[sizeof(((struct sockaddr_un*)0)->sun_path)];
|
||||||
|
static dev_t sockdev;
|
||||||
|
static ino_t sockino;
|
||||||
static Channel *clientc;
|
static Channel *clientc;
|
||||||
|
|
||||||
int
|
/* Removes the socket only while it is still the one we announced. */
|
||||||
srvreadkey(int fd, Keyreq *kr)
|
static void
|
||||||
|
srvunlink(void)
|
||||||
{
|
{
|
||||||
uchar req[Ipcreqsz];
|
struct stat st;
|
||||||
u32int ks, mod;
|
|
||||||
int want;
|
|
||||||
|
|
||||||
if(ipcreadn(fd, req, sizeof req) < 0)
|
if(sockpath[0] != '\0' && lstat(sockpath, &st) == 0 &&
|
||||||
return -1;
|
st.st_dev == sockdev && st.st_ino == sockino)
|
||||||
ipcunpackreq(req, &want, &mod, &ks);
|
unlink(sockpath);
|
||||||
kr->ks = ks;
|
sockpath[0] = '\0';
|
||||||
kr->mod = mod;
|
}
|
||||||
kr->want = want;
|
|
||||||
|
static int
|
||||||
|
srvnote(void *v, char *note)
|
||||||
|
{
|
||||||
|
USED(v);
|
||||||
|
if(notefatal(note))
|
||||||
|
srvunlink();
|
||||||
return 0;
|
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)
|
||||||
{
|
{
|
||||||
Channel *reply;
|
|
||||||
int fd;
|
|
||||||
Keyreq kr;
|
Keyreq kr;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
uchar out[Ipcmaxresp];
|
uchar out[Ipcmaxresp], token;
|
||||||
char commit[Maxutf], preedit[Maxutf];
|
char commit[Maxutf], preedit[Maxutf];
|
||||||
int n, ncommit, npreedit;
|
int fd, n, ncommit, npreedit, rv, want;
|
||||||
uchar token;
|
|
||||||
|
|
||||||
fd = (int)(uintptr)arg;
|
fd = (int)(uintptr)arg;
|
||||||
threadsetname("client %d", fd);
|
threadsetname("client %d", fd);
|
||||||
reply = chancreate(sizeof(Keyres), 0);
|
memset(&kr, 0, sizeof kr);
|
||||||
kr.reply = reply;
|
kr.reply = chancreate(sizeof(Keyres), 0);
|
||||||
while(srvreadkey(fd, &kr) == 0){
|
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);
|
chansend(keyc, &kr);
|
||||||
chanrecv(reply, &res);
|
chanrecv(kr.reply, &res);
|
||||||
|
if(kr.op == Keycaret)
|
||||||
|
continue;
|
||||||
ncommit = stoutf(&res.commit, commit, sizeof commit);
|
ncommit = stoutf(&res.commit, commit, sizeof commit);
|
||||||
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
|
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
|
||||||
n = ipcpackresp(out, sizeof out, res.eaten,
|
/* A capability reply is always eaten: it marks the extension. */
|
||||||
commit, ncommit, preedit, npreedit, kr.want);
|
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)
|
if(n < 0 || ipcsend(fd, out, n) < 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
chanfree(reply);
|
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);
|
chanrecv(clientc, &token);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
void
|
||||||
srvinit(void)
|
srvinit(void)
|
||||||
{
|
{
|
||||||
char addr[64];
|
struct stat st;
|
||||||
|
char *addr;
|
||||||
|
|
||||||
snprint(addr, sizeof(addr), "unix!" IPCPATH, getuid());
|
if(ipcpath(sockpath, sizeof sockpath) < 0)
|
||||||
remove(addr + 5);
|
die("IPC path is too long");
|
||||||
|
addr = smprint("unix!%s", sockpath);
|
||||||
|
if(addr == nil)
|
||||||
|
die("out of memory");
|
||||||
if(announce(addr, adir) < 0)
|
if(announce(addr, adir) < 0)
|
||||||
die("announce: %r");
|
die("IPC endpoint is already in use: %r");
|
||||||
|
free(addr);
|
||||||
|
if(chmod(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
|
||||||
@@ -70,7 +155,6 @@ srvthread(void*)
|
|||||||
uchar token;
|
uchar token;
|
||||||
|
|
||||||
threadsetname("srv");
|
threadsetname("srv");
|
||||||
srvinit();
|
|
||||||
token = 0;
|
token = 0;
|
||||||
clientc = chancreate(sizeof token, Maxclients);
|
clientc = chancreate(sizeof token, Maxclients);
|
||||||
for(;;){
|
for(;;){
|
||||||
@@ -81,9 +165,6 @@ srvthread(void*)
|
|||||||
close(fd);
|
close(fd);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(proccreate(clientthread, (void*)(uintptr)fd, 8192) < 0){
|
proccreate(clientthread, (void*)(uintptr)fd, 8192);
|
||||||
chanrecv(clientc, &token);
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5079
stb_truetype.h
5079
stb_truetype.h
File diff suppressed because it is too large
Load Diff
42
str.c
42
str.c
@@ -1,22 +1,42 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
void
|
/* Fills s from n bytes of UTF-8; whole, valid, and at most Maxrunes.
|
||||||
|
* Anything else leaves s empty. */
|
||||||
|
int
|
||||||
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 && s->n < Maxrunes){
|
while(n > 0){
|
||||||
if(!fullrune(src, n))
|
if(tmp.n >= Maxrunes || !fullrune(src, n))
|
||||||
break;
|
return 0;
|
||||||
len = chartorune(&s->r[s->n], src);
|
len = chartorune(&r, src);
|
||||||
if(len > n)
|
if((r == Runeerror && len == 1) || (r >= 0xd800 && r <= 0xdfff))
|
||||||
break;
|
return 0;
|
||||||
s->n++;
|
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
|
||||||
@@ -28,8 +48,9 @@ sclear(Str *s)
|
|||||||
void
|
void
|
||||||
sputr(Str *s, Rune r)
|
sputr(Str *s, Rune r)
|
||||||
{
|
{
|
||||||
|
/* Str is a capped value; appends at capacity leave it unchanged. */
|
||||||
if(s->n >= Maxrunes)
|
if(s->n >= Maxrunes)
|
||||||
die("sputr overflow");
|
return;
|
||||||
s->r[s->n++] = r;
|
s->r[s->n++] = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,14 +84,13 @@ 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];
|
char tmp[UTFmax];
|
||||||
int i, n, len;
|
int i, n, len;
|
||||||
|
|
||||||
if(sz <= 0)
|
|
||||||
return 0;
|
|
||||||
n = 0;
|
n = 0;
|
||||||
for(i = 0; i < s->n; i++){
|
for(i = 0; i < s->n; i++){
|
||||||
len = runetochar(tmp, &s->r[i]);
|
len = runetochar(tmp, &s->r[i]);
|
||||||
|
|||||||
146
tests/Makefile
146
tests/Makefile
@@ -1,34 +1,150 @@
|
|||||||
CC = 9c
|
CC = 9c
|
||||||
LD = 9l
|
LD = 9l
|
||||||
CFLAGS = -std=c99 -Wall -Wextra -O2 -g -I.. -I../cutest
|
HOSTCC = cc
|
||||||
LIBS = -lthread -lbio
|
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
|
PROG = unit_test
|
||||||
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
STRESS = stress_test
|
||||||
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c
|
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)
|
TESTOBJ = $(TESTSRC:.c=.o)
|
||||||
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c
|
# 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)
|
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
||||||
OBJS = $(TESTOBJ) $(PARENTOBJ)
|
COMMONOBJ = $(TESTOBJ) $(PARENTOBJ)
|
||||||
|
OBJS = unit_test.o stress_test.o $(COMMONOBJ)
|
||||||
|
|
||||||
all: $(PROG)
|
all: $(PROG) $(STRESS) $(LIVE)
|
||||||
|
|
||||||
check test: $(PROG)
|
check test: $(PROG)
|
||||||
./$(PROG) $(TESTARGS)
|
./$(PROG) $(UNITARGS)
|
||||||
|
|
||||||
$(PROG): $(OBJS)
|
check-live: $(SMOKE) ../strans ../gtk/im-strans.so
|
||||||
$(LD) -o $@ $(OBJS) $(LIBS)
|
./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
|
||||||
|
|
||||||
$(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h ../cutest/cutest.h
|
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
|
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
|
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJS) $(PROG)
|
rm -f $(OBJS) $(PROG) $(STRESS) $(LIVE)
|
||||||
|
|
||||||
.PHONY: all check test clean
|
.PHONY: all check check-live check-stress test clean \
|
||||||
|
../strans ../gtk/im-strans.so
|
||||||
|
|||||||
56
tests/compose_test.c
Normal file
56
tests/compose_test.c
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#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");
|
||||||
|
}
|
||||||
214
tests/daemon_collision_test.c
Normal file
214
tests/daemon_collision_test.c
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
138
tests/daemon_failure_test.c
Normal file
138
tests/daemon_failure_test.c
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
406
tests/daemon_restart_test.c
Normal file
406
tests/daemon_restart_test.c
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
2
tests/data/compose
Normal file
2
tests/data/compose
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<dead_acute> <e> : "é"
|
||||||
|
<Multi_key> <o> <c> : "©"
|
||||||
4
tests/data/skk/expected.tsv
Normal file
4
tests/data/skk/expected.tsv
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
;; converter fixture; the test transcodes this file to EUC-JP
|
||||||
|
かんじ 漢字 幹事 感じ
|
||||||
|
えがお 笑顔
|
||||||
|
きごう 記号 普通
|
||||||
|
6
tests/data/skk/source.skk.utf8
Normal file
6
tests/data/skk/source.skk.utf8
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
;; converter fixture; the test transcodes this file to EUC-JP
|
||||||
|
かんじ /漢字;common/幹事/
|
||||||
|
えがお /笑顔;face/
|
||||||
|
かんじ /感じ/漢字;duplicate/
|
||||||
|
きごう /記号;symbol/普通/
|
||||||
|
むこう /候補 with space/
|
||||||
@@ -3,4 +3,3 @@ ab beta
|
|||||||
한 값
|
한 값
|
||||||
duplicate first
|
duplicate first
|
||||||
duplicate second
|
duplicate second
|
||||||
tabs one two
|
|
||||||
|
|||||||
@@ -1,35 +1,28 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
dictionary_candidates(struct ct *t)
|
dictionary_candidates(struct ct *t)
|
||||||
{
|
{
|
||||||
char many[512], item[8];
|
char many[8*(Maxkouho+1)], item[8];
|
||||||
char *p;
|
char *p;
|
||||||
Dictreq req;
|
Str kouho[Maxkouho], key;
|
||||||
Dictres res;
|
Trie *saved;
|
||||||
Hmap *saved;
|
|
||||||
Lang *lang;
|
Lang *lang;
|
||||||
Str key;
|
int i, n;
|
||||||
int i;
|
|
||||||
|
|
||||||
lang = getlang(LangJP);
|
lang = getlang(LangJP);
|
||||||
saved = lang->dict;
|
saved = lang->dict;
|
||||||
lang->dict = hmapalloc(1);
|
lang->dict = trienew();
|
||||||
|
trieput(lang->dict, "かな", strlen("かな"), " 候補1 候補2 ",
|
||||||
|
strlen(" 候補1 候補2 "));
|
||||||
key = mkstr("かな");
|
key = mkstr("かな");
|
||||||
hmapset(&lang->dict, &key, " 候補1 かな 候補2 ",
|
n = dictlookup(lang->dict, &key, kouho, Maxkouho);
|
||||||
strlen(" 候補1 かな 候補2 "));
|
if(CT_EQ_INT(t, 2, n)){
|
||||||
memset(&req, 0, sizeof req);
|
checkstr(t, "candidate 1", "候補1", &kouho[0]);
|
||||||
req.key = key;
|
checkstr(t, "candidate 2", "候補2", &kouho[1]);
|
||||||
req.pre = mkstr("preedit-one");
|
}
|
||||||
req.lang = LangJP;
|
|
||||||
dictlookup(&req, &res);
|
|
||||||
if(!CT_EQ_INT(t, 2, res.nkouho))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_INT(t, LangJP, res.lang);
|
|
||||||
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
|
|
||||||
checkstr(t, "candidate 1", "候補1", &res.kouho[0]);
|
|
||||||
checkstr(t, "candidate 2", "候補2", &res.kouho[1]);
|
|
||||||
key = mkstr("key");
|
|
||||||
p = many;
|
p = many;
|
||||||
for(i = 0; i < Maxkouho+1; i++){
|
for(i = 0; i < Maxkouho+1; i++){
|
||||||
snprint(item, sizeof item, "c%02d", i);
|
snprint(item, sizeof item, "c%02d", i);
|
||||||
@@ -39,53 +32,57 @@ dictionary_candidates(struct ct *t)
|
|||||||
p += strlen(item);
|
p += strlen(item);
|
||||||
}
|
}
|
||||||
*p = '\0';
|
*p = '\0';
|
||||||
hmapset(&lang->dict, &key, many, strlen(many));
|
trieput(lang->dict, "key", 3, many, strlen(many));
|
||||||
req.key = key;
|
key = mkstr("key");
|
||||||
req.pre = mkstr("preedit-two");
|
n = dictlookup(lang->dict, &key, kouho, Maxkouho);
|
||||||
dictlookup(&req, &res);
|
if(CT_EQ_INT(t, Maxkouho, n)){
|
||||||
if(!CT_EQ_INT(t, Maxkouho, res.nkouho))
|
checkstr(t, "first capped candidate", "c00", &kouho[0]);
|
||||||
goto cleanup;
|
snprint(item, sizeof item, "c%02d", Maxkouho-1);
|
||||||
CT_EQ_INT(t, LangJP, res.lang);
|
checkstr(t, "last capped candidate", item, &kouho[Maxkouho-1]);
|
||||||
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
|
}
|
||||||
checkstr(t, "first capped candidate", "c00", &res.kouho[0]);
|
CT_EQ_INT(t, 3, dictlookup(lang->dict, &key, kouho, 3));
|
||||||
checkstr(t, "last capped candidate", "c31", &res.kouho[31]);
|
trieclose(lang->dict);
|
||||||
cleanup:
|
|
||||||
hmapfree(lang->dict);
|
|
||||||
lang->dict = saved;
|
lang->dict = saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
dictionary_misses_clear_result(struct ct *t)
|
dictionary_misses(struct ct *t)
|
||||||
{
|
{
|
||||||
static const struct {
|
Str kouho[Maxkouho], key;
|
||||||
char *name;
|
Trie *saved;
|
||||||
char *key;
|
|
||||||
char *pre;
|
|
||||||
} cases[] = {
|
|
||||||
{ "empty key", "", "empty-preedit" },
|
|
||||||
{ "missing key", "missing", "missing-preedit" },
|
|
||||||
};
|
|
||||||
Dictreq req;
|
|
||||||
Dictres res;
|
|
||||||
Hmap *saved;
|
|
||||||
Lang *lang;
|
Lang *lang;
|
||||||
int i;
|
|
||||||
|
|
||||||
lang = getlang(LangJP);
|
lang = getlang(LangJP);
|
||||||
saved = lang->dict;
|
saved = lang->dict;
|
||||||
lang->dict = hmapalloc(1);
|
lang->dict = trienew();
|
||||||
for(i = 0; i < nelem(cases); i++){
|
key = mkstr("");
|
||||||
memset(&res, 0xa5, sizeof res);
|
CT_EQ_INT(t, 0, dictlookup(lang->dict, &key, kouho, Maxkouho));
|
||||||
req.key = mkstr(cases[i].key);
|
key = mkstr("missing");
|
||||||
req.pre = mkstr(cases[i].pre);
|
CT_EQ_INT(t, 0, dictlookup(lang->dict, &key, kouho, Maxkouho));
|
||||||
req.lang = LangJP;
|
CT_EQ_INT(t, 0, dictlookup(getlang(LangKO)->dict, &key, kouho, Maxkouho));
|
||||||
dictlookup(&req, &res);
|
trieclose(lang->dict);
|
||||||
if(res.nkouho != 0)
|
|
||||||
CT_ERRORF(t, "%s: want 0 candidates, got %d",
|
|
||||||
cases[i].name, res.nkouho);
|
|
||||||
CT_EQ_INT(t, LangJP, res.lang);
|
|
||||||
checkstr(t, cases[i].name, cases[i].pre, &res.key);
|
|
||||||
}
|
|
||||||
hmapfree(lang->dict);
|
|
||||||
lang->dict = saved;
|
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);
|
||||||
|
}
|
||||||
|
|||||||
2142
tests/engine_test.c
2142
tests/engine_test.c
File diff suppressed because it is too large
Load Diff
257
tests/font_test.c
Normal file
257
tests/font_test.c
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
#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();
|
||||||
|
}
|
||||||
1015
tests/gtk_live_test.c
Normal file
1015
tests/gtk_live_test.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,78 +0,0 @@
|
|||||||
#include "test.h"
|
|
||||||
|
|
||||||
void
|
|
||||||
hmap_set_replace_and_grow(struct ct *t)
|
|
||||||
{
|
|
||||||
char keybuf[16], valbuf[16], source[] = "copied";
|
|
||||||
Hmap *h;
|
|
||||||
Hnode *n;
|
|
||||||
Str key;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
h = hmapalloc(1);
|
|
||||||
for(i = 0; i < 4; i++){
|
|
||||||
snprint(keybuf, sizeof keybuf, "key%d", i);
|
|
||||||
snprint(valbuf, sizeof valbuf, "value%d", i);
|
|
||||||
key = mkstr(keybuf);
|
|
||||||
hmapset(&h, &key, valbuf, strlen(valbuf));
|
|
||||||
}
|
|
||||||
for(i = 0; i < 4; i++){
|
|
||||||
snprint(keybuf, sizeof keybuf, "key%d", i);
|
|
||||||
snprint(valbuf, sizeof valbuf, "value%d", i);
|
|
||||||
key = mkstr(keybuf);
|
|
||||||
n = hmapget(h, &key);
|
|
||||||
if(n == nil || strcmp(n->val, valbuf) != 0)
|
|
||||||
CT_ERRORF(t, "%s: collision chain lost value", keybuf);
|
|
||||||
}
|
|
||||||
key = mkstr("key1");
|
|
||||||
hmapset(&h, &key, source, strlen(source));
|
|
||||||
source[0] = 'X';
|
|
||||||
n = hmapget(h, &key);
|
|
||||||
if(!CT_CHECK(t, n != nil))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_STR(t, "copied", n->val);
|
|
||||||
key = mkstr("key2");
|
|
||||||
n = hmapget(h, &key);
|
|
||||||
if(!CT_CHECK(t, n != nil))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_STR(t, "value2", n->val);
|
|
||||||
key = mkstr("key1");
|
|
||||||
hmapset(&h, &key, nil, 0);
|
|
||||||
n = hmapget(h, &key);
|
|
||||||
if(!CT_CHECK(t, n != nil))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_INT(t, 0, n->vlen);
|
|
||||||
CT_EQ_PTR(t, nil, n->val);
|
|
||||||
key = mkstr("missing");
|
|
||||||
CT_EQ_PTR(t, nil, hmapget(h, &key));
|
|
||||||
cleanup:
|
|
||||||
hmapfree(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
hmap_long_utf8_keys(struct ct *t)
|
|
||||||
{
|
|
||||||
Hmap *h;
|
|
||||||
Hnode *n;
|
|
||||||
Str a, b;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for(i = 0; i < Maxrunes-1; i++)
|
|
||||||
a.r[i] = b.r[i] = 0x1f600;
|
|
||||||
a.r[Maxrunes-1] = 0x1f601;
|
|
||||||
b.r[Maxrunes-1] = 0x1f602;
|
|
||||||
a.n = b.n = Maxrunes;
|
|
||||||
h = hmapalloc(1);
|
|
||||||
hmapset(&h, &a, "first", 5);
|
|
||||||
hmapset(&h, &b, "second", 6);
|
|
||||||
n = hmapget(h, &a);
|
|
||||||
if(!CT_CHECK(t, n != nil))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_STR(t, "first", n != nil ? n->val : nil);
|
|
||||||
n = hmapget(h, &b);
|
|
||||||
if(!CT_CHECK(t, n != nil))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_STR(t, "second", n != nil ? n->val : nil);
|
|
||||||
cleanup:
|
|
||||||
hmapfree(h);
|
|
||||||
}
|
|
||||||
386
tests/ibus_client_smoke.c
Normal file
386
tests/ibus_client_smoke.c
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
262
tests/ibus_live_test.c
Normal file
262
tests/ibus_live_test.c
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
439
tests/ibus_test.c
Normal file
439
tests/ibus_test.c
Normal file
@@ -0,0 +1,439 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
390
tests/ipc_live_test.c
Normal file
390
tests/ipc_live_test.c
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
432
tests/ipc_test.c
432
tests/ipc_test.c
@@ -1,5 +1,33 @@
|
|||||||
|
#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"
|
#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
|
void
|
||||||
ipc_masks_modifiers(struct ct *t)
|
ipc_masks_modifiers(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -12,4 +40,408 @@ ipc_masks_modifiers(struct ct *t)
|
|||||||
CT_EQ_INT(t, 1, want);
|
CT_EQ_INT(t, 1, want);
|
||||||
CT_EQ_UINT(t, Mctrl|Malt, mod);
|
CT_EQ_UINT(t, Mctrl|Malt, mod);
|
||||||
CT_EQ_UINT(t, 0x1f642, key);
|
CT_EQ_UINT(t, 0x1f642, key);
|
||||||
|
CT_CHECK(t, !ipcreqreset(buf));
|
||||||
|
|
||||||
|
ipcpackreset(buf, 1);
|
||||||
|
ipcunpackreq(buf, &want, &mod, &key);
|
||||||
|
CT_EQ_INT(t, 1, want);
|
||||||
|
CT_EQ_UINT(t, 0, mod);
|
||||||
|
CT_EQ_UINT(t, 0, key);
|
||||||
|
CT_CHECK(t, ipcreqreset(buf));
|
||||||
|
|
||||||
|
/* 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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
|
static Str
|
||||||
|
kotrans(char *keys)
|
||||||
|
{
|
||||||
|
Str out, raw;
|
||||||
|
|
||||||
|
raw = mkstr(keys);
|
||||||
|
transstr(getlang(LangKO), nil, &raw, &out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
korean_sequences(struct ct *t)
|
korean_sequences(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -13,40 +25,78 @@ korean_sequences(struct ct *t)
|
|||||||
{ "rr", "ㄱㄱ" },
|
{ "rr", "ㄱㄱ" },
|
||||||
{ "Rk", "까" },
|
{ "Rk", "까" },
|
||||||
{ "rk1", "가1" },
|
{ "rk1", "가1" },
|
||||||
|
{ "rt", "ㄳ" },
|
||||||
|
{ "rtk", "ㄱ사" },
|
||||||
|
{ "rtt", "ㄳㅅ" },
|
||||||
|
{ "kr", "가" },
|
||||||
|
{ "hkr", "과" },
|
||||||
|
{ "kk", "ㅏㅏ" },
|
||||||
};
|
};
|
||||||
Emit e;
|
|
||||||
Im state;
|
|
||||||
Str out;
|
Str out;
|
||||||
Rune key;
|
|
||||||
char *p;
|
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for(i = 0; i < nelem(cases); i++){
|
for(i = 0; i < nelem(cases); i++){
|
||||||
memset(&state, 0, sizeof state);
|
out = kotrans(cases[i].keys);
|
||||||
state.l = getlang(LangKO);
|
|
||||||
sclear(&out);
|
|
||||||
for(p = cases[i].keys; *p != '\0'; p++){
|
|
||||||
key = (uchar)*p;
|
|
||||||
e = transko(&state, key);
|
|
||||||
sappend(&out, &e.s);
|
|
||||||
state.pre = e.next;
|
|
||||||
if(!e.eat)
|
|
||||||
sputr(&out, key);
|
|
||||||
}
|
|
||||||
sappend(&out, &state.pre);
|
|
||||||
checkstr(t, cases[i].keys, cases[i].want, &out);
|
checkstr(t, cases[i].keys, cases[i].want, &out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
korean_compound_vowels(struct ct *t)
|
||||||
|
{
|
||||||
|
static const struct { char *keys, *want; } cases[] = {
|
||||||
|
{ "hk", "ㅘ" }, { "ho", "ㅙ" }, { "hl", "ㅚ" },
|
||||||
|
{ "nj", "ㅝ" }, { "np", "ㅞ" }, { "nl", "ㅟ" },
|
||||||
|
{ "ml", "ㅢ" },
|
||||||
|
};
|
||||||
|
Str out;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < nelem(cases); i++){
|
||||||
|
out = kotrans(cases[i].keys);
|
||||||
|
checkstr(t, cases[i].keys, cases[i].want, &out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
korean_compound_finals(struct ct *t)
|
||||||
|
{
|
||||||
|
static const struct { char *keys, *want, *split; } cases[] = {
|
||||||
|
{ "rkrt", "갃", "각사" }, { "rksw", "갅", "간자" },
|
||||||
|
{ "rksg", "갆", "간하" }, { "rkfr", "갉", "갈가" },
|
||||||
|
{ "rkfa", "갊", "갈마" }, { "rkfq", "갋", "갈바" },
|
||||||
|
{ "rkft", "갌", "갈사" }, { "rkfx", "갍", "갈타" },
|
||||||
|
{ "rkfv", "갎", "갈파" }, { "rkfg", "갏", "갈하" },
|
||||||
|
{ "rkqt", "값", "갑사" },
|
||||||
|
};
|
||||||
|
char keys[16];
|
||||||
|
Str out;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < nelem(cases); i++){
|
||||||
|
out = kotrans(cases[i].keys);
|
||||||
|
checkstr(t, cases[i].keys, cases[i].want, &out);
|
||||||
|
snprint(keys, sizeof keys, "%sk", cases[i].keys);
|
||||||
|
out = kotrans(keys);
|
||||||
|
checkstr(t, keys, cases[i].split, &out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
korean_backspace(struct ct *t)
|
korean_backspace(struct ct *t)
|
||||||
{
|
{
|
||||||
static const struct { char *before, *after; } cases[] = {
|
static const struct { char *before, *after; } cases[] = {
|
||||||
{ "ㄱ", "" },
|
{ "ㄱ", "" },
|
||||||
|
{ "ㄳ", "ㄱ" },
|
||||||
{ "가", "ㄱ" },
|
{ "가", "ㄱ" },
|
||||||
{ "과", "고" },
|
{ "과", "고" }, { "괘", "고" }, { "괴", "고" },
|
||||||
|
{ "궈", "구" }, { "궤", "구" }, { "귀", "구" },
|
||||||
|
{ "긔", "그" },
|
||||||
{ "각", "가" },
|
{ "각", "가" },
|
||||||
{ "갃", "각" },
|
{ "갃", "각" }, { "갅", "간" }, { "갆", "간" },
|
||||||
|
{ "갉", "갈" }, { "갊", "갈" }, { "갋", "갈" },
|
||||||
|
{ "갌", "갈" }, { "갍", "갈" }, { "갎", "갈" },
|
||||||
|
{ "갏", "갈" }, { "값", "갑" },
|
||||||
};
|
};
|
||||||
Im state;
|
Im state;
|
||||||
int i;
|
int i;
|
||||||
|
|||||||
774
tests/live.c
Normal file
774
tests/live.c
Normal file
@@ -0,0 +1,774 @@
|
|||||||
|
#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
Normal file
75
tests/live.h
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#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
Normal file
168
tests/livebus.c
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
17
tests/livebus.h
Normal file
17
tests/livebus.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#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*);
|
||||||
@@ -9,13 +9,14 @@ from pathlib import Path
|
|||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
MKEMOJI = ROOT / "map" / "mkemoji"
|
MKEMOJI = ROOT / "map" / "mkemoji"
|
||||||
|
TIMEOUT = 10
|
||||||
|
|
||||||
|
|
||||||
def generate(source=None):
|
def generate(*sources):
|
||||||
args = [sys.executable, "-B", str(MKEMOJI)]
|
args = [sys.executable, "-B", str(MKEMOJI)] + [str(s) for s in sources]
|
||||||
if source is not None:
|
return subprocess.run(
|
||||||
args.append(str(source))
|
args, capture_output=True, text=True, check=False, timeout=TIMEOUT
|
||||||
return subprocess.run(args, capture_output=True, text=True, check=False)
|
)
|
||||||
|
|
||||||
|
|
||||||
def table(output):
|
def table(output):
|
||||||
@@ -34,27 +35,31 @@ class MkemojiTest(unittest.TestCase):
|
|||||||
result = generate()
|
result = generate()
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
data = table(result.stdout)
|
data = table(result.stdout)
|
||||||
self.assertEqual(data["^"].split()[:9], list("¹²³⁴⁵⁶⁷⁸⁹"))
|
# The engine walks a prefix's children in rune order, so only the
|
||||||
self.assertEqual(data["_"].split()[:9], list("₁₂₃₄₅₆₇₈₉"))
|
# 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["<"].split()[2], "♥")
|
||||||
self.assertEqual(data["^1"], "¹")
|
self.assertEqual(data["^1"], "¹")
|
||||||
self.assertEqual(data["_2"], "₂")
|
self.assertEqual(data["_2"], "₂")
|
||||||
self.assertEqual(data["<3"], "♥")
|
self.assertEqual(data["<3"].split()[0], "♥")
|
||||||
|
self.assertIn("😀", data["smile"].split())
|
||||||
|
self.assertIn("😀", data["웃음"].split())
|
||||||
|
|
||||||
def test_fold_normalize_and_exact_first(self):
|
def test_fold_normalize_and_source_order(self):
|
||||||
source = self.source(
|
source = self.source(
|
||||||
"β\tALPHABET\n"
|
"β\tALPHABET\talpha\n"
|
||||||
"α\talpha\n"
|
"α\talpha\n"
|
||||||
"e\u0301\tE\u0301\n"
|
"é\tÉ\n"
|
||||||
"#\thash\n"
|
"#\thash\n"
|
||||||
)
|
)
|
||||||
first = generate(source)
|
first = generate(source)
|
||||||
second = generate(source)
|
second = generate(source, source)
|
||||||
self.assertEqual(first.returncode, 0, first.stderr)
|
self.assertEqual(first.returncode, 0, first.stderr)
|
||||||
self.assertEqual(first.stdout, second.stdout)
|
self.assertEqual(first.stdout, second.stdout)
|
||||||
data = table(first.stdout)
|
data = table(first.stdout)
|
||||||
self.assertEqual(data["alpha"].split(), ["α", "β"])
|
self.assertEqual(list(data), ["alphabet", "alpha", "é", "hash"])
|
||||||
self.assertEqual(data["al"].split(), ["β", "α"])
|
self.assertEqual(data["alpha"].split(), ["β", "α"])
|
||||||
self.assertEqual(data["é"], "é")
|
self.assertEqual(data["é"], "é")
|
||||||
self.assertEqual(data["hash"], "#")
|
self.assertEqual(data["hash"], "#")
|
||||||
|
|
||||||
@@ -66,6 +71,7 @@ class MkemojiTest(unittest.TestCase):
|
|||||||
"x\t a\n",
|
"x\t a\n",
|
||||||
"\0\ta\n",
|
"\0\ta\n",
|
||||||
"x\ta\0b\n",
|
"x\ta\0b\n",
|
||||||
|
"x\t;hidden\n",
|
||||||
"x" * 65 + "\ta\n",
|
"x" * 65 + "\ta\n",
|
||||||
"x\t" + "a" * 65 + "\n",
|
"x\t" + "a" * 65 + "\n",
|
||||||
]
|
]
|
||||||
|
|||||||
190
tests/mkhanja_test.py
Normal file
190
tests/mkhanja_test.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
#!/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()
|
||||||
323
tests/popup_test.c
Normal file
323
tests/popup_test.c
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
549
tests/server_test.c
Normal file
549
tests/server_test.c
Normal file
@@ -0,0 +1,549 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
64
tests/skk2ktrans_test.py
Executable file
64
tests/skk2ktrans_test.py
Executable file
@@ -0,0 +1,64 @@
|
|||||||
|
#!/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()
|
||||||
@@ -1,21 +1,52 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
str_init_utf8(struct ct *t)
|
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];
|
char full[Maxrunes+2];
|
||||||
char incomplete[] = { (char)0xea, (char)0xb0 };
|
|
||||||
Str s;
|
Str s;
|
||||||
|
int i;
|
||||||
|
|
||||||
s = mkstr("A한😀");
|
s = mkstr("A한😀");
|
||||||
CT_EQ_INT(t, 3, s.n);
|
CT_EQ_INT(t, 3, s.n);
|
||||||
checkstr(t, "round trip", "A한😀", &s);
|
checkstr(t, "round trip", "A한😀", &s);
|
||||||
sinit(&s, incomplete, sizeof incomplete);
|
for(i = 0; i < nelem(cases); i++){
|
||||||
CT_EQ_INT(t, 0, s.n);
|
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);
|
memset(full, 'a', sizeof full);
|
||||||
full[sizeof full-1] = '\0';
|
full[sizeof full-1] = '\0';
|
||||||
sinit(&s, full, strlen(full));
|
CT_CHECK(t, !sinit(&s, full, strlen(full)));
|
||||||
CT_EQ_INT(t, Maxrunes, s.n);
|
CT_EQ_INT(t, 0, s.n);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -45,7 +76,6 @@ str_utf8_capacity(struct ct *t)
|
|||||||
int n;
|
int n;
|
||||||
char *want;
|
char *want;
|
||||||
} cases[] = {
|
} cases[] = {
|
||||||
{ 0, 0, nil },
|
|
||||||
{ 1, 0, "" },
|
{ 1, 0, "" },
|
||||||
{ 2, 1, "a" },
|
{ 2, 1, "a" },
|
||||||
{ 3, 1, "a" },
|
{ 3, 1, "a" },
|
||||||
@@ -64,10 +94,6 @@ str_utf8_capacity(struct ct *t)
|
|||||||
if(n != cases[i].n)
|
if(n != cases[i].n)
|
||||||
CT_ERRORF(t, "size %d: want length %d, got %d",
|
CT_ERRORF(t, "size %d: want length %d, got %d",
|
||||||
cases[i].size, cases[i].n, n);
|
cases[i].size, cases[i].n, n);
|
||||||
if(cases[i].size == 0){
|
|
||||||
CT_EQ_INT(t, 'Z', buf[0]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(strcmp(cases[i].want, buf) != 0)
|
if(strcmp(cases[i].want, buf) != 0)
|
||||||
CT_ERRORF(t, "size %d: want \"%s\", got \"%s\"",
|
CT_ERRORF(t, "size %d: want \"%s\", got \"%s\"",
|
||||||
cases[i].size, cases[i].want, buf);
|
cases[i].size, cases[i].want, buf);
|
||||||
@@ -82,3 +108,17 @@ str_utf8_capacity(struct ct *t)
|
|||||||
CT_EQ_STR(t, "😀", buf);
|
CT_EQ_STR(t, "😀", buf);
|
||||||
CT_EQ_INT(t, 'Z', buf[5]);
|
CT_EQ_INT(t, 'Z', buf[5]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
str_invalid_and_full_appends(struct ct *t)
|
||||||
|
{
|
||||||
|
Str s;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
|||||||
118
tests/test.h
118
tests/test.h
@@ -1,28 +1,55 @@
|
|||||||
#ifndef STRANS_TEST_H
|
/* Included after dat.h and fn.h (or after the source under test). */
|
||||||
#define STRANS_TEST_H
|
#include "cutest/cutest.h"
|
||||||
|
|
||||||
#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;
|
||||||
|
};
|
||||||
|
|
||||||
#ifndef STRANS_TEST_ENGINE
|
enum
|
||||||
#include "../dat.h"
|
{
|
||||||
#include "../fn.h"
|
Pumpnone = -1,
|
||||||
#endif
|
Pumpall = -2,
|
||||||
|
};
|
||||||
extern Lang testvi;
|
|
||||||
|
|
||||||
Str mkstr(char*);
|
Str mkstr(char*);
|
||||||
int checkstr(struct ct*, char*, char*, Str*);
|
int checkstr(struct ct*, char*, char*, Str*);
|
||||||
Str shownpre(Im*);
|
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_init_utf8(struct ct*);
|
||||||
void str_edit_and_alias(struct ct*);
|
void str_edit_and_alias(struct ct*);
|
||||||
void str_utf8_capacity(struct ct*);
|
void str_utf8_capacity(struct ct*);
|
||||||
void hmap_set_replace_and_grow(struct ct*);
|
void str_invalid_and_full_appends(struct ct*);
|
||||||
void hmap_long_utf8_keys(struct ct*);
|
|
||||||
void trie_exact_prefix_and_duplicate(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 production_maps_load(struct ct*);
|
||||||
void transmap_states(struct ct*);
|
void transmap_states(struct ct*);
|
||||||
void korean_sequences(struct ct*);
|
void korean_sequences(struct ct*);
|
||||||
|
void korean_compound_vowels(struct ct*);
|
||||||
|
void korean_compound_finals(struct ct*);
|
||||||
void korean_backspace(struct ct*);
|
void korean_backspace(struct ct*);
|
||||||
void vietnamese_transitions(struct ct*);
|
void vietnamese_transitions(struct ct*);
|
||||||
void vietnamese_backspace(struct ct*);
|
void vietnamese_backspace(struct ct*);
|
||||||
@@ -30,18 +57,77 @@ void vietnamese_state_lifetime(struct ct*);
|
|||||||
void engine_clears_candidates(struct ct*);
|
void engine_clears_candidates(struct ct*);
|
||||||
void engine_backspace_clears_candidates(struct ct*);
|
void engine_backspace_clears_candidates(struct ct*);
|
||||||
void engine_selects_visible_candidate(struct ct*);
|
void engine_selects_visible_candidate(struct ct*);
|
||||||
|
void engine_candidate_shortcut_modifiers(struct ct*);
|
||||||
|
void engine_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_commit_contract(struct ct*);
|
||||||
void engine_language_switch_state(struct ct*);
|
void engine_language_switch_state(struct ct*);
|
||||||
void engine_telex_history_bound(struct ct*);
|
void engine_telex_history_bound(struct ct*);
|
||||||
|
void engine_korean_modifiers_and_backspace(struct ct*);
|
||||||
|
void engine_direct_language_modes(struct ct*);
|
||||||
|
void engine_japanese_readings(struct ct*);
|
||||||
|
void engine_japanese_candidates(struct ct*);
|
||||||
|
void engine_japanese_backspace_and_boundaries(struct ct*);
|
||||||
|
void engine_katakana_sequences(struct ct*);
|
||||||
void engine_emoji_single_candidate(struct ct*);
|
void engine_emoji_single_candidate(struct ct*);
|
||||||
void engine_emoji_queries(struct ct*);
|
void engine_emoji_queries(struct ct*);
|
||||||
|
void engine_emoji_japanese_and_multirune(struct ct*);
|
||||||
void engine_emoji_digit_aliases(struct ct*);
|
void engine_emoji_digit_aliases(struct ct*);
|
||||||
void engine_emoji_navigation(struct ct*);
|
void engine_emoji_navigation(struct ct*);
|
||||||
|
void engine_search_candidate_keys(struct ct*);
|
||||||
void engine_emoji_preedit_languages(struct ct*);
|
void engine_emoji_preedit_languages(struct ct*);
|
||||||
void engine_emoji_start_and_unknown(struct ct*);
|
void engine_emoji_start_and_unknown(struct ct*);
|
||||||
void engine_emoji_dictionary_identity(struct ct*);
|
void engine_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_candidates(struct ct*);
|
||||||
void dictionary_misses_clear_result(struct ct*);
|
void dictionary_misses(struct ct*);
|
||||||
|
void dictionary_prefix(struct ct*);
|
||||||
void ipc_masks_modifiers(struct ct*);
|
void ipc_masks_modifiers(struct ct*);
|
||||||
|
void ipc_control_and_caret_frames(struct ct*);
|
||||||
#endif
|
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,3 +1,5 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
Str
|
Str
|
||||||
@@ -21,12 +23,82 @@ checkstr(struct ct *t, char *where, char *want, Str *got)
|
|||||||
where, want, buf);
|
where, want, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
Str
|
void
|
||||||
shownpre(Im *state)
|
checkenginepreedit(struct ct *t, char *want)
|
||||||
{
|
{
|
||||||
Str shown;
|
Str preedit;
|
||||||
|
|
||||||
if(state->l->map != nil && mapget(state->l->map, &state->pre, &shown))
|
testenginepreedit(&preedit);
|
||||||
return shown;
|
checkstr(t, "engine preedit", want, &preedit);
|
||||||
return state->pre;
|
}
|
||||||
|
|
||||||
|
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,36 +1,71 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
trie_exact_prefix_and_duplicate(struct ct *t)
|
trie_exact_prefix_and_duplicate(struct ct *t)
|
||||||
{
|
{
|
||||||
char *v, *sentinel;
|
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;
|
Trie *trie;
|
||||||
int n;
|
Str key;
|
||||||
|
int i, match, n;
|
||||||
|
|
||||||
trie = trieopen("data/trie.map");
|
trie = trieopen("data/trie.map");
|
||||||
v = trieget(trie, "a", 1, &n);
|
for(i = 0; i < nelem(cases); i++){
|
||||||
if(!CT_CHECK(t, v != nil))
|
key = mkstr(cases[i].key);
|
||||||
goto cleanup;
|
match = trielookup(trie, &key, &v, &n);
|
||||||
CT_EQ_INT(t, 5, n);
|
if(match != cases[i].match){
|
||||||
CT_EQ_MEM(t, "alpha", v, n);
|
CT_ERRORF(t, "case %d: want match %d, got %d",
|
||||||
sentinel = "unchanged";
|
i, cases[i].match, match);
|
||||||
v = sentinel;
|
continue;
|
||||||
n = 77;
|
}
|
||||||
CT_CHECK(t, trielookup(trie, "dupli", 5, &v, &n));
|
if(cases[i].want == nil){
|
||||||
CT_EQ_PTR(t, sentinel, v);
|
CT_EQ_PTR(t, nil, v);
|
||||||
CT_EQ_INT(t, 77, n);
|
CT_EQ_INT(t, 0, n);
|
||||||
v = trieget(trie, "duplicate", 9, &n);
|
}else{
|
||||||
if(!CT_CHECK(t, v != nil))
|
CT_EQ_INT(t, strlen(cases[i].want), n);
|
||||||
goto cleanup;
|
CT_EQ_MEM(t, cases[i].want, v, n);
|
||||||
CT_EQ_INT(t, 6, n);
|
}
|
||||||
CT_EQ_MEM(t, "second", v, n);
|
}
|
||||||
v = trieget(trie, "tabs", 4, &n);
|
trieclose(trie);
|
||||||
if(!CT_CHECK(t, v != nil))
|
}
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_INT(t, 7, n);
|
void
|
||||||
CT_EQ_MEM(t, "one\ttwo", v, n);
|
trie_put_and_unloaded(struct ct *t)
|
||||||
CT_EQ_PTR(t, nil, trieget(trie, "missing", 7, &n));
|
{
|
||||||
cleanup:
|
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);
|
trieclose(trie);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,20 +101,23 @@ transmap_states(struct ct *t)
|
|||||||
int eat;
|
int eat;
|
||||||
char *emit;
|
char *emit;
|
||||||
char *next;
|
char *next;
|
||||||
char *mapped;
|
|
||||||
} cases[] = {
|
} cases[] = {
|
||||||
{ "", 'k', 1, "", "k", "" },
|
{ "", 'k', 1, "", "k" },
|
||||||
{ "k", 'a', 1, "", "ka", "か" },
|
{ "k", 'a', 1, "", "ka" },
|
||||||
{ "ka", 's', 1, "か", "s", "" },
|
{ "ka", 's', 1, "か", "s" },
|
||||||
{ "ka", 'q', 0, "か", "", "" },
|
{ "ka", 'q', 0, "か", "" },
|
||||||
{ "k", 'q', 0, "k", "", "" },
|
{ "k", 'q', 0, "k", "" },
|
||||||
};
|
};
|
||||||
Emit e;
|
Emit e;
|
||||||
Im state;
|
Im state;
|
||||||
|
Trie *fixture, *saved;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
fixture = trieopen("data/hira.map");
|
||||||
memset(&state, 0, sizeof state);
|
memset(&state, 0, sizeof state);
|
||||||
state.l = getlang(LangJP);
|
state.l = getlang(LangJP);
|
||||||
|
saved = state.l->map;
|
||||||
|
state.l->map = fixture;
|
||||||
for(i = 0; i < nelem(cases); i++){
|
for(i = 0; i < nelem(cases); i++){
|
||||||
state.pre = mkstr(cases[i].pre);
|
state.pre = mkstr(cases[i].pre);
|
||||||
e = transmap(&state, cases[i].key);
|
e = transmap(&state, cases[i].key);
|
||||||
@@ -88,6 +126,7 @@ transmap_states(struct ct *t)
|
|||||||
i, cases[i].eat, e.eat);
|
i, cases[i].eat, e.eat);
|
||||||
checkstr(t, "emit", cases[i].emit, &e.s);
|
checkstr(t, "emit", cases[i].emit, &e.s);
|
||||||
checkstr(t, "next", cases[i].next, &e.next);
|
checkstr(t, "next", cases[i].next, &e.next);
|
||||||
checkstr(t, "mapped", cases[i].mapped, &e.dict);
|
|
||||||
}
|
}
|
||||||
|
trieclose(fixture);
|
||||||
|
state.l->map = saved;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
#define CT_IMPLEMENTATION
|
#define CT_IMPLEMENTATION
|
||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
Channel *drawc;
|
Channel *drawc;
|
||||||
Channel *keyc;
|
Channel *keyc;
|
||||||
Channel *dictreqc;
|
|
||||||
Channel *dictresc;
|
|
||||||
Lang testvi;
|
|
||||||
|
|
||||||
void
|
void
|
||||||
die(char *fmt, ...)
|
die(char *fmt, ...)
|
||||||
@@ -27,10 +26,9 @@ emalloc(ulong n)
|
|||||||
{
|
{
|
||||||
void *p;
|
void *p;
|
||||||
|
|
||||||
p = malloc(n);
|
p = mallocz(n, 1);
|
||||||
if(p == nil)
|
if(p == nil)
|
||||||
die("out of memory");
|
die("out of memory");
|
||||||
memset(p, 0, n);
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,30 +44,33 @@ erealloc(void *p, ulong n)
|
|||||||
static void
|
static void
|
||||||
testmapinit(void)
|
testmapinit(void)
|
||||||
{
|
{
|
||||||
Lang *jp;
|
Lang *jp, *kata, *vi;
|
||||||
|
|
||||||
jp = getlang(LangJP);
|
jp = getlang(LangJP);
|
||||||
if(jp == nil)
|
kata = getlang(LangJPK);
|
||||||
|
vi = getlang(LangVI);
|
||||||
|
if(jp == nil || kata == nil || vi == nil)
|
||||||
die("test language is not registered");
|
die("test language is not registered");
|
||||||
jp->map = trieopen("data/hira.map");
|
jp->map = trieopen("../map/hira.map");
|
||||||
memset(&testvi, 0, sizeof testvi);
|
kata->map = trieopen("../map/kata.map");
|
||||||
testvi.lang = LangVI;
|
vi->map = trieopen("../map/telex.map");
|
||||||
testvi.mapname = "telex";
|
|
||||||
testvi.trans = transvi;
|
|
||||||
testvi.back = backvi;
|
|
||||||
testvi.map = trieopen("../map/telex.map");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef STRESS
|
||||||
static const struct ct_test tests[] = {
|
static const struct ct_test tests[] = {
|
||||||
{ "str/init-utf8", str_init_utf8 },
|
{ "str/init-utf8", str_init_utf8 },
|
||||||
{ "str/edit-and-alias", str_edit_and_alias },
|
{ "str/edit-and-alias", str_edit_and_alias },
|
||||||
{ "str/utf8-capacity", str_utf8_capacity },
|
{ "str/utf8-capacity", str_utf8_capacity },
|
||||||
{ "hmap/set-replace-grow", hmap_set_replace_and_grow },
|
{ "str/invalid-full-appends", str_invalid_and_full_appends },
|
||||||
{ "hmap/long-utf8-keys", hmap_long_utf8_keys },
|
|
||||||
{ "trie/exact-prefix-duplicate", trie_exact_prefix_and_duplicate },
|
{ "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 },
|
{ "map/production-lifecycle", production_maps_load },
|
||||||
{ "transmap/states", transmap_states },
|
{ "transmap/states", transmap_states },
|
||||||
{ "hangul/sequences", korean_sequences },
|
{ "hangul/sequences", korean_sequences },
|
||||||
|
{ "hangul/compound-vowels", korean_compound_vowels },
|
||||||
|
{ "hangul/compound-finals", korean_compound_finals },
|
||||||
{ "hangul/backspace", korean_backspace },
|
{ "hangul/backspace", korean_backspace },
|
||||||
{ "telex/transitions", vietnamese_transitions },
|
{ "telex/transitions", vietnamese_transitions },
|
||||||
{ "telex/backspace", vietnamese_backspace },
|
{ "telex/backspace", vietnamese_backspace },
|
||||||
@@ -77,20 +78,85 @@ static const struct ct_test tests[] = {
|
|||||||
{ "engine/clears-candidates", engine_clears_candidates },
|
{ "engine/clears-candidates", engine_clears_candidates },
|
||||||
{ "engine/backspace-clears-candidates", engine_backspace_clears_candidates },
|
{ "engine/backspace-clears-candidates", engine_backspace_clears_candidates },
|
||||||
{ "engine/selects-visible-candidate", engine_selects_visible_candidate },
|
{ "engine/selects-visible-candidate", engine_selects_visible_candidate },
|
||||||
|
{ "engine/candidate-shortcut-modifiers", engine_candidate_shortcut_modifiers },
|
||||||
|
{ "engine/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/commit-contract", engine_commit_contract },
|
||||||
{ "engine/language-switch-state", engine_language_switch_state },
|
{ "engine/language-switch-state", engine_language_switch_state },
|
||||||
{ "engine/telex-history-bound", engine_telex_history_bound },
|
{ "engine/telex-history-bound", engine_telex_history_bound },
|
||||||
|
{ "engine/korean-modifiers-backspace", engine_korean_modifiers_and_backspace },
|
||||||
|
{ "engine/direct-language-modes", engine_direct_language_modes },
|
||||||
|
{ "engine/japanese-readings", engine_japanese_readings },
|
||||||
|
{ "engine/japanese-candidates", engine_japanese_candidates },
|
||||||
|
{ "engine/japanese-backspace-boundaries", engine_japanese_backspace_and_boundaries },
|
||||||
|
{ "engine/katakana-sequences", engine_katakana_sequences },
|
||||||
{ "engine/emoji-single-candidate", engine_emoji_single_candidate },
|
{ "engine/emoji-single-candidate", engine_emoji_single_candidate },
|
||||||
{ "engine/emoji-queries", engine_emoji_queries },
|
{ "engine/emoji-queries", engine_emoji_queries },
|
||||||
|
{ "engine/emoji-japanese-multirune", engine_emoji_japanese_and_multirune },
|
||||||
{ "engine/emoji-digit-aliases", engine_emoji_digit_aliases },
|
{ "engine/emoji-digit-aliases", engine_emoji_digit_aliases },
|
||||||
{ "engine/emoji-navigation", engine_emoji_navigation },
|
{ "engine/emoji-navigation", engine_emoji_navigation },
|
||||||
|
{ "engine/search-candidate-keys", engine_search_candidate_keys },
|
||||||
{ "engine/emoji-preedit-languages", engine_emoji_preedit_languages },
|
{ "engine/emoji-preedit-languages", engine_emoji_preedit_languages },
|
||||||
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
|
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
|
||||||
{ "engine/emoji-dictionary-identity", engine_emoji_dictionary_identity },
|
{ "engine/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/candidates", dictionary_candidates },
|
||||||
{ "dict/misses-clear-result", dictionary_misses_clear_result },
|
{ "dict/misses", dictionary_misses },
|
||||||
|
{ "dict/prefix", dictionary_prefix },
|
||||||
{ "ipc/masks-modifiers", ipc_masks_modifiers },
|
{ "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
|
void
|
||||||
threadmain(int argc, char **argv)
|
threadmain(int argc, char **argv)
|
||||||
@@ -99,19 +165,13 @@ threadmain(int argc, char **argv)
|
|||||||
|
|
||||||
drawc = chancreate(sizeof(Drawcmd), 4);
|
drawc = chancreate(sizeof(Drawcmd), 4);
|
||||||
keyc = chancreate(sizeof(Keyreq), 0);
|
keyc = chancreate(sizeof(Keyreq), 0);
|
||||||
dictreqc = chancreate(sizeof(Dictreq), 64);
|
|
||||||
dictresc = chancreate(sizeof(Dictres), 0);
|
|
||||||
testmapinit();
|
testmapinit();
|
||||||
status = CT_RUN_ARGS(tests, argc, argv);
|
status = CT_RUN_ARGS(tests, argc, argv);
|
||||||
for(i = 0; i < nlang; i++){
|
for(i = 0; i < nlang; i++){
|
||||||
trieclose(langs[i].map);
|
trieclose(langs[i].map);
|
||||||
langs[i].map = nil;
|
langs[i].map = nil;
|
||||||
}
|
}
|
||||||
trieclose(testvi.map);
|
|
||||||
testvi.map = nil;
|
|
||||||
chanfree(drawc);
|
chanfree(drawc);
|
||||||
chanfree(keyc);
|
chanfree(keyc);
|
||||||
chanfree(dictreqc);
|
|
||||||
chanfree(dictresc);
|
|
||||||
threadexitsall(status == 0 ? nil : "tests failed");
|
threadexitsall(status == 0 ? nil : "tests failed");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,14 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
typevi(struct ct *t, char *keys, char *want)
|
typevi(struct ct *t, char *keys, char *want)
|
||||||
{
|
{
|
||||||
Emit e;
|
Str out, raw;
|
||||||
Im state;
|
|
||||||
Str out, shown;
|
|
||||||
char *p;
|
|
||||||
|
|
||||||
memset(&state, 0, sizeof state);
|
raw = mkstr(keys);
|
||||||
state.l = &testvi;
|
transstr(getlang(LangVI), nil, &raw, &out);
|
||||||
sclear(&out);
|
|
||||||
for(p = keys; *p != '\0'; p++){
|
|
||||||
e = transvi(&state, (uchar)*p);
|
|
||||||
sappend(&out, &e.s);
|
|
||||||
state.pre = e.next;
|
|
||||||
if(!e.eat)
|
|
||||||
sputr(&out, (uchar)*p);
|
|
||||||
}
|
|
||||||
shown = shownpre(&state);
|
|
||||||
sappend(&out, &shown);
|
|
||||||
checkstr(t, keys, want, &out);
|
checkstr(t, keys, want, &out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +35,11 @@ vietnamese_transitions(struct ct *t)
|
|||||||
{ "gias", "giá" },
|
{ "gias", "giá" },
|
||||||
{ "Gias", "Giá" },
|
{ "Gias", "Giá" },
|
||||||
{ "gif", "gì" },
|
{ "gif", "gì" },
|
||||||
|
{ "cuar", "của" },
|
||||||
|
{ "vowis", "với" },
|
||||||
|
{ "cuwar", "cửa" },
|
||||||
|
{ "nguyeenx", "nguyễn" },
|
||||||
|
{ "dduwowngf", "đường" },
|
||||||
{ "\\s", "s" },
|
{ "\\s", "s" },
|
||||||
{ "a\\s", "as" },
|
{ "a\\s", "as" },
|
||||||
};
|
};
|
||||||
@@ -73,12 +67,12 @@ vietnamese_backspace(struct ct *t)
|
|||||||
|
|
||||||
for(i = 0; i < nelem(cases); i++){
|
for(i = 0; i < nelem(cases); i++){
|
||||||
memset(&state, 0, sizeof state);
|
memset(&state, 0, sizeof state);
|
||||||
state.l = &testvi;
|
state.l = getlang(LangVI);
|
||||||
state.pre = mkstr(cases[i].pre);
|
state.pre = mkstr(cases[i].pre);
|
||||||
state.raw = mkstr(cases[i].raw);
|
state.raw = mkstr(cases[i].raw);
|
||||||
backvi(&state);
|
backvi(&state);
|
||||||
checkstr(t, cases[i].raw, cases[i].rawafter, &state.raw);
|
checkstr(t, cases[i].raw, cases[i].rawafter, &state.raw);
|
||||||
shown = shownpre(&state);
|
impre(&state, &shown);
|
||||||
checkstr(t, cases[i].raw, cases[i].after, &shown);
|
checkstr(t, cases[i].raw, cases[i].after, &shown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
165
tests/wl_adapter_test.c
Normal file
165
tests/wl_adapter_test.c
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#include <wayland-client.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
#include "vkv1.h"
|
||||||
|
|
||||||
|
/* The virtual keyboard is the only wire call the tested code makes. */
|
||||||
|
static int nkeysent;
|
||||||
|
static void wirekey(struct zwp_virtual_keyboard_v1*, uint32_t, uint32_t,
|
||||||
|
uint32_t);
|
||||||
|
|
||||||
|
#define zwp_virtual_keyboard_v1_key wirekey
|
||||||
|
|
||||||
|
#include "wl.c"
|
||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
static void
|
||||||
|
wirekey(struct zwp_virtual_keyboard_v1 *vkb, uint32_t time, uint32_t code,
|
||||||
|
uint32_t state)
|
||||||
|
{
|
||||||
|
USED(vkb);
|
||||||
|
USED(time);
|
||||||
|
USED(code);
|
||||||
|
USED(state);
|
||||||
|
nkeysent++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only the modifiers are read from it, so nothing else need be here. */
|
||||||
|
static char keymaptext[] =
|
||||||
|
"xkb_keymap {\n"
|
||||||
|
"xkb_keycodes { <AD01> = 24; };\n"
|
||||||
|
"xkb_types { type \"ONE_LEVEL\" {\n"
|
||||||
|
" modifiers = none; map[none] = 1; level_name[1] = \"Any\"; }; };\n"
|
||||||
|
"xkb_compatibility { };\n"
|
||||||
|
"xkb_symbols { key <AD01> { [ q ] }; };\n"
|
||||||
|
"};\n";
|
||||||
|
|
||||||
|
void
|
||||||
|
wl_modifier_mask(struct ct *t)
|
||||||
|
{
|
||||||
|
struct xkb_context *ctx;
|
||||||
|
struct xkb_keymap *km;
|
||||||
|
xkb_mod_mask_t caps, ctrl, shift;
|
||||||
|
|
||||||
|
ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||||
|
km = xkb_keymap_new_from_string(ctx, keymaptext,
|
||||||
|
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||||
|
CT_CHECK(t, km != nil);
|
||||||
|
if(km != nil){
|
||||||
|
kstate = xkb_state_new(km);
|
||||||
|
shift = (xkb_mod_mask_t)1 <<
|
||||||
|
xkb_keymap_mod_get_index(km, XKB_MOD_NAME_SHIFT);
|
||||||
|
ctrl = (xkb_mod_mask_t)1 <<
|
||||||
|
xkb_keymap_mod_get_index(km, XKB_MOD_NAME_CTRL);
|
||||||
|
caps = (xkb_mod_mask_t)1 <<
|
||||||
|
xkb_keymap_mod_get_index(km, XKB_MOD_NAME_CAPS);
|
||||||
|
xkb_state_update_mask(kstate, shift, 0, 0, 0, 0, 0);
|
||||||
|
CT_EQ_UINT(t, Mshift, modmask());
|
||||||
|
xkb_state_update_mask(kstate, shift|ctrl, 0, 0, 0, 0, 0);
|
||||||
|
CT_EQ_UINT(t, Mshift|Mctrl, modmask());
|
||||||
|
/* Caps Lock is not Shift; a Korean key must not shift under it. */
|
||||||
|
xkb_state_update_mask(kstate, 0, 0, caps, 0, 0, 0);
|
||||||
|
CT_EQ_UINT(t, 0, modmask());
|
||||||
|
xkb_state_unref(kstate);
|
||||||
|
kstate = nil;
|
||||||
|
xkb_keymap_unref(km);
|
||||||
|
}
|
||||||
|
xkb_context_unref(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wl_forwarded_keys(struct ct *t)
|
||||||
|
{
|
||||||
|
haskeymap = 1;
|
||||||
|
memset(sent, 0, sizeof sent);
|
||||||
|
nkeysent = 0;
|
||||||
|
|
||||||
|
forward(30, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
CT_CHECK(t, issent(30));
|
||||||
|
forward(30, WL_KEYBOARD_KEY_STATE_RELEASED);
|
||||||
|
CT_CHECK(t, !issent(30));
|
||||||
|
CT_EQ_INT(t, 2, nkeysent);
|
||||||
|
|
||||||
|
/* A code the bitmap cannot hold is not passed on at all. */
|
||||||
|
nkeysent = 0;
|
||||||
|
forward(Maxcode, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
CT_EQ_INT(t, 0, nkeysent);
|
||||||
|
|
||||||
|
/* Whatever is still down is released, once each and no more. */
|
||||||
|
forward(1, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
forward(200, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
nkeysent = 0;
|
||||||
|
releasekeys();
|
||||||
|
CT_EQ_INT(t, 2, nkeysent);
|
||||||
|
CT_CHECK(t, !issent(1));
|
||||||
|
CT_CHECK(t, !issent(200));
|
||||||
|
releasekeys();
|
||||||
|
CT_EQ_INT(t, 2, nkeysent);
|
||||||
|
|
||||||
|
/* Before a keymap the virtual keyboard must not be touched. */
|
||||||
|
haskeymap = 0;
|
||||||
|
nkeysent = 0;
|
||||||
|
forward(30, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
CT_EQ_INT(t, 0, nkeysent);
|
||||||
|
CT_CHECK(t, !issent(30));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Arming a repeat needs the engine, which is not here; ending one does
|
||||||
|
* not, and a repeat that outlives its key is the worst this can do.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* The text before the cursor is what a reading may reach into, and a
|
||||||
|
* take-back is measured in the bytes those runes hold.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
wl_surrounding_text(struct ct *t)
|
||||||
|
{
|
||||||
|
sclear(&pendingsurround);
|
||||||
|
imsurrounding(nil, nil, "가나다라", 9, 9);
|
||||||
|
checkstr(t, "the text before the cursor", "가나다", &pendingsurround);
|
||||||
|
surround = pendingsurround;
|
||||||
|
CT_EQ_INT(t, 3, backbytes(1));
|
||||||
|
CT_EQ_INT(t, 6, backbytes(2));
|
||||||
|
CT_EQ_INT(t, 9, backbytes(9));
|
||||||
|
CT_EQ_INT(t, 0, backbytes(0));
|
||||||
|
/* A cursor past the end of the buffer is the whole of it. */
|
||||||
|
imsurrounding(nil, nil, "ab", 99, 99);
|
||||||
|
checkstr(t, "a cursor past the end", "ab", &pendingsurround);
|
||||||
|
/* An activation starts with none, whatever the last one had. */
|
||||||
|
imactivate(nil, nil);
|
||||||
|
CT_EQ_INT(t, 0, pendingsurround.n);
|
||||||
|
sclear(&surround);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wl_repeat_ends(struct ct *t)
|
||||||
|
{
|
||||||
|
haskeymap = 1;
|
||||||
|
memset(sent, 0, sizeof sent);
|
||||||
|
|
||||||
|
/* Not a rate or a delay any seat would pick: the body was empty
|
||||||
|
* once, and a stored constant would pass for the real thing. */
|
||||||
|
grabrepeat(nil, nil, 33, 410);
|
||||||
|
CT_EQ_INT(t, 33, reprate);
|
||||||
|
CT_EQ_INT(t, 410, repdelay);
|
||||||
|
|
||||||
|
/* The key's own release ends it; another key's release does not. */
|
||||||
|
repcode = 14;
|
||||||
|
repdue = 1;
|
||||||
|
grabkey(nil, nil, 0, 0, 15, WL_KEYBOARD_KEY_STATE_RELEASED);
|
||||||
|
CT_CHECK(t, repdue != 0);
|
||||||
|
grabkey(nil, nil, 0, 0, 14, WL_KEYBOARD_KEY_STATE_RELEASED);
|
||||||
|
CT_EQ_INT(t, 0, repdue);
|
||||||
|
|
||||||
|
/* So does a seat that has stopped repeating anything. */
|
||||||
|
repdue = 1;
|
||||||
|
grabrepeat(nil, nil, 0, 410);
|
||||||
|
CT_EQ_INT(t, 0, repdue);
|
||||||
|
|
||||||
|
/* A due repeat with no text input to serve is dropped, not sent. */
|
||||||
|
grabrepeat(nil, nil, 33, 410);
|
||||||
|
active = 0;
|
||||||
|
repdue = 1;
|
||||||
|
repeat();
|
||||||
|
CT_EQ_INT(t, 0, repdue);
|
||||||
|
}
|
||||||
1160
tests/xim_adapter_test.c
Normal file
1160
tests/xim_adapter_test.c
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user