Compare commits
94 Commits
279b9230a4
...
ea363ca792
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -2,13 +2,16 @@
|
||||
/strans
|
||||
/tests/*.o
|
||||
/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
|
||||
/xim/*.o
|
||||
/xim/strans-xim
|
||||
/xim/xim_test
|
||||
/gtk/im-strans.so
|
||||
/bench/bench
|
||||
|
||||
19
Dockerfile
19
Dockerfile
@@ -1,23 +1,40 @@
|
||||
FROM archlinux:base@sha256:b0deabeb3d283da2c7f7dbf0eea051b7b2cd0554e0b737cc457fd21683bdcdd1
|
||||
|
||||
RUN pacman -Syu --noconfirm --needed \
|
||||
cairo \
|
||||
fontconfig \
|
||||
diffutils \
|
||||
freetype2 \
|
||||
gcc \
|
||||
gtk3 \
|
||||
ibus \
|
||||
harfbuzz \
|
||||
libx11 \
|
||||
libxcb \
|
||||
libxkbcommon \
|
||||
make \
|
||||
noto-fonts-emoji \
|
||||
pango \
|
||||
pkgconf \
|
||||
plan9port \
|
||||
python \
|
||||
ttf-dejavu \
|
||||
ttf-jigmo \
|
||||
valgrind \
|
||||
which \
|
||||
xorg-server-xvfb \
|
||||
xcb-imdkit \
|
||||
dbus \
|
||||
&& pacman -Scc --noconfirm
|
||||
|
||||
RUN export DEBUGINFOD_URLS=https://debuginfod.archlinux.org \
|
||||
&& buildid="$(readelf -n /usr/lib/ld-linux-x86-64.so.2 | sed -n 's/.*Build ID: //p')" \
|
||||
&& test -n "$buildid" \
|
||||
&& debugdir="/usr/lib/debug/.build-id/$(printf '%.2s' "$buildid")" \
|
||||
&& mkdir -p "$debugdir" \
|
||||
&& install -m 0644 "$(debuginfod-find debuginfo /usr/lib/ld-linux-x86-64.so.2)" \
|
||||
"$debugdir/${buildid#??}.debug" \
|
||||
&& rm -rf /root/.cache/debuginfod_client
|
||||
|
||||
RUN printf "egrep='grep -E'\n" > /usr/lib/plan9/config
|
||||
RUN dbus-uuidgen --ensure=/etc/machine-id
|
||||
|
||||
|
||||
@@ -6,7 +6,4 @@
|
||||
- `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.
|
||||
- License texts and provenance for the historically bundled fonts are retained
|
||||
in `font/`; no font binary remains in the source tree.
|
||||
|
||||
No repository-wide license has been declared for original strans source.
|
||||
|
||||
76
Makefile
76
Makefile
@@ -1,37 +1,50 @@
|
||||
CC = 9c
|
||||
LD = 9l
|
||||
PKG_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
|
||||
PKG_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
|
||||
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
|
||||
FT_LIBS = $(shell pkg-config --libs freetype2)
|
||||
CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS)
|
||||
PKG_CONFIG ?= pkg-config
|
||||
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-xkb xkbcommon-x11)
|
||||
XIM_LIBS = $(shell $(PKG_CONFIG) --libs xcb-imdkit 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-randr)
|
||||
POPUP_LIBS = $(shell $(PKG_CONFIG) --libs xcb-randr)
|
||||
PROJECT_CPPFLAGS =
|
||||
PROJECT_LDLIBS = -lthread -lbio -lxcb $(IBUS_LIBS) $(TEXT_LIBS) \
|
||||
$(XIM_LIBS) $(POPUP_LIBS)
|
||||
PROG = strans
|
||||
DOCKER_IMAGE = strans-build
|
||||
DOCKER_RUN = docker run --rm --user "$$(id -u):$$(id -g)" \
|
||||
-v "$(CURDIR):/src" $(DOCKER_IMAGE)
|
||||
|
||||
SRCS = dict.c font.c hash.c ibus.c ipc.c ko.c main.c popup_layout.c \
|
||||
SRCS = 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
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
XIMSRCS = xim/xim.c xim/keymap.c
|
||||
XIMOBJS = $(XIMSRCS:.c=.o)
|
||||
OBJS = $(SRCS:.c=.o) $(XIMOBJS)
|
||||
|
||||
all: $(PROG) xim gtk
|
||||
all: $(PROG) gtk
|
||||
|
||||
$(PROG): $(OBJS)
|
||||
$(LD) -o $@ $(OBJS) -lthread -lbio -lxcb $(PKG_LIBS) $(FT_LIBS)
|
||||
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(PROJECT_LDLIBS) $(LDLIBS)
|
||||
|
||||
$(OBJS): dat.h fn.h ipc.h
|
||||
font.o: CFLAGS += $(FT_CFLAGS)
|
||||
ibus.o: PROJECT_CPPFLAGS += $(IBUS_CFLAGS)
|
||||
font.o: PROJECT_CPPFLAGS += $(TEXT_CFLAGS)
|
||||
win.o: PROJECT_CPPFLAGS += $(POPUP_CFLAGS)
|
||||
$(XIMOBJS): PROJECT_CPPFLAGS += -I. $(XIM_CFLAGS)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CPPFLAGS) $(PROJECT_CPPFLAGS) $(WARN_CFLAGS) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(PROG)
|
||||
$(MAKE) -C tests clean
|
||||
$(MAKE) -C xim/ clean
|
||||
$(MAKE) -C gtk/ clean
|
||||
$(MAKE) -C bench/ clean
|
||||
|
||||
xim:
|
||||
$(MAKE) -C xim/
|
||||
|
||||
gtk:
|
||||
$(MAKE) -C gtk/
|
||||
|
||||
@@ -42,22 +55,38 @@ docker-image: Dockerfile
|
||||
docker build -t $(DOCKER_IMAGE) - < Dockerfile
|
||||
|
||||
docker-build:
|
||||
$(DOCKER_RUN) make -B all
|
||||
$(DOCKER_RUN) sh -c 'make clean && make all'
|
||||
|
||||
docker-check:
|
||||
$(DOCKER_RUN) make -B check TESTARGS="$(TESTARGS)"
|
||||
$(DOCKER_RUN) sh -c 'make clean && \
|
||||
make check check-live check-stress UNITARGS="$(UNITARGS)"'
|
||||
|
||||
docker-check-live:
|
||||
$(DOCKER_RUN) sh -c 'make clean && make check-live'
|
||||
|
||||
docker-check-stress:
|
||||
$(DOCKER_RUN) sh -c 'make clean && make check-stress'
|
||||
|
||||
docker-bench:
|
||||
$(DOCKER_RUN) make -B 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 test
|
||||
$(MAKE) -C xim check
|
||||
check: verify-map
|
||||
$(MAKE) -C tests check UNITARGS="$(UNITARGS)"
|
||||
|
||||
test: $(PROG)
|
||||
$(MAKE) -C tests check TESTARGS="$(TESTARGS)"
|
||||
check-live: $(PROG) gtk
|
||||
$(MAKE) -C tests check-live
|
||||
|
||||
check-stress: $(PROG)
|
||||
$(MAKE) -C tests check-stress
|
||||
|
||||
test: check
|
||||
|
||||
verify-map:
|
||||
python3 map/mktelex.py | cmp - map/telex.map
|
||||
@@ -68,5 +97,6 @@ verify-map:
|
||||
python3 -B tests/mkhanja_test.py
|
||||
python3 -B tests/skk2ktrans_test.py
|
||||
|
||||
.PHONY: all check test verify-map clean xim gtk bench docker docker-image \
|
||||
docker-build docker-check docker-bench
|
||||
.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
|
||||
|
||||
137
README.md
137
README.md
@@ -15,53 +15,116 @@ Vietnamese Telex is also available as a compatibility mode.
|
||||
| `Ctrl+V` | Vietnamese Telex |
|
||||
| `Ctrl+E` | Emoji and symbol search |
|
||||
| `Ctrl+H` | One-shot Hanja search |
|
||||
| `Ctrl+P` | Toggle popup preedit |
|
||||
|
||||
Use `Up`/`Down` to move through candidates, `Enter` or `1`-`9` to select,
|
||||
`Tab` to cycle temporary search results, and `Esc` to cancel. `0` commits the
|
||||
current reading without conversion.
|
||||
Use `Up`/`Down` to move through candidates and `PageUp`/`PageDown` to move by
|
||||
nine without wrapping. An unmodified `1`-`9` selects that row on the current
|
||||
page. `Enter` and `Tab` commit a candidate you have moved to; otherwise they
|
||||
commit the reading, as `0` always does. Japanese consumes the confirming key;
|
||||
Korean and Vietnamese pass it on to the application. In a temporary Emoji or
|
||||
Hanja search, `Enter` commits the highlighted result and `Tab`/`Shift-Tab`
|
||||
wrap through the results. Leaving the field or clicking elsewhere commits
|
||||
what is pending; `Esc` cancels it.
|
||||
|
||||
Hiragana mode composes a complete reading before offering Kanji candidates.
|
||||
Katakana mode does not perform Kanji conversion. Emoji and Hanja searches
|
||||
return to the previous mode after use. Hanja lookup currently supports one
|
||||
modern Hangul syllable at a time.
|
||||
return to the previous mode after use; switching language during a search
|
||||
commits the shown query. `Ctrl+H` takes the syllable being composed as its
|
||||
query; the Hanja dictionary lists one modern Hangul syllable at a time.
|
||||
|
||||
## Preedit and candidates
|
||||
|
||||
| Frontend | Preedit | Candidates |
|
||||
| --- | --- | --- |
|
||||
| GTK 3 and IBus | inline in the client | strans popup |
|
||||
| XIM PreeditCallbacks | inline through XIM callbacks | strans popup |
|
||||
| XIM PreeditPosition | strans popup | strans popup |
|
||||
| XIM PreeditNothing | strans popup | strans popup |
|
||||
|
||||
XIM text travels as `COMPOUND_TEXT`, which carries any UTF-8. A client that
|
||||
sends `XNSpotLocation` gets the popup at that spot whatever its preedit
|
||||
style; otherwise the popup sits under the focus window, or under the client
|
||||
window when no focus window is set.
|
||||
|
||||
The popup is an X11 window sized by `GDK_SCALE` on HiDPI displays, XIM is
|
||||
X11-only, and GTK popup placement converts the GTK caret to X11 root
|
||||
coordinates. There is no native Wayland popup surface or Wayland caret
|
||||
positioning. GTK and IBus clients can still use inline preedit where their
|
||||
display environment supports it.
|
||||
|
||||
## Build and test
|
||||
|
||||
Docker is the supported build environment:
|
||||
Initialize the bundled test library, then use the pinned Docker environment
|
||||
for a reproducible build:
|
||||
|
||||
```sh
|
||||
git submodule update --init
|
||||
make docker-image
|
||||
make docker-build
|
||||
make docker-check
|
||||
```
|
||||
|
||||
Build output:
|
||||
|
||||
```text
|
||||
strans daemon and IBus frontend
|
||||
xim/strans-xim XIM frontend
|
||||
gtk/im-strans.so GTK 3 frontend
|
||||
strans daemon with IBus and XIM frontends
|
||||
gtk/im-strans.so GTK 3 frontend module
|
||||
```
|
||||
|
||||
Use `make docker-check TESTARGS=hangul` to run selected C tests. Native
|
||||
`make all`, `make check`, and `make bench` are also available when the
|
||||
dependencies listed in [`Dockerfile`](Dockerfile) are installed.
|
||||
Tests have three tiers:
|
||||
|
||||
```sh
|
||||
make check # generated-map validation and focused unit tests
|
||||
make check-live # one IBus, GTK, XIM, and IPC daemon smoke
|
||||
make check-stress # randomized, capacity, collision, failure, and restart tests
|
||||
```
|
||||
|
||||
`UNITARGS` filters only the focused C unit suite, for example
|
||||
`make check UNITARGS=hangul`. It does not select or skip live and stress
|
||||
tests. `make docker-check` rebuilds and runs all three tiers in the
|
||||
container, and `make docker-valgrind` runs the unit suite under Valgrind
|
||||
there; run `make docker-image` first after changing `Dockerfile`.
|
||||
|
||||
Native Linux source builds require a C toolchain, Make, pkg-config, Python 3,
|
||||
and cmp/diff, plus development files for:
|
||||
|
||||
- Plan 9 port (`9c`, `9l`, `libthread`, and `libbio`);
|
||||
- D-Bus development files;
|
||||
- XCB, XCB RandR, XCB XKB, xcb-imdkit, xkbcommon, and xkbcommon-x11;
|
||||
- Pango, PangoCairo, Cairo, and Fontconfig;
|
||||
- GTK 3, plus libibus and Xlib for the live clients;
|
||||
- Xvfb for the X11 live tests;
|
||||
- a working `C.UTF-8` locale and fonts covering Latin, CJK, and emoji.
|
||||
|
||||
The package names used by the canonical Arch Linux image are listed in
|
||||
[`Dockerfile`](Dockerfile).
|
||||
|
||||
## Run
|
||||
|
||||
The popup needs one to four scalable font files. No fonts are bundled.
|
||||
`run.sh` uses installed DejaVu, Jigmo, or Noto CJK fonts when available, or
|
||||
accepts explicit files in fallback order:
|
||||
The popup uses the normal system fonts found by Pango and Fontconfig; no font
|
||||
file argument or system-wide strans installation is required. Run it directly
|
||||
from the build tree:
|
||||
|
||||
```sh
|
||||
./run.sh
|
||||
./run.sh /path/to/primary.ttf /path/to/fallback.ttc
|
||||
# equivalently
|
||||
./strans map
|
||||
```
|
||||
|
||||
The script starts the daemon and, when `DISPLAY` is set, the XIM frontend. It
|
||||
restarts any existing `strans` processes owned by the current user.
|
||||
`run.sh` stops every same-user process whose exact name is `strans`, starts one
|
||||
build-tree daemon in the background, and returns to the shell. It requires
|
||||
`pkill` from procps. Startup and frontend failures remain visible on the
|
||||
caller's standard error. A service supervisor should instead run
|
||||
`./strans map` directly in the foreground.
|
||||
|
||||
The build does not copy or install the daemon or its data. `./strans DIR`
|
||||
opens `hira.map`, `kata.map`, `telex.map`, `kanji.dict`, `emoji.dict`, and
|
||||
`hanja.dict` from `DIR` at runtime; `run.sh` passes the tracked `map/`
|
||||
directory. Only the GTK subdirectory has install and uninstall targets.
|
||||
|
||||
Pango shapes the complete string and selects glyph fallback from the system
|
||||
font set.
|
||||
|
||||
The popup renderer uses Pango/PangoCairo and Cairo. When `DISPLAY` is set,
|
||||
`strans` starts its XIM worker in the same process.
|
||||
|
||||
Configure clients as needed:
|
||||
|
||||
@@ -71,14 +134,31 @@ export XMODIFIERS=@im=strans
|
||||
|
||||
# GTK 3 module
|
||||
doas make -C gtk install
|
||||
export GTK_IM_MODULE=strans
|
||||
|
||||
# IBus client example
|
||||
GLFW_IM_MODULE=ibus kitty
|
||||
```
|
||||
|
||||
Building `gtk/im-strans.so` requires GTK development files. Installing an
|
||||
already-built, up-to-date module from this build tree does not: the install
|
||||
target first uses an explicit `GTK_MODULE_DIR`, then GTK's pkg-config
|
||||
variables, and finally the module directory reported by the GTK runtime's
|
||||
`gtk-query-immodules-3.0`. Packagers and cross builds should set
|
||||
`GTK_MODULE_DIR` explicitly. A prebuilt module must still match the target
|
||||
architecture and library ABI.
|
||||
|
||||
`DESTDIR` staging is supported and deliberately skips the host module-cache
|
||||
update. A native install needs the GTK runtime query utility so it can refresh
|
||||
that cache. Remove a native installation with:
|
||||
|
||||
```sh
|
||||
doas make -C gtk uninstall
|
||||
```
|
||||
|
||||
strans provides its own IBus endpoint; `ibus-daemon` and fcitx are not
|
||||
required. Without `DISPLAY`, the daemon and IBus frontend still work, but the
|
||||
popup is disabled.
|
||||
required. Without `DISPLAY`, the daemon and IBus frontend still work, but XIM
|
||||
is not started and the popup is disabled.
|
||||
|
||||
## Dictionary data
|
||||
|
||||
@@ -93,9 +173,14 @@ make verify-map
|
||||
See [`map/README`](map/README) for Hanja import and Japanese dictionary
|
||||
generation.
|
||||
|
||||
## Benchmark
|
||||
|
||||
`make bench` builds `bench/bench`. With the daemon stopped, `./bench.sh` starts
|
||||
one build-tree instance, warms it up, and records the workload with Linux
|
||||
`perf`. It requires `perf` and permission to profile the daemon, and writes
|
||||
`bench/perf.data`.
|
||||
|
||||
## Licensing
|
||||
|
||||
Data and third-party notices are in
|
||||
[`docs/PROVENANCE.md`](docs/PROVENANCE.md) and
|
||||
[`font/PROVENANCE`](font/PROVENANCE). The repository does not currently
|
||||
declare a license for the strans source as a whole.
|
||||
Third-party license notices are in [`LICENSES`](LICENSES). The repository does
|
||||
not currently declare a license for the strans source as a whole.
|
||||
|
||||
66
bench.sh
66
bench.sh
@@ -4,18 +4,8 @@ set -eu
|
||||
|
||||
cd "$(dirname "$0")" || exit 1
|
||||
|
||||
case ${XDG_RUNTIME_DIR-} in
|
||||
/) ipc=/strans.sock ;;
|
||||
/*/) ipc=${XDG_RUNTIME_DIR}strans.sock ;;
|
||||
/*) ipc=${XDG_RUNTIME_DIR}/strans.sock ;;
|
||||
*) ipc=/tmp/strans.$(id -u) ;;
|
||||
esac
|
||||
if test -n "${XDG_CONFIG_HOME-}"; then
|
||||
busdir=$XDG_CONFIG_HOME/ibus/bus
|
||||
elif test "${HOME+x}" = x; then
|
||||
busdir=$HOME/.config/ibus/bus
|
||||
else
|
||||
echo "bench.sh: HOME or XDG_CONFIG_HOME is required for IBus discovery" >&2
|
||||
if test "$#" -ne 0; then
|
||||
echo "usage: bench.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -29,70 +19,36 @@ cleanup()
|
||||
if test -n "$perf_pid"; then
|
||||
kill -INT "$perf_pid" 2>/dev/null || :
|
||||
wait "$perf_pid" 2>/dev/null || :
|
||||
perf_pid=
|
||||
fi
|
||||
if test -n "$strans_pid"; then
|
||||
kill "$strans_pid" 2>/dev/null || :
|
||||
wait "$strans_pid" 2>/dev/null || :
|
||||
strans_pid=
|
||||
fi
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
ibusready()
|
||||
{
|
||||
for address in "$busdir"/*; do
|
||||
test -f "$address" || continue
|
||||
foundpid=
|
||||
foundaddress=
|
||||
while IFS= read -r line; do
|
||||
case $line in
|
||||
IBUS_DAEMON_PID="$strans_pid") foundpid=1 ;;
|
||||
IBUS_ADDRESS=unix:abstract=strans-"$strans_pid",*) foundaddress=1 ;;
|
||||
esac
|
||||
done <"$address"
|
||||
if test -n "$foundpid" && test -n "$foundaddress"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
trap cleanup 0
|
||||
trap 'exit 129' 1
|
||||
trap 'exit 130' 2
|
||||
trap 'exit 143' 15
|
||||
|
||||
./strans map "$@" &
|
||||
./strans map &
|
||||
strans_pid=$!
|
||||
|
||||
# The daemon is ready once the benchmark client can complete a round.
|
||||
attempt=0
|
||||
while test "$attempt" -lt 10; do
|
||||
if test -S "$ipc" && ibusready; then
|
||||
break
|
||||
fi
|
||||
until ./bench/bench bench/bench.keys 1 >/dev/null 2>&1; do
|
||||
if ! kill -0 "$strans_pid" 2>/dev/null; then
|
||||
if wait "$strans_pid"; then
|
||||
status=0
|
||||
else
|
||||
status=$?
|
||||
fi
|
||||
strans_pid=
|
||||
if test "$status" -eq 0; then
|
||||
status=1
|
||||
fi
|
||||
echo "bench.sh: daemon exited before publishing IPC and IBus endpoints" >&2
|
||||
exit "$status"
|
||||
echo "bench.sh: daemon exited before accepting IPC clients" >&2
|
||||
exit 1
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
if test "$attempt" -lt 10; then
|
||||
sleep 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
|
||||
if test "$attempt" -eq 10; then
|
||||
echo "bench.sh: daemon did not publish IPC and IBus endpoints within 10 seconds" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# warm up the renderer
|
||||
./bench/bench bench/bench.keys 100
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
CC = cc
|
||||
CFLAGS = -Wall -O2
|
||||
CFLAGS ?= -O2
|
||||
|
||||
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:
|
||||
rm -f bench
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include "../ipc.h"
|
||||
#include "ipc.h"
|
||||
|
||||
typedef struct Key Key;
|
||||
struct Key {
|
||||
@@ -128,10 +128,10 @@ sendkey(int key, int mod)
|
||||
static int
|
||||
readresp(void)
|
||||
{
|
||||
char buf[Ipcfieldmax+1];
|
||||
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
||||
Ipcresp resp;
|
||||
|
||||
return ipcreadresp(fd, 0, buf, sizeof buf, NULL, 0, &resp);
|
||||
return ipcreadresp(fd, 0, commit, preedit, &resp);
|
||||
}
|
||||
|
||||
static double
|
||||
|
||||
120
dat.h
120
dat.h
@@ -7,6 +7,7 @@
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||
|
||||
/* A language id is the control code of the Ctrl key that selects it. */
|
||||
enum
|
||||
{
|
||||
LangEN = 0x14,
|
||||
@@ -17,8 +18,6 @@ enum
|
||||
LangEMOJI = 0x05,
|
||||
LangVI = 0x16,
|
||||
|
||||
Fontsz = 32,
|
||||
Maxfonts = 4,
|
||||
Maxrunes = 64,
|
||||
Maxutf = Maxrunes * UTFmax + 1,
|
||||
};
|
||||
@@ -26,16 +25,26 @@ enum
|
||||
enum
|
||||
{
|
||||
Maxclients = 64,
|
||||
Maxkouho = 32,
|
||||
Maxkouho = 128,
|
||||
Maxdisp = 9,
|
||||
Imgw = (Maxrunes + 3) * Fontsz,
|
||||
Imgh = (Maxdisp + 1) * Fontsz,
|
||||
|
||||
Colfg = 0x000000,
|
||||
Colbg = 0xffffff,
|
||||
Colsel = 0xcccccc,
|
||||
Colsep = 0xd0d0d0,
|
||||
Colsel = 0x333333,
|
||||
Colselfg = 0xffffff,
|
||||
};
|
||||
|
||||
/* 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 PopupNumw Fontsz
|
||||
#define PopupTextw (12*Fontsz)
|
||||
#define PopupBasew (2*PopupPad + PopupNumw + PopupTextw)
|
||||
#define Imgh (2*PopupPad + (Maxdisp + 2)*Fontsz + PopupSep)
|
||||
|
||||
typedef struct Str Str;
|
||||
struct Str
|
||||
{
|
||||
@@ -43,53 +52,43 @@ struct Str
|
||||
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;
|
||||
struct Emit
|
||||
{
|
||||
int eat;
|
||||
Str s;
|
||||
Str next;
|
||||
Str dict;
|
||||
};
|
||||
|
||||
typedef struct Hnode Hnode;
|
||||
struct Hnode
|
||||
{
|
||||
int filled;
|
||||
int next;
|
||||
char *key;
|
||||
int klen;
|
||||
char *val;
|
||||
int vlen;
|
||||
Str raw;
|
||||
};
|
||||
|
||||
typedef struct Tnode Tnode;
|
||||
struct Tnode
|
||||
{
|
||||
char *val;
|
||||
int vlen;
|
||||
int child;
|
||||
int sibling;
|
||||
char c;
|
||||
char *val;
|
||||
int vlen;
|
||||
};
|
||||
|
||||
typedef struct Trie Trie;
|
||||
struct Trie
|
||||
{
|
||||
int root;
|
||||
Tnode *nodes;
|
||||
int n;
|
||||
int cap;
|
||||
};
|
||||
|
||||
typedef struct Hmap Hmap;
|
||||
struct Hmap
|
||||
enum
|
||||
{
|
||||
int nbs;
|
||||
int nsz;
|
||||
int len;
|
||||
int cap;
|
||||
uchar *nodes;
|
||||
TrieMiss,
|
||||
TriePrefix,
|
||||
TrieExact,
|
||||
};
|
||||
|
||||
typedef struct Lang Lang;
|
||||
@@ -101,9 +100,8 @@ struct Lang
|
||||
char *dictname;
|
||||
Emit (*trans)(Im*, Rune);
|
||||
void (*back)(Im*);
|
||||
void (*dictq)(Im*);
|
||||
Trie *map;
|
||||
Hmap *dict;
|
||||
Trie *dict;
|
||||
};
|
||||
|
||||
struct Im
|
||||
@@ -119,6 +117,15 @@ struct Im
|
||||
|
||||
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;
|
||||
@@ -133,21 +140,52 @@ struct Drawcmd
|
||||
Str kouho[Maxdisp];
|
||||
int nkouho;
|
||||
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 Keyres Keyres;
|
||||
enum
|
||||
{
|
||||
Cclientpreedit = 1<<0,
|
||||
};
|
||||
enum
|
||||
{
|
||||
Keypress,
|
||||
Keyreset,
|
||||
Keyrelease,
|
||||
Keycaret,
|
||||
Keycap,
|
||||
};
|
||||
struct Keyres
|
||||
{
|
||||
int eaten;
|
||||
int eaten; /* Keycap: caller is active and capability was applied. */
|
||||
Str commit;
|
||||
Str preedit;
|
||||
};
|
||||
@@ -155,6 +193,7 @@ struct Keyres
|
||||
struct Keyreq
|
||||
{
|
||||
void *owner; /* stable until the context's release is acknowledged */
|
||||
int cap;
|
||||
int op;
|
||||
u32int ks;
|
||||
u32int mod;
|
||||
@@ -162,26 +201,7 @@ struct Keyreq
|
||||
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 int nlang;
|
||||
extern Channel *drawc;
|
||||
extern Channel *keyc;
|
||||
extern Channel *dictreqc;
|
||||
extern Channel *dictresc;
|
||||
|
||||
141
dict.c
141
dict.c
@@ -1,122 +1,59 @@
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
|
||||
void
|
||||
dictlookup(Dictreq *req, Dictres *res)
|
||||
/*
|
||||
* Fills out[] with up to max candidates for key: the space-separated words
|
||||
* of the entry, minus the key itself except in the emoji dictionary, where
|
||||
* a query may name its own answer.
|
||||
*/
|
||||
int
|
||||
dictlookup(Lang *l, Str *key, Str *out, int max)
|
||||
{
|
||||
Lang *l;
|
||||
Hmap *dict;
|
||||
Hnode *n;
|
||||
char *p, *e, *sp;
|
||||
Str tmp;
|
||||
int n, vlen;
|
||||
|
||||
res->key = req->pre;
|
||||
res->nkouho = 0;
|
||||
res->lang = req->lang;
|
||||
if(req->key.n == 0)
|
||||
return;
|
||||
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){
|
||||
if(l == nil || key->n == 0 ||
|
||||
trielookup(l->dict, key, &p, &vlen) != TrieExact)
|
||||
return 0;
|
||||
n = 0;
|
||||
e = p + vlen;
|
||||
while(n < max && p < e){
|
||||
while(p < e && *p == ' ')
|
||||
p++;
|
||||
if(p == e)
|
||||
break;
|
||||
sp = p;
|
||||
while(p < e && *p != ' ')
|
||||
p++;
|
||||
sinit(&tmp, sp, p - sp);
|
||||
if(req->lang == LangEMOJI || scmp(&tmp, &req->key) != 0)
|
||||
res->kouho[res->nkouho++] = tmp;
|
||||
if(p < e)
|
||||
p++;
|
||||
if(sinit(&tmp, sp, p - sp) && tmp.n > 0 &&
|
||||
(l->lang == LangEMOJI || scmp(&tmp, key) != 0))
|
||||
out[n++] = tmp;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
dictthread(void*)
|
||||
{
|
||||
Dictreq req;
|
||||
static Dictres res;
|
||||
|
||||
threadsetname("dict");
|
||||
for(;;){
|
||||
if(chanrecv(dictreqc, &req) < 0)
|
||||
break;
|
||||
while(channbrecv(dictreqc, &req) > 0)
|
||||
;
|
||||
dictlookup(&req, &res);
|
||||
chansend(dictresc, &res);
|
||||
}
|
||||
}
|
||||
|
||||
static Hmap*
|
||||
dictopen(char *path)
|
||||
{
|
||||
Hmap *h;
|
||||
Biobuf *b;
|
||||
Str key;
|
||||
char *line, *tab, *p, *e;
|
||||
int len, lineno;
|
||||
|
||||
b = Bopen(path, OREAD);
|
||||
if(b == nil)
|
||||
die("can't open: %s", path);
|
||||
h = hmapalloc(4096);
|
||||
lineno = 0;
|
||||
while((line = Brdstr(b, '\n', 1)) != nil){
|
||||
lineno++;
|
||||
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 || tab >= line + len - 1 ||
|
||||
strchr(tab+1, '\t') != nil)
|
||||
die("malformed dictionary: %s:%d", path, lineno);
|
||||
*tab = '\0';
|
||||
if(utflen(line) > Maxrunes)
|
||||
die("dictionary key too long: %s:%d", path, lineno);
|
||||
for(p = tab+1; p < line+len; p = e+1){
|
||||
e = memchr(p, ' ', line+len-p);
|
||||
if(e == nil)
|
||||
e = line+len;
|
||||
if(utfnlen(p, e-p) > Maxrunes)
|
||||
die("dictionary candidate too long: %s:%d", path, lineno);
|
||||
if(e == line+len)
|
||||
break;
|
||||
}
|
||||
sinit(&key, line, tab - line);
|
||||
hmapset(&h, &key, tab+1, len - (tab - line) - 1);
|
||||
free(line);
|
||||
}
|
||||
Bterm(b);
|
||||
return h;
|
||||
}
|
||||
|
||||
void
|
||||
dictinit(char *dir)
|
||||
static Trie*
|
||||
langopen(char *dir, char *name, char *ext)
|
||||
{
|
||||
char *path;
|
||||
int i;
|
||||
Trie *t;
|
||||
|
||||
for(i = 0; i < nlang; i++){
|
||||
if(langs[i].dictname == nil)
|
||||
continue;
|
||||
path = smprint("%s/%s.dict", dir, langs[i].dictname);
|
||||
if(path == nil)
|
||||
die("out of memory");
|
||||
langs[i].dict = dictopen(path);
|
||||
free(path);
|
||||
path = smprint("%s/%s.%s", dir, name, ext);
|
||||
if(path == nil)
|
||||
die("out of memory");
|
||||
t = trieopen(path);
|
||||
free(path);
|
||||
return t;
|
||||
}
|
||||
|
||||
void
|
||||
langinit(char *dir)
|
||||
{
|
||||
Lang *l;
|
||||
|
||||
for(l = langs; l < langs + nlang; l++){
|
||||
if(l->mapname != nil)
|
||||
l->map = langopen(dir, l->mapname, "map");
|
||||
if(l->dictname != nil)
|
||||
l->dict = langopen(dir, l->dictname, "dict");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
# Data and third-party provenance
|
||||
|
||||
This file records what can be established from repository contents and
|
||||
history. It does not assign a license to original strans source code.
|
||||
|
||||
## Input maps and dictionaries
|
||||
|
||||
`map/hira.map`, `map/kata.map`, and the original map data entered the history
|
||||
in commit `cc157c0b3c33506d1cbf5bd251ddc6a9ca909081`, whose subject says the
|
||||
maps came from 9front. That commit does not identify a 9front revision or
|
||||
carry a separate data license. `map/telex.map` is generated by
|
||||
`map/mktelex.py`; Vietnamese behavior is retained for compatibility.
|
||||
|
||||
`map/emoji.dict` is generated deterministically from the repository-maintained
|
||||
`map/emoji.src` by `map/mkemoji`. No external dataset is required.
|
||||
|
||||
`map/hanja.src` is a single-character subset of libhangul's
|
||||
`data/hanja/hanja.txt` from release tag `libhangul-0.2.0`. Its immutable
|
||||
identifiers are:
|
||||
|
||||
- annotated tag object: `20afc38922e3595ee3ed5b186f2ea05afe663763`
|
||||
- peeled commit: `41c702f5d3581325b646ef6249f1f641b0427ae0`
|
||||
- input blob: `199cfd70c4b306257ac2e018714a1285f7cf0ed3`
|
||||
- input SHA-256: `dd44dcc856cf542b1022d0f39c2e9b9f8805fdcc5923be80f04849ed97ce0996`
|
||||
|
||||
`map/libhangul2hanja` keeps only a one-syllable modern Hangul reading paired
|
||||
with one BMP Hanja rune. It writes one
|
||||
`Hanja<TAB>Hangul syllable` pair per row in upstream order; word entries are
|
||||
not part of this data. `map/mkhanja` groups those rows by reading for the
|
||||
runtime dictionary without changing candidate order, retaining the first 32
|
||||
candidates supported by the engine. Exact import and regeneration commands
|
||||
are in `map/README`.
|
||||
|
||||
The upstream data header is BSD 3-Clause licensed, copyright 2005, 2006 Choe
|
||||
Hwanjin. Its complete license text is in
|
||||
`LICENSES/BSD-3-Clause-libhangul-hanja.txt`.
|
||||
|
||||
`map/kanji.dict` identifies itself as SKK Medium dictionary version 8.1,
|
||||
May 24, 1995, rearranged for Plan 9 ktrans by Kenji Okamoto on February 17,
|
||||
2000. Its header names its copyright holders and grants GPL version 2 or
|
||||
later. The complete GPL version 2 text is in
|
||||
`LICENSES/GPL-2.0-or-later.txt`; the dictionary header supplies the
|
||||
"or later" option. `map/README` records the exact repository history,
|
||||
repairs made to this copy, and the reproducible procedure for importing a
|
||||
new SKK revision.
|
||||
|
||||
## Fonts
|
||||
|
||||
No font binaries are bundled. The daemon receives one to four font file paths
|
||||
on its command line and tries them in that order; it does not discover fonts by
|
||||
family name or directory contents. `font/PROVENANCE` retains the exact
|
||||
checksums, source revisions, and license records for the three Noto faces that
|
||||
were formerly stored in the repository.
|
||||
|
||||
The Docker renderer tests obtain their fonts from signed Arch packages rather
|
||||
than the source checkout. On August 13, 2026 the tested package contract was:
|
||||
|
||||
- `ttf-dejavu` `2.37+18+g9b5d1b2f-8`, providing
|
||||
`/usr/share/fonts/TTF/DejaVuSans.ttf` (SHA-256
|
||||
`6038a160b491e121c1f12c7bccb4a9c8730296e3adc1086a059404ed84b7451c`);
|
||||
- `ttf-jigmo` `20250912-1`, providing
|
||||
`/usr/share/fonts/TTF/Jigmo.ttf` (SHA-256
|
||||
`c8f295b9bd8f9f117a76b3a454aaaa1bb5b4babc18e254978c6deb88464e40cf`)
|
||||
and `/usr/share/fonts/TTF/Jigmo2.ttf` (SHA-256
|
||||
`5da3582efe77e22073b86b3b86b556d7111148a76b957cfb53318a91da2efff0`).
|
||||
|
||||
Pacman validates the package checksums and repository signatures. DejaVu Sans
|
||||
supplies ASCII and the monochrome U+1F600 outline in the tests; Jigmo supplies
|
||||
Hangul and BMP CJK, and Jigmo2 supplies U+2000B. Their installed license files
|
||||
are `/usr/share/licenses/ttf-dejavu/LICENSE` and
|
||||
`/usr/share/licenses/ttf-jigmo/LICENSE.txt`; the packages identify their terms
|
||||
as the DejaVu custom license and CC0 1.0, respectively. These packages are
|
||||
test fixtures, not names or paths hard-coded into the daemon.
|
||||
|
||||
## Git submodules
|
||||
|
||||
`cutest` is pinned by the repository as the unit-test framework. XIM uses
|
||||
the system xcb-imdkit and xcb-util libraries at build time; those system
|
||||
libraries are not bundled by this repository.
|
||||
34
fn.h
34
fn.h
@@ -1,6 +1,6 @@
|
||||
void die(char*, ...);
|
||||
|
||||
void sinit(Str*, char*, int);
|
||||
int sinit(Str*, char*, int);
|
||||
void sclear(Str*);
|
||||
void sputr(Str*, Rune);
|
||||
void spopr(Str*);
|
||||
@@ -9,28 +9,28 @@ int scmp(Str*, Str*);
|
||||
int stoutf(Str*, char*, int);
|
||||
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*);
|
||||
|
||||
Trie* trienew(void);
|
||||
void trieput(Trie*, char*, int, char*, int);
|
||||
Trie* trieopen(char*);
|
||||
void trieclose(Trie*);
|
||||
char* trieget(Trie*, char*, int, int*);
|
||||
int trielookup(Trie*, char*, int, char**, int*);
|
||||
int trielookup(Trie*, Str*, char**, int*);
|
||||
|
||||
Lang* getlang(int);
|
||||
void mapinit(char*);
|
||||
void dictinit(char*);
|
||||
void langinit(char*);
|
||||
|
||||
void dictthread(void*);
|
||||
void dictlookup(Dictreq*, Dictres*);
|
||||
int dictlookup(Lang*, Str*, Str*, int);
|
||||
|
||||
void drawthread(void*);
|
||||
int popupcells(Rune*, int);
|
||||
void popupposition(Caret*, int, int, int, int, int, int, int*, int*);
|
||||
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 impre(Im*, Str*);
|
||||
void transstr(Lang*, Str*, Str*);
|
||||
Emit transmap(Im*, Rune);
|
||||
Emit transko(Im*, Rune);
|
||||
Emit transvi(Im*, Rune);
|
||||
@@ -40,9 +40,13 @@ void backvi(Im*);
|
||||
void srvinit(void);
|
||||
void srvthread(void*);
|
||||
void ibusthread(void*);
|
||||
void ximthread(void*);
|
||||
int keymeaningful(u32int);
|
||||
|
||||
void* emalloc(ulong);
|
||||
void* erealloc(void*, ulong);
|
||||
|
||||
int fontinit(char**, int);
|
||||
void putfont(u32int*, int, int, int, int, Rune);
|
||||
int textinit(void);
|
||||
void textclose(void);
|
||||
int textwidth(Str*);
|
||||
void textdraw(u32int*, int, int, int, int, int, u32int, Str*);
|
||||
|
||||
314
font.c
314
font.c
@@ -1,168 +1,168 @@
|
||||
#include "dat.h"
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include <fontconfig/fontconfig.h>
|
||||
#include <pango/pangocairo.h>
|
||||
#include "fn.h"
|
||||
|
||||
typedef struct Font Font;
|
||||
struct Font
|
||||
{
|
||||
FT_Face face;
|
||||
int base;
|
||||
};
|
||||
|
||||
static FT_Library lib;
|
||||
static Font fonts[Maxfonts];
|
||||
static int nfonts;
|
||||
static u32int blendtab[2][256];
|
||||
|
||||
static u32int
|
||||
blend(u32int bg, u32int fg, int a)
|
||||
{
|
||||
int r, g, b, inv;
|
||||
|
||||
inv = 255 - a;
|
||||
r = ((bg >> 16 & 0xff) * inv + (fg >> 16 & 0xff) * a) / 255;
|
||||
g = ((bg >> 8 & 0xff) * inv + (fg >> 8 & 0xff) * a) / 255;
|
||||
b = ((bg & 0xff) * inv + (fg & 0xff) * a) / 255;
|
||||
return (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
static int
|
||||
loadfont(char *path)
|
||||
{
|
||||
FT_Face face;
|
||||
Font *f;
|
||||
vlong height, pixels;
|
||||
|
||||
if(nfonts >= nelem(fonts))
|
||||
return 0;
|
||||
if(FT_New_Face(lib, path, 0, &face) != 0){
|
||||
fprint(2, "strans: popup: can't load font: %s\n", path);
|
||||
return 0;
|
||||
}
|
||||
height = (vlong)face->ascender - face->descender;
|
||||
if(height <= 0 || face->units_per_EM == 0){
|
||||
fprint(2, "strans: popup: invalid font metrics: %s\n", path);
|
||||
FT_Done_Face(face);
|
||||
return 0;
|
||||
}
|
||||
pixels = (vlong)Fontsz * face->units_per_EM / height;
|
||||
if(pixels < 1)
|
||||
pixels = 1;
|
||||
if(FT_Set_Pixel_Sizes(face, 0, pixels) != 0){
|
||||
fprint(2, "strans: popup: can't size font: %s\n", path);
|
||||
FT_Done_Face(face);
|
||||
return 0;
|
||||
}
|
||||
f = &fonts[nfonts];
|
||||
f->face = face;
|
||||
f->base = (vlong)Fontsz * face->ascender / height;
|
||||
nfonts++;
|
||||
return 1;
|
||||
}
|
||||
static PangoFontMap *fontmap;
|
||||
static PangoContext *context;
|
||||
static PangoLayout *layout;
|
||||
|
||||
static void
|
||||
clearfonts(void)
|
||||
resetlayout(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; i < nfonts; i++){
|
||||
FT_Done_Face(fonts[i].face);
|
||||
fonts[i].face = nil;
|
||||
}
|
||||
nfonts = 0;
|
||||
}
|
||||
|
||||
int
|
||||
fontinit(char **path, int npath)
|
||||
{
|
||||
int i, a;
|
||||
|
||||
if(path == nil || npath < 1 || npath > nelem(fonts)){
|
||||
fprint(2, "strans: popup: need 1-%d font files\n", nelem(fonts));
|
||||
clearfonts();
|
||||
return 0;
|
||||
}
|
||||
if(lib == nil && FT_Init_FreeType(&lib) != 0){
|
||||
fprint(2, "strans: popup: can't initialize FreeType\n");
|
||||
return 0;
|
||||
}
|
||||
clearfonts();
|
||||
for(i = 0; i < npath; i++){
|
||||
if(path[i] == nil || !loadfont(path[i])){
|
||||
clearfonts();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
for(a = 0; a < 256; a++){
|
||||
blendtab[0][a] = blend(Colbg, Colfg, a);
|
||||
blendtab[1][a] = blend(Colsel, Colfg, a);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
drawglyph(Font *f, u32int *buf, int w, int h, int px, int py)
|
||||
{
|
||||
FT_Bitmap *b;
|
||||
FT_GlyphSlot g;
|
||||
uchar *row;
|
||||
vlong pitch, x0, x1, y0, y1;
|
||||
int a, sel, x, xa, xb, y, ya, yb;
|
||||
u32int *p;
|
||||
|
||||
g = f->face->glyph;
|
||||
b = &g->bitmap;
|
||||
if(b->pixel_mode != FT_PIXEL_MODE_GRAY || b->num_grays != 256)
|
||||
return 0;
|
||||
if(b->width == 0 || b->rows == 0)
|
||||
return 1;
|
||||
pitch = b->pitch;
|
||||
if(pitch < 0)
|
||||
pitch = -pitch;
|
||||
if(b->buffer == nil || pitch < b->width)
|
||||
return 0;
|
||||
x0 = (vlong)px + g->bitmap_left;
|
||||
y0 = (vlong)py + f->base - g->bitmap_top;
|
||||
x1 = x0 + b->width;
|
||||
y1 = y0 + b->rows;
|
||||
if(x1 <= 0 || y1 <= 0 || x0 >= w || y0 >= h)
|
||||
return 1;
|
||||
xa = x0 < 0 ? 0 : x0;
|
||||
xb = x1 > w ? w : x1;
|
||||
ya = y0 < 0 ? 0 : y0;
|
||||
yb = y1 > h ? h : y1;
|
||||
for(y = ya; y < yb; y++){
|
||||
if(b->pitch < 0)
|
||||
row = b->buffer + ((vlong)b->rows - 1 - (y - y0)) * pitch;
|
||||
else
|
||||
row = b->buffer + ((vlong)y - y0) * pitch;
|
||||
for(x = xa; x < xb; x++){
|
||||
a = row[(vlong)x - x0];
|
||||
if(a != 0){
|
||||
p = &buf[y * w + x];
|
||||
sel = *p == Colsel;
|
||||
*p = blendtab[sel][a];
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
pango_layout_set_width(layout, -1);
|
||||
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
|
||||
}
|
||||
|
||||
void
|
||||
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
|
||||
textclose(void)
|
||||
{
|
||||
FT_UInt g;
|
||||
int f;
|
||||
|
||||
if(buf == nil || w <= 0 || h <= 0)
|
||||
return;
|
||||
for(f = 0; f < nfonts; f++){
|
||||
g = FT_Get_Char_Index(fonts[f].face, r);
|
||||
if(g == 0 || FT_Load_Glyph(fonts[f].face, g, FT_LOAD_DEFAULT) != 0 ||
|
||||
FT_Render_Glyph(fonts[f].face->glyph, FT_RENDER_MODE_NORMAL) != 0)
|
||||
continue;
|
||||
if(drawglyph(&fonts[f], buf, w, h, px, py))
|
||||
return;
|
||||
}
|
||||
if(layout != nil)
|
||||
g_object_unref(layout);
|
||||
if(context != nil)
|
||||
g_object_unref(context);
|
||||
if(fontmap != nil)
|
||||
g_object_unref(fontmap);
|
||||
layout = nil;
|
||||
context = nil;
|
||||
fontmap = nil;
|
||||
}
|
||||
|
||||
static void
|
||||
setfont(void)
|
||||
{
|
||||
PangoFontDescription *font;
|
||||
PangoRectangle r;
|
||||
int size;
|
||||
|
||||
font = pango_font_description_new();
|
||||
pango_font_description_set_family(font, "sans");
|
||||
pango_font_description_set_absolute_size(font, Fontsz * PANGO_SCALE);
|
||||
pango_layout_set_font_description(layout, font);
|
||||
pango_layout_set_text(layout, "Mg", -1);
|
||||
pango_layout_get_pixel_extents(layout, nil, &r);
|
||||
if(r.height > 0){
|
||||
/* Fontsz is the popup row height, not a point size. */
|
||||
size = Fontsz * PANGO_SCALE * Fontsz / r.height;
|
||||
pango_font_description_set_absolute_size(font, max(size, PANGO_SCALE));
|
||||
pango_layout_set_font_description(layout, font);
|
||||
}
|
||||
pango_font_description_free(font);
|
||||
}
|
||||
|
||||
int
|
||||
textinit(void)
|
||||
{
|
||||
textclose();
|
||||
FcInit();
|
||||
fontmap = pango_cairo_font_map_new();
|
||||
if(fontmap == nil){
|
||||
fprint(2, "strans: popup: can't initialize PangoCairo\n");
|
||||
return 0;
|
||||
}
|
||||
pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(fontmap), 96);
|
||||
context = pango_font_map_create_context(fontmap);
|
||||
if(context != nil)
|
||||
layout = pango_layout_new(context);
|
||||
if(context == nil || layout == nil){
|
||||
fprint(2, "strans: popup: can't create text layout\n");
|
||||
textclose();
|
||||
return 0;
|
||||
}
|
||||
pango_layout_set_single_paragraph_mode(layout, TRUE);
|
||||
setfont();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
settext(Str *s)
|
||||
{
|
||||
char utf[Maxutf];
|
||||
int n;
|
||||
|
||||
if(layout == nil || s == nil || s->n <= 0)
|
||||
return 0;
|
||||
n = stoutf(s, utf, sizeof utf);
|
||||
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;
|
||||
|
||||
if(layout == nil)
|
||||
return 0;
|
||||
resetlayout();
|
||||
if(!settext(s))
|
||||
return 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
|
||||
textdraw(u32int *buf, int w, int h, int x, int y, int fit, u32int color,
|
||||
Str *s)
|
||||
{
|
||||
PangoRectangle r;
|
||||
cairo_surface_t *surface;
|
||||
cairo_t *cr;
|
||||
int b, g, red, stride;
|
||||
|
||||
if(layout == nil || fit <= 0)
|
||||
return;
|
||||
resetlayout();
|
||||
pango_layout_set_width(layout, fit * PANGO_SCALE);
|
||||
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
|
||||
if(w <= 0 || h <= 0 || !settext(s))
|
||||
return;
|
||||
stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w);
|
||||
if(stride < 0 || (vlong)stride != (vlong)w * sizeof buf[0])
|
||||
return;
|
||||
surface = cairo_image_surface_create_for_data((uchar*)buf,
|
||||
CAIRO_FORMAT_RGB24, w, h, stride);
|
||||
if(cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS){
|
||||
cairo_surface_destroy(surface);
|
||||
return;
|
||||
}
|
||||
cairo_surface_mark_dirty(surface);
|
||||
cr = cairo_create(surface);
|
||||
if(cairo_status(cr) == CAIRO_STATUS_SUCCESS){
|
||||
pango_cairo_update_layout(cr, layout);
|
||||
textextents(&r);
|
||||
cairo_rectangle(cr, x, y, fit, Fontsz);
|
||||
cairo_clip(cr);
|
||||
cairo_move_to(cr, x - r.x, y + (Fontsz - r.height) / 2 - r.y);
|
||||
red = color >> 16 & 0xff;
|
||||
g = color >> 8 & 0xff;
|
||||
b = color & 0xff;
|
||||
cairo_set_source_rgb(cr, red / 255.0, g / 255.0, b / 255.0);
|
||||
pango_cairo_show_layout(cr, layout);
|
||||
}
|
||||
cairo_destroy(cr);
|
||||
cairo_surface_flush(surface);
|
||||
cairo_surface_destroy(surface);
|
||||
}
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,94 +0,0 @@
|
||||
Copyright 2018 The Noto Project Authors (github.com/googlei18n/noto-fonts)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,92 +0,0 @@
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,49 +0,0 @@
|
||||
Historical popup fonts
|
||||
======================
|
||||
|
||||
These binaries are no longer stored in the strans source tree. Before font
|
||||
selection became an ordered list of command-line file paths, the renderer used
|
||||
these repository files in this fixed order:
|
||||
|
||||
1. NotoSans-Regular.ttf
|
||||
2. NotoSansMonoCJKjp-Regular.otf
|
||||
3. NotoEmoji-Regular.ttf
|
||||
|
||||
NotoSans-Regular.ttf
|
||||
--------------------
|
||||
|
||||
SHA-256: 9cb49a54e520423033f9727be2e53e4805a60656deb09c219740d8e5f3e033ac
|
||||
Internal family/version: Noto Sans Regular, version 2.005
|
||||
License: SIL Open Font License 1.1 (OFL-NotoSans.txt)
|
||||
Source revision: 7697007fcb3563290d73f41f56a70d5d559d828c
|
||||
Source: https://raw.githubusercontent.com/notofonts/noto-fonts/7697007fcb3563290d73f41f56a70d5d559d828c/hinted/ttf/NotoSans/NotoSans-Regular.ttf
|
||||
|
||||
This file entered the strans repository in commit 874b941. Its source URL was
|
||||
not recorded then; the immutable revision above was recovered later and is
|
||||
byte-identical to the historical file.
|
||||
|
||||
NotoSansMonoCJKjp-Regular.otf
|
||||
--------------------------------
|
||||
|
||||
SHA-256: 4d01725be822d144cf9a56ade981e6fb920cd7a610b8fc24cc601a920beea5b9
|
||||
Release: Noto Sans CJK 2.004, tag Sans2.004
|
||||
Source revision: 523d033d6cb47f4a80c58a35753646f5c3608a78
|
||||
Source: https://raw.githubusercontent.com/notofonts/noto-cjk/523d033d6cb47f4a80c58a35753646f5c3608a78/Sans/Mono/NotoSansMonoCJKjp-Regular.otf
|
||||
License: SIL Open Font License 1.1 (OFL-NotoSansCJK.txt)
|
||||
License source: https://raw.githubusercontent.com/notofonts/noto-cjk/523d033d6cb47f4a80c58a35753646f5c3608a78/LICENSE
|
||||
|
||||
NotoEmoji-Regular.ttf
|
||||
---------------------
|
||||
|
||||
SHA-256: 415dc6290378574135b64c808dc640c1df7531973290c4970c51fdeb849cb0c5
|
||||
Internal family/version: Noto Emoji Regular, version 1.05
|
||||
Source revision: 2f1ffdd6fbbd05d6f382138a3d3adcd89c5ce800
|
||||
Source: https://raw.githubusercontent.com/googlei18n/noto-emoji/2f1ffdd6fbbd05d6f382138a3d3adcd89c5ce800/fonts/NotoEmoji-Regular.ttf
|
||||
Font license: SIL Open Font License 1.1
|
||||
Font license source: https://raw.githubusercontent.com/googlei18n/noto-emoji/2f1ffdd6fbbd05d6f382138a3d3adcd89c5ce800/fonts/LICENSE
|
||||
Font license SHA-256: 6a73f9541c2de74158c0e7cf6b0a58ef774f5a780bf191f2d7ec9cc53efe2bf2
|
||||
|
||||
The font-specific license is byte-identical to `OFL-NotoSansCJK.txt`. The
|
||||
previously recorded `Apache-2.0-NotoEmoji.txt` is retained as the license of
|
||||
the upstream project at that revision; it is not the license declared by this
|
||||
font binary.
|
||||
40
gtk/Makefile
40
gtk/Makefile
@@ -1,24 +1,40 @@
|
||||
PROG = im-strans.so
|
||||
CFLAGS = -Wall -O2 -I.. $(shell pkg-config --cflags gtk+-3.0)
|
||||
LDFLAGS = $(shell pkg-config --libs gtk+-3.0)
|
||||
LIBDIR ?= /usr/lib
|
||||
GTK_MODULE_DIR = $(LIBDIR)/gtk-3.0/3.0.0/immodules
|
||||
PKG_CONFIG ?= pkg-config
|
||||
CFLAGS ?= -O2
|
||||
GTK_CFLAGS = $(shell $(PKG_CONFIG) --cflags gtk+-3.0)
|
||||
GTK_LIBS = $(shell $(PKG_CONFIG) --libs gtk+-3.0)
|
||||
GTK_QUERY_IMMODULES ?= gtk-query-immodules-3.0
|
||||
GTK_LIBDIR ?= $(shell $(PKG_CONFIG) --variable=libdir gtk+-3.0 2>/dev/null)
|
||||
GTK_BINARY_VERSION ?= $(shell $(PKG_CONFIG) \
|
||||
--variable=gtk_binary_version gtk+-3.0 2>/dev/null)
|
||||
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)
|
||||
|
||||
$(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)
|
||||
test -n "$(LIBDIR)"
|
||||
mkdir -p "$(DESTDIR)$(GTK_MODULE_DIR)"
|
||||
cp $(PROG) "$(DESTDIR)$(GTK_MODULE_DIR)/"
|
||||
if test -z "$(DESTDIR)"; then gtk-query-immodules-3.0 --update-cache; fi
|
||||
module_dir="$(GTK_MODULE_DIR)"; $(CHECKDIR); \
|
||||
mkdir -p "$(DESTDIR)$$module_dir" && \
|
||||
cp "$(PROG)" "$(DESTDIR)$$module_dir/" && $(REFRESH)
|
||||
|
||||
uninstall:
|
||||
test -n "$(LIBDIR)"
|
||||
rm -f "$(DESTDIR)$(GTK_MODULE_DIR)/$(PROG)"
|
||||
if test -z "$(DESTDIR)"; then gtk-query-immodules-3.0 --update-cache; fi
|
||||
module_dir="$(GTK_MODULE_DIR)"; $(CHECKDIR); \
|
||||
rm -f "$(DESTDIR)$$module_dir/$(PROG)" && $(REFRESH)
|
||||
|
||||
clean:
|
||||
rm -f $(PROG)
|
||||
|
||||
455
gtk/main.c
455
gtk/main.c
@@ -1,132 +1,246 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <gtk/gtk.h>
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
#include "ipc.h"
|
||||
|
||||
typedef struct Im Im;
|
||||
struct Im
|
||||
{
|
||||
GtkIMContext parent;
|
||||
GtkIMContextSimple parent;
|
||||
int fd;
|
||||
int usepreedit;
|
||||
int ext;
|
||||
int private;
|
||||
int simpleactive;
|
||||
int simpledone;
|
||||
char pre[Ipcfieldmax+1];
|
||||
int prelen;
|
||||
GdkWindow *win;
|
||||
GdkRectangle cursor;
|
||||
int cursorvalid;
|
||||
int caretsent;
|
||||
unsigned char sent[Ipccaretsz];
|
||||
};
|
||||
|
||||
typedef struct ImClass ImClass;
|
||||
struct ImClass
|
||||
{
|
||||
GtkIMContextClass parent;
|
||||
GtkIMContextSimpleClass parent;
|
||||
};
|
||||
|
||||
static GType imtype;
|
||||
static GObjectClass *parentobject;
|
||||
static GtkIMContextClass *parentim;
|
||||
|
||||
static void
|
||||
srvconnect(Im *im)
|
||||
setpreedit(Im *im, const char *s, int n)
|
||||
{
|
||||
int was;
|
||||
|
||||
if(n < 0 || n > Ipcfieldmax)
|
||||
n = 0;
|
||||
if(n == 0 && im->prelen == 0)
|
||||
return;
|
||||
was = im->prelen;
|
||||
if(n > 0)
|
||||
memcpy(im->pre, s, n);
|
||||
im->pre[n] = '\0';
|
||||
im->prelen = n;
|
||||
if(was == 0 && n > 0)
|
||||
g_signal_emit_by_name(im, "preedit-start");
|
||||
g_signal_emit_by_name(im, "preedit-changed");
|
||||
if(was > 0 && n == 0)
|
||||
g_signal_emit_by_name(im, "preedit-end");
|
||||
}
|
||||
|
||||
static void
|
||||
srvdrop(Im *im, int notify)
|
||||
{
|
||||
if(im->fd >= 0)
|
||||
return;
|
||||
im->fd = ipcconnect();
|
||||
close(im->fd);
|
||||
im->fd = -1;
|
||||
im->ext = 0;
|
||||
im->caretsent = 0;
|
||||
if(notify)
|
||||
setpreedit(im, "", 0);
|
||||
else{
|
||||
im->pre[0] = '\0';
|
||||
im->prelen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
srvclose(Im *im)
|
||||
{
|
||||
int was;
|
||||
srvdrop(im, 1);
|
||||
}
|
||||
|
||||
if(im->fd >= 0)
|
||||
close(im->fd);
|
||||
im->fd = -1;
|
||||
was = im->prelen;
|
||||
im->pre[0] = '\0';
|
||||
im->prelen = 0;
|
||||
if(was > 0){
|
||||
g_signal_emit_by_name(im, "preedit-changed");
|
||||
g_signal_emit_by_name(im, "preedit-end");
|
||||
/* Both buffers are char[Ipcfieldmax+1]. */
|
||||
static int
|
||||
readresp(Im *im, int want, char *commit, char *pre, Ipcresp *resp)
|
||||
{
|
||||
if(ipcreadresp(im->fd, want, commit, pre, resp) < 0)
|
||||
return -1;
|
||||
if(!g_utf8_validate(commit, resp->commitlen, NULL) ||
|
||||
(want && !g_utf8_validate(pre, resp->preeditlen, NULL))){
|
||||
errno = EPROTO;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
dropwindow(Im *im)
|
||||
{
|
||||
if(im->win != NULL)
|
||||
g_object_unref(im->win);
|
||||
im->win = NULL;
|
||||
im->cursorvalid = 0;
|
||||
}
|
||||
|
||||
/* Root-relative caret in device pixels; returns whether it is known. */
|
||||
static int
|
||||
caretget(Im *im, int32_t *x, int32_t *y, int32_t *h)
|
||||
{
|
||||
gint rx, ry, scale;
|
||||
int64_t sx, sy, sh;
|
||||
|
||||
*x = *y = *h = 0;
|
||||
if(im->win != NULL && gdk_window_is_destroyed(im->win))
|
||||
dropwindow(im);
|
||||
if(!im->cursorvalid || im->win == NULL || im->cursor.height < 0)
|
||||
return 0;
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
if(!GDK_IS_X11_WINDOW(im->win))
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
scale = gdk_window_get_scale_factor(im->win);
|
||||
if(scale <= 0)
|
||||
return 0;
|
||||
/* GDK translates the client-relative point before device scaling. */
|
||||
gdk_window_get_root_coords(im->win, 0, 0, &rx, &ry);
|
||||
sx = ((int64_t)rx + im->cursor.x) * scale;
|
||||
sy = ((int64_t)ry + im->cursor.y) * scale;
|
||||
sh = (int64_t)im->cursor.height * scale;
|
||||
if(sx < INT32_MIN || sx > INT32_MAX ||
|
||||
sy < INT32_MIN || sy > INT32_MAX || sh > INT32_MAX)
|
||||
return 0;
|
||||
*x = sx;
|
||||
*y = sy;
|
||||
*h = sh;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
readresp(Im *im, char *buf, int bufsz)
|
||||
sendcaret(Im *im)
|
||||
{
|
||||
Ipcresp resp;
|
||||
int was;
|
||||
unsigned char buf[Ipccaretsz];
|
||||
int32_t x, y, h;
|
||||
int valid;
|
||||
|
||||
was = im->prelen;
|
||||
if(ipcreadresp(im->fd, 1, buf, bufsz, im->pre,
|
||||
sizeof(im->pre), &resp) < 0)
|
||||
if(im->fd < 0 || !im->ext)
|
||||
return 0;
|
||||
valid = caretget(im, &x, &y, &h);
|
||||
ipcpackcaret(buf, valid, x, y, h);
|
||||
if(im->caretsent && memcmp(buf, im->sent, sizeof buf) == 0)
|
||||
return 0;
|
||||
if(ipcsend(im->fd, buf, sizeof buf) < 0)
|
||||
return -1;
|
||||
im->prelen = resp.npreedit;
|
||||
if(was == 0 && im->prelen > 0)
|
||||
g_signal_emit_by_name(im, "preedit-start");
|
||||
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;
|
||||
memcpy(im->sent, buf, sizeof buf);
|
||||
im->caretsent = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
kget(uint32_t gdk)
|
||||
static int
|
||||
srvconnect(Im *im)
|
||||
{
|
||||
uint32_t u;
|
||||
unsigned char buf[Ipcreqsz];
|
||||
char commit[Ipcfieldmax+1];
|
||||
char pre[Ipcfieldmax+1];
|
||||
Ipcresp resp;
|
||||
|
||||
u = gdk_keyval_to_unicode(gdk);
|
||||
if((gdk & 0xff000000) == 0x01000000 && u != 0)
|
||||
return u;
|
||||
if(gdk >= 0xff00 && gdk <= 0xffff)
|
||||
return Kspec + (gdk - 0xff00);
|
||||
return u;
|
||||
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 uint32_t
|
||||
mget(uint32_t state)
|
||||
static void
|
||||
simplecommit(GtkIMContext *ctx, const char *s, Im *im)
|
||||
{
|
||||
uint32_t m;
|
||||
|
||||
m = 0;
|
||||
if(state & GDK_SHIFT_MASK)
|
||||
m |= Mshift;
|
||||
if(state & GDK_CONTROL_MASK)
|
||||
m |= Mctrl;
|
||||
if(state & GDK_MOD1_MASK)
|
||||
m |= Malt;
|
||||
if(state & GDK_SUPER_MASK)
|
||||
m |= Msuper;
|
||||
return m;
|
||||
(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
|
||||
sendreset(Im *im)
|
||||
{
|
||||
unsigned char buf[Ipcreqsz];
|
||||
char resp[Ipcfieldmax+1];
|
||||
char commit[Ipcfieldmax+1];
|
||||
char pre[Ipcfieldmax+1];
|
||||
Ipcresp resp;
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
ipcpackreset(buf, 1);
|
||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||
readresp(im, resp, sizeof(resp)) < 0)
|
||||
srvclose(im);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
plaincommit(GtkIMContext *ctx, uint32_t key, uint32_t mod)
|
||||
{
|
||||
char u[8];
|
||||
int n;
|
||||
|
||||
if(mod & (Mctrl|Malt|Msuper))
|
||||
return FALSE;
|
||||
if(key < 0x20 || key >= Kspec)
|
||||
return FALSE;
|
||||
n = g_unichar_to_utf8(key, u);
|
||||
u[n] = '\0';
|
||||
g_signal_emit_by_name(ctx, "commit", u);
|
||||
return TRUE;
|
||||
setpreedit(im, "", 0);
|
||||
if(commit[0] != '\0')
|
||||
g_signal_emit_by_name(im, "commit", commit);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@@ -134,41 +248,45 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
||||
{
|
||||
Im *im;
|
||||
unsigned char buf[Ipcreqsz];
|
||||
char resp[Ipcfieldmax+1];
|
||||
char commit[Ipcfieldmax+1];
|
||||
char pre[Ipcfieldmax+1];
|
||||
uint32_t key, mod;
|
||||
int r;
|
||||
GtkInputPurpose purpose;
|
||||
Ipcresp resp;
|
||||
|
||||
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;
|
||||
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(ipcsend(im->fd, buf, sizeof buf) < 0){
|
||||
if(im->simpleactive)
|
||||
return simplefilter(ctx, ev, 0);
|
||||
key = ipckeysym(ev->keyval, gdk_keyval_to_unicode(ev->keyval));
|
||||
mod = ipcmod(ev->state);
|
||||
if(im->private || key == 0)
|
||||
return simplefilter(ctx, ev, 0);
|
||||
if(srvconnect(im) < 0)
|
||||
return simplefilter(ctx, ev, 0);
|
||||
/* A retained GDK window may have moved since the last cursor report. */
|
||||
if(sendcaret(im) < 0){
|
||||
srvclose(im);
|
||||
return plaincommit(ctx, key, mod);
|
||||
return simplefilter(ctx, ev, 0);
|
||||
}
|
||||
r = readresp(im, resp, sizeof(resp));
|
||||
if(r < 0){
|
||||
ipcpackreq(buf, im->usepreedit, mod, key);
|
||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||
readresp(im, im->usepreedit, commit, pre, &resp) < 0){
|
||||
srvclose(im);
|
||||
return plaincommit(ctx, key, mod);
|
||||
return simplefilter(ctx, ev, 0);
|
||||
}
|
||||
if(resp[0] != '\0')
|
||||
g_signal_emit_by_name(ctx, "commit", resp);
|
||||
if(r != 0)
|
||||
if(im->usepreedit && commit[0] != '\0' && im->prelen > 0)
|
||||
setpreedit(im, "", 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 plaincommit(ctx, key, mod);
|
||||
return simplefilter(ctx, ev, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -179,6 +297,10 @@ getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs,
|
||||
PangoAttribute *u;
|
||||
|
||||
im = (Im*)ctx;
|
||||
if(im->simpleactive){
|
||||
parentim->get_preedit_string(ctx, str, attrs, cursor_pos);
|
||||
return;
|
||||
}
|
||||
if(str)
|
||||
*str = g_strdup(im->pre);
|
||||
if(attrs){
|
||||
@@ -197,6 +319,9 @@ getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs,
|
||||
static void
|
||||
reset(GtkIMContext *ctx)
|
||||
{
|
||||
if(parentim->reset != NULL)
|
||||
parentim->reset(ctx);
|
||||
((Im*)ctx)->simpleactive = 0;
|
||||
sendreset((Im*)ctx);
|
||||
}
|
||||
|
||||
@@ -206,28 +331,136 @@ focusout(GtkIMContext *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);
|
||||
/* Closing the context connection releases engine ownership. */
|
||||
srvclose(im);
|
||||
}
|
||||
|
||||
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*)obj;
|
||||
if(im->fd >= 0)
|
||||
close(im->fd);
|
||||
G_OBJECT_CLASS(g_type_class_peek_parent(G_OBJECT_GET_CLASS(obj)))->finalize(obj);
|
||||
srvdrop(im, 0);
|
||||
dropwindow(im);
|
||||
parentobject->dispose(obj);
|
||||
}
|
||||
|
||||
static void
|
||||
init(Im *im)
|
||||
{
|
||||
GtkInputPurpose purpose;
|
||||
|
||||
im->fd = -1;
|
||||
im->usepreedit = 1;
|
||||
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
|
||||
@@ -238,11 +471,16 @@ classinit(ImClass *klass)
|
||||
|
||||
ic = GTK_IM_CONTEXT_CLASS(klass);
|
||||
oc = G_OBJECT_CLASS(klass);
|
||||
parentim = g_type_class_peek_parent(klass);
|
||||
parentobject = G_OBJECT_CLASS(parentim);
|
||||
ic->filter_keypress = kpress;
|
||||
ic->get_preedit_string = getpreedit;
|
||||
ic->reset = reset;
|
||||
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 = {
|
||||
@@ -267,7 +505,8 @@ im_module_init(GTypeModule *mod)
|
||||
0,
|
||||
(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
|
||||
|
||||
168
hash.c
168
hash.c
@@ -1,168 +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)
|
||||
return nil;
|
||||
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;
|
||||
|
||||
if(h == nil || key == nil || key->n < 0 || key->n > Maxrunes)
|
||||
return nil;
|
||||
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, int *len)
|
||||
{
|
||||
char buf[Maxutf];
|
||||
char *p;
|
||||
int n;
|
||||
|
||||
n = stoutf(s, buf, sizeof(buf));
|
||||
p = emalloc(n + 1);
|
||||
memmove(p, buf, n);
|
||||
p[n] = '\0';
|
||||
*len = n;
|
||||
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;
|
||||
|
||||
if(store == nil || *store == nil || key == nil ||
|
||||
key->n < 0 || key->n > Maxrunes || vlen < 0 ||
|
||||
(vlen > 0 && val == nil))
|
||||
return;
|
||||
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);
|
||||
n->filled = 1;
|
||||
}
|
||||
n->next = next;
|
||||
free(n->val);
|
||||
n->val = newval;
|
||||
n->vlen = vlen;
|
||||
}
|
||||
306
ipc.c
306
ipc.c
@@ -1,12 +1,87 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include "ipc.h"
|
||||
|
||||
static int64_t
|
||||
nowms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
static int64_t
|
||||
deadline(void)
|
||||
{
|
||||
return nowms() + Ipcwaitms;
|
||||
}
|
||||
|
||||
static int
|
||||
waitfd(int fd, short events, int64_t until)
|
||||
{
|
||||
struct pollfd pfd;
|
||||
int64_t left;
|
||||
int n;
|
||||
|
||||
pfd.fd = fd;
|
||||
pfd.events = events;
|
||||
for(;;){
|
||||
left = until - nowms();
|
||||
if(left <= 0){
|
||||
errno = ETIMEDOUT;
|
||||
return -1;
|
||||
}
|
||||
n = poll(&pfd, 1, left);
|
||||
if(n < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(n < 0)
|
||||
return -1;
|
||||
if(n == 0){
|
||||
errno = ETIMEDOUT;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
readwait(int fd, void *buf, size_t n, int64_t until)
|
||||
{
|
||||
unsigned char *p;
|
||||
ssize_t r;
|
||||
|
||||
p = buf;
|
||||
while(n > 0){
|
||||
r = recv(fd, p, n, MSG_DONTWAIT);
|
||||
if(r < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){
|
||||
if(waitfd(fd, POLLIN, until) < 0)
|
||||
return -1;
|
||||
continue;
|
||||
}
|
||||
if(r < 0)
|
||||
return -1;
|
||||
if(r == 0){
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
p += r;
|
||||
n -= r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
putlen(unsigned char p[Ipclensz], size_t n)
|
||||
{
|
||||
@@ -20,14 +95,33 @@ getlen(const unsigned char p[Ipclensz])
|
||||
return p[0] | (p[1] << 8);
|
||||
}
|
||||
|
||||
static void
|
||||
put32(unsigned char *p, int32_t v)
|
||||
{
|
||||
uint32_t u;
|
||||
|
||||
u = v;
|
||||
p[0] = u;
|
||||
p[1] = u >> 8;
|
||||
p[2] = u >> 16;
|
||||
p[3] = u >> 24;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
get32(const unsigned char *p)
|
||||
{
|
||||
return (int32_t)((uint32_t)p[0] |
|
||||
((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) |
|
||||
((uint32_t)p[3] << 24));
|
||||
}
|
||||
|
||||
int
|
||||
ipcpath(char *dst, size_t cap)
|
||||
{
|
||||
const char *dir, *sep;
|
||||
int n;
|
||||
|
||||
if(dst == NULL || cap == 0)
|
||||
return -1;
|
||||
dir = getenv("XDG_RUNTIME_DIR");
|
||||
if(dir != NULL && dir[0] == '/'){
|
||||
sep = dir[strlen(dir)-1] == '/' ? "" : "/";
|
||||
@@ -40,6 +134,11 @@ ipcpath(char *dst, size_t cap)
|
||||
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)
|
||||
{
|
||||
@@ -52,15 +151,49 @@ ipcconnect(void)
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
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)
|
||||
return fd;
|
||||
e = errno;
|
||||
close(fd);
|
||||
errno = e;
|
||||
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.
|
||||
* The keypad and Shift+Tab (ISO_Left_Tab) variants fold onto Enter and Tab.
|
||||
*/
|
||||
uint32_t
|
||||
ipckeysym(uint32_t sym, uint32_t unicode)
|
||||
{
|
||||
if(unicode >= ' ' && unicode != 0x7f)
|
||||
return unicode;
|
||||
switch(sym){
|
||||
case 0xfe20:
|
||||
case 0xff89:
|
||||
return Ktab;
|
||||
case 0xff8d:
|
||||
return Kret;
|
||||
}
|
||||
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
|
||||
@@ -82,6 +215,32 @@ ipcpackreset(unsigned char req[Ipcreqsz], int want)
|
||||
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
|
||||
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
||||
uint32_t *key)
|
||||
@@ -94,6 +253,40 @@ ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
||||
((uint32_t)req[5] << 24);
|
||||
}
|
||||
|
||||
int
|
||||
ipcreqtype(const unsigned char req[Ipcreqsz])
|
||||
{
|
||||
if((req[0] & Ipcext) == 0)
|
||||
return Ipckey;
|
||||
if(req[1] != Ipcversion)
|
||||
return Ipcunknown;
|
||||
switch(req[2]){
|
||||
case Ipcopcap:
|
||||
if((req[0] & ~(Ipcext|Ipcreqwant)) != 0 ||
|
||||
req[3] != 0 || req[4] != 0 || req[5] != 0)
|
||||
return Ipcunknown;
|
||||
return Ipccap;
|
||||
case Ipcopcaret:
|
||||
if(req[0] != Ipcext || req[3] > 1)
|
||||
return Ipcunknown;
|
||||
return Ipccaret;
|
||||
}
|
||||
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])
|
||||
{
|
||||
@@ -109,21 +302,15 @@ ipcpackresp(unsigned char *dst, size_t cap, int eaten,
|
||||
|
||||
if(ncommit > Ipcfieldmax || npreedit > Ipcfieldmax)
|
||||
return -1;
|
||||
if((ncommit > 0 && commit == NULL) ||
|
||||
(want && npreedit > 0 && preedit == NULL))
|
||||
return -1;
|
||||
n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0);
|
||||
if(dst == NULL || cap < n)
|
||||
if(cap < n)
|
||||
return -1;
|
||||
dst[0] = eaten != 0;
|
||||
putlen(dst + 1, ncommit);
|
||||
if(ncommit > 0)
|
||||
memcpy(dst + Ipcresphdrsz, commit, ncommit);
|
||||
memcpy(dst + Ipcresphdrsz, commit, ncommit);
|
||||
if(want){
|
||||
putlen(dst + Ipcresphdrsz + ncommit, npreedit);
|
||||
if(npreedit > 0)
|
||||
memcpy(dst + Ipcresphdrsz + ncommit + Ipclensz,
|
||||
preedit, npreedit);
|
||||
memcpy(dst + Ipcresphdrsz + ncommit + Ipclensz, preedit, npreedit);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
@@ -152,73 +339,78 @@ ipcsend(int fd, const void *buf, size_t n)
|
||||
{
|
||||
const unsigned char *p;
|
||||
ssize_t r;
|
||||
int64_t until;
|
||||
|
||||
p = buf;
|
||||
until = deadline();
|
||||
while(n > 0){
|
||||
r = send(fd, p, n, MSG_NOSIGNAL);
|
||||
r = send(fd, p, n, MSG_NOSIGNAL|MSG_DONTWAIT);
|
||||
if(r < 0 && errno == EINTR)
|
||||
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;
|
||||
if(r == 0){
|
||||
errno = EPIPE;
|
||||
return -1;
|
||||
}
|
||||
p += r;
|
||||
n -= r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Reads a length-prefixed field into dst[Ipcfieldmax+1]. */
|
||||
static int
|
||||
readfield(int fd, size_t n, char *dst, size_t cap)
|
||||
readfield(int fd, char *dst, int64_t until)
|
||||
{
|
||||
unsigned char discard[128];
|
||||
size_t keep, part;
|
||||
unsigned char len[Ipclensz];
|
||||
size_t n;
|
||||
|
||||
if(cap > 0 && dst == NULL)
|
||||
if(readwait(fd, len, sizeof len, until) < 0)
|
||||
return -1;
|
||||
keep = 0;
|
||||
if(cap > 0)
|
||||
keep = n >= cap ? cap - 1 : n;
|
||||
if(keep > 0 && ipcreadn(fd, dst, keep) < 0)
|
||||
n = getlen(len);
|
||||
if(n > Ipcfieldmax){
|
||||
errno = EPROTO;
|
||||
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
|
||||
ipcreadresp(int fd, int want, char *commit, size_t ccap,
|
||||
char *preedit, size_t pcap, Ipcresp *resp)
|
||||
ipcreadresp(int fd, int want, char *commit, char *preedit, Ipcresp *resp)
|
||||
{
|
||||
unsigned char hdr[Ipcresphdrsz], npreedit[Ipclensz];
|
||||
unsigned char eaten;
|
||||
int64_t until;
|
||||
int n;
|
||||
|
||||
if(resp == NULL || (ccap > 0 && commit == NULL) ||
|
||||
(pcap > 0 && preedit == NULL))
|
||||
return -1;
|
||||
if(ccap > 0)
|
||||
commit[0] = '\0';
|
||||
if(pcap > 0)
|
||||
preedit[0] = '\0';
|
||||
commit[0] = '\0';
|
||||
preedit[0] = '\0';
|
||||
memset(resp, 0, sizeof *resp);
|
||||
if(ipcreadn(fd, hdr, sizeof hdr) < 0)
|
||||
until = deadline();
|
||||
if(readwait(fd, &eaten, 1, until) < 0)
|
||||
return -1;
|
||||
resp->eaten = hdr[0] != 0;
|
||||
resp->ncommit = getlen(hdr + 1);
|
||||
if(resp->ncommit > Ipcfieldmax)
|
||||
if(eaten > 1){
|
||||
errno = EPROTO;
|
||||
return -1;
|
||||
if(readfield(fd, resp->ncommit, commit, ccap) < 0)
|
||||
}
|
||||
resp->eaten = eaten;
|
||||
n = readfield(fd, commit, until);
|
||||
if(n < 0)
|
||||
return -1;
|
||||
resp->commitlen = n;
|
||||
if(!want)
|
||||
return 0;
|
||||
if(ipcreadn(fd, npreedit, sizeof npreedit) < 0)
|
||||
n = readfield(fd, preedit, until);
|
||||
if(n < 0)
|
||||
return -1;
|
||||
resp->npreedit = getlen(npreedit);
|
||||
if(resp->npreedit > Ipcfieldmax)
|
||||
return -1;
|
||||
return readfield(fd, resp->npreedit, preedit, pcap);
|
||||
resp->preeditlen = n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
45
ipc.h
45
ipc.h
@@ -2,21 +2,33 @@
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* Request: [flags, modifiers, key byte 0, ..., key byte 3].
|
||||
* Flags request preedit and distinguish lifecycle reset from physical Escape.
|
||||
* Key request: [flags, modifiers, key byte 0, ..., key byte 3].
|
||||
* The want flag asks for the preedit text: the client then draws it and
|
||||
* the popup does not. The reset flag is a lifecycle reset, not Escape.
|
||||
* The server identifies the connection as the engine owner; that identity is
|
||||
* not sent on the wire.
|
||||
* Only Mmask modifier bits are retained.
|
||||
* Response: [eaten, commit-length-low, commit-length-high, commit...],
|
||||
* followed, when requested, by
|
||||
* [preedit-length-low, preedit-length-high, preedit...].
|
||||
* Lengths are little-endian byte counts; fields are at most Ipcfieldmax bytes.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
enum
|
||||
{
|
||||
Ipcreqwant = 1<<0,
|
||||
Ipcreqreset = 1<<1,
|
||||
Ipcext = 1<<7,
|
||||
Ipcversion = 1,
|
||||
Ipcopcap = 0,
|
||||
Ipcopcaret = 1,
|
||||
|
||||
Kspec = 0x110000,
|
||||
Kback = Kspec|0x08,
|
||||
@@ -25,37 +37,58 @@ enum
|
||||
Kesc = Kspec|0x1b,
|
||||
Kup = Kspec|0x52,
|
||||
Kdown = Kspec|0x54,
|
||||
Kpgup = Kspec|0x55,
|
||||
Kpgdown = Kspec|0x56,
|
||||
Kmodfirst = Kspec|0xe1,
|
||||
Kmodlast = Kspec|0xee,
|
||||
|
||||
/* X core modifier bits; GDK and IBus also flag Super at bit 26. */
|
||||
Mshift = 1<<0,
|
||||
Mctrl = 1<<2,
|
||||
Malt = 1<<3,
|
||||
Msuper = 1<<6,
|
||||
Mmask = Mshift|Mctrl|Malt|Msuper,
|
||||
Msupervirt = 1<<26,
|
||||
|
||||
Ipcreqsz = 6,
|
||||
Ipccaretsz = 16,
|
||||
Ipclensz = 2,
|
||||
Ipcresphdrsz = 1 + Ipclensz,
|
||||
Ipcfieldmax = 256,
|
||||
Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax,
|
||||
Ipcwaitms = 250,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
Ipcunknown = -1,
|
||||
Ipckey,
|
||||
Ipccap,
|
||||
Ipccaret,
|
||||
};
|
||||
|
||||
typedef struct Ipcresp Ipcresp;
|
||||
struct Ipcresp
|
||||
{
|
||||
int eaten;
|
||||
size_t ncommit;
|
||||
size_t npreedit;
|
||||
/* Bytes copied to each caller buffer, excluding its trailing NUL. */
|
||||
size_t commitlen;
|
||||
size_t preeditlen;
|
||||
};
|
||||
|
||||
uint32_t ipckeysym(uint32_t, uint32_t);
|
||||
uint32_t ipcmod(uint32_t);
|
||||
void ipcpackreq(unsigned char[Ipcreqsz], int, uint32_t, uint32_t);
|
||||
void ipcpackreset(unsigned char[Ipcreqsz], int);
|
||||
void ipcpackcap(unsigned char[Ipcreqsz], int);
|
||||
void ipcpackcaret(unsigned char[Ipccaretsz], int, int32_t, int32_t, int32_t);
|
||||
void 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, const char*, size_t, const char*, size_t, int);
|
||||
int ipcreadn(int, void*, size_t);
|
||||
int ipcsend(int, const void*, size_t);
|
||||
int ipcreadresp(int, int, char*, size_t, char*, size_t, Ipcresp*);
|
||||
int ipcreadresp(int, int, char*, char*, Ipcresp*);
|
||||
int ipcpath(char*, size_t);
|
||||
int ipcconnect(void);
|
||||
|
||||
16
ko.c
16
ko.c
@@ -189,6 +189,10 @@ issyl(Rune r)
|
||||
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
|
||||
transko(Im *im, Rune c)
|
||||
{
|
||||
@@ -199,10 +203,7 @@ transko(Im *im, Rune c)
|
||||
memset(&e, 0, sizeof e);
|
||||
jm = keytojamo(c);
|
||||
if(jm == 0){
|
||||
if(im->pre.n > 0){
|
||||
e.s = im->pre;
|
||||
sclear(&im->pre);
|
||||
}
|
||||
e.s = im->pre;
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -217,14 +218,12 @@ transko(Im *im, Rune c)
|
||||
ci = Choidx(last);
|
||||
ji = Jungidx(jm);
|
||||
if(ci >= 0 && ji >= 0){
|
||||
spopr(&im->pre);
|
||||
sputr(&e.next, compose(ci, ji, 0));
|
||||
return e;
|
||||
}
|
||||
if(Jungidx(last) >= 0 && ji >= 0){
|
||||
comb = combine(cvow, nelem(cvow), last, jm);
|
||||
if(comb){
|
||||
spopr(&im->pre);
|
||||
sputr(&e.next, comb);
|
||||
return e;
|
||||
}
|
||||
@@ -239,7 +238,6 @@ transko(Im *im, Rune c)
|
||||
if(joi == 0){
|
||||
ni = Jongidx(jm);
|
||||
if(ni > 0){
|
||||
spopr(&im->pre);
|
||||
sputr(&e.next, compose(ci, ji, ni));
|
||||
return e;
|
||||
}
|
||||
@@ -248,7 +246,6 @@ transko(Im *im, Rune c)
|
||||
comb = combine(cvow, nelem(cvow), jung[ji], jm);
|
||||
if(comb){
|
||||
ni = Jungidx(comb);
|
||||
spopr(&im->pre);
|
||||
sputr(&e.next, compose(ci, ni, 0));
|
||||
return e;
|
||||
}
|
||||
@@ -260,7 +257,6 @@ transko(Im *im, Rune c)
|
||||
if(comb){
|
||||
ni = Jongidx(comb);
|
||||
if(ni > 0){
|
||||
spopr(&im->pre);
|
||||
sputr(&e.next, compose(ci, ji, ni));
|
||||
return e;
|
||||
}
|
||||
@@ -271,14 +267,12 @@ transko(Im *im, Rune c)
|
||||
if(splitpair(cjong, nelem(cjong), jc, &stay, &next)){
|
||||
si = Jongidx(stay);
|
||||
ni = Choidx(next);
|
||||
spopr(&im->pre);
|
||||
sputr(&e.s, compose(ci, ji, si));
|
||||
sputr(&e.next, compose(ni, vi, 0));
|
||||
return e;
|
||||
}
|
||||
ni = Choidx(jc);
|
||||
if(ni >= 0){
|
||||
spopr(&im->pre);
|
||||
sputr(&e.s, compose(ci, ji, 0));
|
||||
sputr(&e.next, compose(ni, vi, 0));
|
||||
return e;
|
||||
|
||||
31
main.c
31
main.c
@@ -3,15 +3,11 @@
|
||||
|
||||
Channel *drawc;
|
||||
Channel *keyc;
|
||||
Channel *dictreqc;
|
||||
Channel *dictresc;
|
||||
char **fontfiles;
|
||||
int nfontfiles;
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
fprint(2, "usage: strans mapdir fontfile [fontfile ...]\n");
|
||||
fprint(2, "usage: strans mapdir\n");
|
||||
threadexitsall("usage");
|
||||
}
|
||||
|
||||
@@ -51,25 +47,20 @@ erealloc(void *p, ulong n)
|
||||
void
|
||||
threadmain(int argc, char **argv)
|
||||
{
|
||||
if(argc < 3 || argc > Maxfonts + 2)
|
||||
char *display;
|
||||
|
||||
if(argc != 2)
|
||||
usage();
|
||||
|
||||
fontfiles = argv + 2;
|
||||
nfontfiles = argc - 2;
|
||||
drawc = chancreate(sizeof(Drawcmd), 4);
|
||||
keyc = chancreate(sizeof(Keyreq), 0);
|
||||
dictreqc = chancreate(sizeof(Dictreq), 4);
|
||||
dictresc = chancreate(sizeof(Dictres), 0);
|
||||
mapinit(argv[1]);
|
||||
dictinit(argv[1]);
|
||||
langinit(argv[1]);
|
||||
srvinit();
|
||||
if(proccreate(drawthread, nil, 16384) < 0)
|
||||
die("can't create draw worker");
|
||||
if(proccreate(srvthread, nil, 16384) < 0)
|
||||
die("can't create server worker");
|
||||
if(proccreate(ibusthread, nil, 32768) < 0)
|
||||
die("can't create IBus worker");
|
||||
if(threadcreate(dictthread, nil, 16384) < 0)
|
||||
die("can't create dictionary worker");
|
||||
proccreate(drawthread, nil, 16384);
|
||||
proccreate(srvthread, nil, 16384);
|
||||
proccreate(ibusthread, nil, 32768);
|
||||
display = getenv("DISPLAY");
|
||||
if(display != nil && display[0] != '\0')
|
||||
proccreate(ximthread, nil, 32768);
|
||||
imthread(nil);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ It contains no word rows such as `견출지` or `방학`. `mkhanja` validates t
|
||||
contract and groups rows by their Hangul reading to produce the existing
|
||||
runtime dictionary format. Candidate order follows source order. The source
|
||||
keeps all retained pairs for review; the generated dictionary stores the first
|
||||
32 candidates per reading because that is the engine's lookup limit.
|
||||
128 candidates per reading because that is the engine's lookup limit.
|
||||
|
||||
The source is derived from libhangul release tag `libhangul-0.2.0`. The
|
||||
annotated tag object is `20afc38922e3595ee3ed5b186f2ea05afe663763`, its
|
||||
@@ -78,4 +78,5 @@ rejected; rewrite or omit those rows before import.
|
||||
|
||||
`verifymap.py` checks UTF-8, row structure, unique keys, 64-rune keys and
|
||||
values, canonical candidate spacing, and duplicate dictionary candidates.
|
||||
Pass it the exact `.map` and `.dict` files installed by the build.
|
||||
Pass it the exact `.map` and `.dict` files used for runtime or validation.
|
||||
The normal build does not install map data.
|
||||
|
||||
562
map/hanja.dict
562
map/hanja.dict
@@ -25,87 +25,87 @@
|
||||
;; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
;; POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
가 可 家 加 歌 價 街 假 佳 暇 架 价 賈 伽 柯 迦 軻 嘉 駕 嫁 稼 哥 呵 苛 袈 訶 枷 珂 茄 痂 跏 斝 葭
|
||||
각 各 角 脚 覺 閣 刻 却 珏 恪 殼 慤 榷 骼 桷 礭 搉 卻 躩 咯 埆 擱 塙 阁 愨 覚 礐 殻 胳 傕 崅 茖 佫
|
||||
간 間 干 看 乾 幹 姦 刊 肝 簡 懇 奸 艮 杆 諫 艱 揀 澗 竿 墾 癎 侃 柬 磵 桿 稈 赶 衎 慳 榦 玕 迀 偘
|
||||
갈 渴 葛 鞨 竭 喝 褐 曷 碣 蝎 乫 羯 秸 鶡 楬 噶 蠍 暍 渇 輵 轕 鍻 鹖 圿 嵑 嶱 扴 擖 毼 獦 礍 鞂 騔
|
||||
감 感 減 敢 甘 監 鑑 憾 邯 堪 勘 瞰 紺 柑 疳 坎 戡 鑒 嵌 橄 龕 歛 酣 撼 欿 减 弇 嵁 闞 憨 澉 轗 坩
|
||||
갑 甲 閘 岬 鉀 匣 胛 韐 瞌 帢 㠷 钾 闸 厒 搕 敆 玾 笚 筪 舺 醘 㓣 㕅 㕉 㗐 㧁 㪉 㭱 䐦 䖬 䗘 䘥 䪺
|
||||
가 可 家 加 歌 價 街 假 佳 暇 架 价 賈 伽 柯 迦 軻 嘉 駕 嫁 稼 哥 呵 苛 袈 訶 枷 珂 茄 痂 跏 斝 葭 舸 笳 哿 坷 檟 謌 珈 砢 佉 呿 榎 仮 傢 咖 瘕 耞 宊 椵 叚 価 猳 髂 瘸 鴐 䴥 拁 㤎 榢 毠 溊 牁 豭 槚 诃 贾 轲 镓 驾 鲄 婽 尜 岢 幏 彁 徦 愘 戓 戨 斚 樖 泇 渮 滒 炣 牫 牱 犌 玍 癿 笴 糘 胢 腵 蚵 貑 跒 酠 鉫 鉲 鎵 鎶 魺 鴚 麚 㗎 㚙 㞹 㢦 㤉 㧝 㪃 㪼 㱒 㹢 䂟 䈔 䑝 䔅 䕒 䪪 䯊 䶗 嘏
|
||||
각 各 角 脚 覺 閣 刻 却 珏 恪 殼 慤 榷 骼 桷 礭 搉 卻 躩 咯 埆 擱 塙 阁 愨 覚 礐 殻 胳 傕 崅 茖 佫 墧 斍 腳 隺 㩁 㰌 㴶 壳 悫 搁 觉 愙 捔 斠 櫊 毃 玨 袼 覐 踋 鵅 鵤 㑢 㔖 㕁 㙾 㤩 㱋 㱿 㲉 㾡 䁷 䇶 䐘 䖼 䟩 䧄 䶅
|
||||
간 間 干 看 乾 幹 姦 刊 肝 簡 懇 奸 艮 杆 諫 艱 揀 澗 竿 墾 癎 侃 柬 磵 桿 稈 赶 衎 慳 榦 玕 迀 偘 秆 栞 茛 齦 间 恳 旰 简 㸔 簳 瞯 㵎 趕 暕 盰 覸 顅 芉 囏 涆 貇 鬝 㿻 悭 拣 擀 涧 痫 羟 艰 裥 谏 踬 锏 龈 倝 墹 姧 婜 孯 忓 攼 檊 癇 癷 皯 硍 礀 筸 粯 繝 羥 葌 蕑 蕳 虷 衦 裉 襇 襉 豤 鐗 鐧 靬 馯 鬜 鳱 鶫 㫔 㶕 㶥 㸧 䉍 䓸 䔵 䧲 䫀 䯎 䳚 䵟 矸
|
||||
갈 渴 葛 鞨 竭 喝 褐 曷 碣 蝎 乫 羯 秸 鶡 楬 噶 蠍 暍 渇 輵 轕 鍻 鹖 圿 嵑 嶱 扴 擖 毼 獦 礍 鞂 騔 骱 㐓 㓞 㓤 㠰 㬞 㮖 㵣 㵧 䂒 䅥 䈓
|
||||
감 感 減 敢 甘 監 鑑 憾 邯 堪 勘 瞰 紺 柑 疳 坎 戡 鑒 嵌 橄 龕 歛 酣 撼 欿 减 弇 嵁 闞 憨 澉 轗 坩 埳 泔 淦 矙 鹻 监 瑊 鍳 砍 椷 歁 鹼 堿 塪 惂 撖 汵 蚶 詌 碱 绀 鉴 鳡 鹐 龛 佄 冚 凲 凵 厱 咁 墈 尲 尶 崁 嵅 玪 玵 甝 磡 礛 竷 篏 粓 苷 莰 譼 贑 輡 轁 鑬 魐 鱤 鵮 鹸 㒈 㔋 㔶 㘛 㙳 㜟 㤌 㦑 㪠 㮀 㰸 㺂 㽍 䀍 䃭 䇞 䊻 䌠 䗣 䘓 䛓 䤗 䧩 䲺
|
||||
갑 甲 閘 岬 鉀 匣 胛 韐 瞌 帢 㠷 钾 闸 厒 搕 敆 玾 笚 筪 舺 醘 㓣 㕅 㕉 㗐 㧁 㪉 㭱 䐦 䖬 䗘 䘥 䪺 䫦
|
||||
갓 㖙
|
||||
강 江 强 講 降 康 綱 鋼 姜 剛 疆 岡 彊 崗 慷 腔 薑 糠 舡 絳 襁 羌 堈 畺 鱇 杠 僵 忼 跫 橿 韁 茳 壃
|
||||
개 改 開 個 皆 豈 槪 介 蓋 慨 价 塏 箇 漑 芥 凱 愾 盖 鎧 愷 恝 疥 槩 剴 玠 揩 匃 闓 磕 个 丐 葢 㮣
|
||||
강 江 强 講 降 康 綱 鋼 姜 剛 疆 岡 彊 崗 慷 腔 薑 糠 舡 絳 襁 羌 堈 畺 鱇 杠 僵 忼 跫 橿 韁 茳 壃 扛 釭 殭 矼 繈 穅 罡 羗 羫 傋 豇 強 冈 奋 棡 掆 犺 玒 糨 繮 峕 嵻 鏹 㽘 缰 刚 岗 绛 讲 钢 镪 冮 勥 匟 唴 啌 嗴 囥 堽 夅 嫝 嵹 弜 弶 摃 摾 椌 槺 溬 漒 漮 焵 牨 犅 犟 猐 獇 玜 琷 瓨 畕 疅 砊 碙 礓 筻 粇 糡 繦 翞 耩 膙 葁 蔃 蜣 袶 謽 躿 鎠 鏮 顜 㓻 㔔 㛨 㝩 㟠 㟵 㢜 㧏 㩖 㭃 㭎 㱂 㳾 㹔 㼚
|
||||
개 改 開 個 皆 豈 槪 介 蓋 慨 价 塏 箇 漑 芥 凱 愾 盖 鎧 愷 恝 疥 槩 剴 玠 揩 匃 闓 磕 个 丐 葢 㮣 嘅 喈 湝 祴 礚 暟 蚧 稭 概 烗 䁗 䡷 凯 剀 垲 开 恺 钙 铠 锎 锴 闿 丯 乢 勓 匄 吤 嗰 奒 媘 尬 岕 幆 庎 忋 忦 戤 摡 斺 溉 炌 炏 煯 犗 琾 瓂 砎 祄 絠 腉 蒈 蝔 衸 郂 鈣 鍇 鎎 鐦 阣 颽 魀 魪 鶛 㑘 㕢 㝏 㠹 㨟 㪡 㯼 㱾 㿍 䃈 䇒 䏗 䐩 䐴 䒓 䤤 䪱 䯰 䰺 䱄 䲸
|
||||
객 客 喀 塥 揢 碦 衉 㗆 䘔
|
||||
갯 㖋 㖎
|
||||
갱 更 坑 羹 粳 賡 鏗 硜 秔 鍞 阬 硻 䎴 硁 赓 铿 劥 坈 妔 稉 羮 銵 㧶 㰢 㹴 䃘 䡖 䡠 䡰 䢚
|
||||
갹 醵 蹻 噱 谻 臄 勪 㖸 㷾 䣰
|
||||
거 車 去 居 巨 擧 拒 距 據 渠 倨 醵 遽 据 祛 鉅 炬 踞 鋸 迲 裾 袪 蘧 莒 蕖 秬 籧 駏 腒 苣 胠 筥 佉
|
||||
거 車 去 居 巨 擧 拒 距 據 渠 倨 醵 遽 据 祛 鉅 炬 踞 鋸 迲 裾 袪 蘧 莒 蕖 秬 籧 駏 腒 苣 胠 筥 佉 呿 昛 舉 詎 琚 柜 㨿 㩀 耟 簴 㞐 鐻 璖 虡 弆 磲 阹 椐 璩 佢 俥 壉 岠 怇 懅 涺 鶋 举 拠 挙 榉 讵 钜 键 锯 伡 凥 勮 匷 厺 啹 姖 婮 崌 攑 櫸 欅 歫 洰 淭 澽 粔 紶 翗 艍 菃 蚷 蜛 螶 蟝 衐 襷 詓 豦 躆 鮔 鱋 麮 鼁 﨔 㝒 㠪 㣄 㪯 㫢 㭕 㯫 㰦 䅕 䆽 䒧 䛯 䝣 䝻 䟊 䢹 䱟 䵕 䶙
|
||||
걱 巪
|
||||
건 建 乾 件 健 巾 鍵 虔 腱 愆 蹇 騫 楗 褰 謇 鞬 揵 犍 睷 搴 鶱 䖍 諐 湕 徤 乹 寋 㨜 骞 亁 囝 墘 寈
|
||||
건 建 乾 件 健 巾 鍵 虔 腱 愆 蹇 騫 楗 褰 謇 鞬 揵 犍 睷 搴 鶱 䖍 諐 湕 徤 乹 寋 㨜 骞 亁 囝 墘 寈 弿 攐 攓 旔 榩 毽 漧 瀽 藆 踺 鑳 騝 鰎 鰬 㓺 㔓 㗔 㨴 㩃 㩮 㩷 㸫 䇂 䇟 䊕 䘜 䙭 䞿 䭈 䮿 䢖
|
||||
걸 傑 乞 杰 桀 偈 朅 乬 榤 嵥 㐦 滐 搩 櫭 芞 藒 㘶 㹇 䎢 䒗
|
||||
검 檢 儉 劍 劒 黔 鈐 瞼 撿 芡 岒 臉 劎 俭 倹 剑 剣 捡 检 検 睑 脸 钤 仱 剱 劔 忴 㧄 䇜 䈩 䩎
|
||||
겁 怯 劫 迲 刦 刧 刼 㤼 㥘 袷 跲 昅 抾 极 砝 蜐 衱 鉣 魥 鵖 㭲 㹤 㾀 䀷 䂲 䂶
|
||||
것 唟
|
||||
게 揭 憩 偈 愒 垍 掲 憇
|
||||
격 格 激 擊 隔 檄 膈 覡 翮 骼 闃 鬲 挌 鴃 毄 撃 繳 閴 綌 鵙 愅 滆 薂 諽 击 绤 缂 缴 觋 阒 䴗 呄 嗝
|
||||
견 見 犬 堅 遣 牽 肩 絹 甄 譴 鵑 繭 俔 蠲 畎 縳 狷 繾 筧 羂 樫 鰹 牵 掔 汧 岍 熞 縴 梘 罥 汱 甽 詃
|
||||
결 結 決 潔 缺 訣 拮 抉 闋 觖 絜 玦 鴃 决 鴂 鈌 駃 袺 刔 洯 逫 㓗 结 诀 阕 僪 妜 挗 炔 焆 砄 缼 芵
|
||||
겸 兼 謙 慊 箝 鉗 鎌 歉 蒹 嗛 縑 傔 拑 檶 鹻 黚 鼸 岒 㻩 鶼 缣 谦 钳 鳒 鹣 嵰 槏 羬 膁 趝 鰜 䈤 䈴
|
||||
격 格 激 擊 隔 檄 膈 覡 翮 骼 闃 鬲 挌 鴃 毄 撃 繳 閴 綌 鵙 愅 滆 薂 諽 击 绤 缂 缴 觋 阒 䴗 呄 嗝 垎 墼 搹 搿 敋 楁 欫 湨 燩 犑 狊 獥 礊 緙 臵 蛒 觡 譤 轚 郹 鄓 鞷 鶪 鸄 鼳 㐿 㦘 㦴 㪾 䁶 䂆 䈪 䐙 䙐 䛿 䞦 䠐
|
||||
견 見 犬 堅 遣 牽 肩 絹 甄 譴 鵑 繭 俔 蠲 畎 縳 狷 繾 筧 羂 樫 鰹 牵 掔 汧 岍 熞 縴 梘 罥 汱 甽 詃 趼 鋻 坚 枧 笕 绢 缱 见 谴 钘 鲣 鹃 䌸 儙 幵 悓 挸 掮 撁 枤 涀 犭 猏 獧 瓹 睊 絸 茾 菺 蚈 蜸 蠒 裐 襺 豜 豣 鄄 鈃 銒 鏲 鑓 雃 鳽 鵳 麉 㑠 㝁 㢾 㭴 㯞 㯠 㹂 䄯 䅌 䅐 䋌 䋗 䌑 䑷 䗗 䚈 䚊 䡓 䣺 䪈 䭤 䳌 䵖 䵛 䵡 䵤 䶬
|
||||
결 結 決 潔 缺 訣 拮 抉 闋 觖 絜 玦 鴃 决 鴂 鈌 駃 袺 刔 洯 逫 㓗 结 诀 阕 僪 妜 挗 炔 焆 砄 缼 芵 蒛 蚗 蛪 觼 赽 趹 迼 釨 鐑 魝 㓶 㛃 㭈 㮮 㹟 㼤 䀗 䊽 䍳 䏐 䝌 䤿 䥛 䦬 䦼 鍥
|
||||
겸 兼 謙 慊 箝 鉗 鎌 歉 蒹 嗛 縑 傔 拑 檶 鹻 黚 鼸 岒 㻩 鶼 缣 谦 钳 鳒 鹣 嵰 槏 羬 膁 趝 鰜 䈤 䈴 䊴 䭠 䯡
|
||||
겹 裌 唊 䛟 郟 郏 圶 掐 殎 鵊 䀫 䁍 䩡
|
||||
경 京 敬 輕 經 慶 更 景 庚 驚 耕 競 境 竟 警 鏡 傾 卿 頃 徑 硬 梗 瓊 儆 璟 炅 勁 鯨 磬 頸 莖 脛 憬
|
||||
계 計 界 癸 溪 季 鷄 啓 繼 戒 係 系 階 契 桂 械 繫 悸 谿 稽 棨 誡 蹊 屆 磎 堺 雞 烓 罽 薊 堦 髻 禊
|
||||
고 古 高 告 苦 固 故 考 姑 孤 庫 鼓 稿 枯 顧 雇 賈 皐 錮 拷 痼 辜 膏 叩 股 袴 敲 呱 誥 槁 藁 攷 沽
|
||||
곡 曲 谷 穀 哭 鵠 梏 斛 轂 嚳 縠 觳 槲 糓 榖 毂 牿 瑴 喾 鹄 俈 匤 唂 唃 嘝 浀 濲 瀔 瀫 焀 焅 硞 硲
|
||||
곤 困 坤 昆 棍 袞 崑 梱 鯤 滾 琨 衮 閫 悃 髡 鵾 崐 褌 捆 堃 緄 裍 錕 鶤 齫 鯀 壼 滚 髠 璭 綑 稇 晜
|
||||
경 京 敬 輕 經 慶 更 景 庚 驚 耕 競 境 竟 警 鏡 傾 卿 頃 徑 硬 梗 瓊 儆 璟 炅 勁 鯨 磬 頸 莖 脛 憬 痙 絅 耿 逕 坰 璥 擎 勍 涇 烱 暻 俓 倞 罄 扃 竸 冏 煢 惸 畊 鯁 哽 檠 綆 黥 褧 謦 駉 剄 熲 憼 焭 顈 経 軽 亰 獍 璚 曔 㯳 澃 挭 埛 麖 燝 牼 鞕 囧 藑 㷀 傹 剠 桱 浭 綗 莄 誙 鶊 䪫 径 茎 倾 刭 劲 庆 弪 氢 泾 烃 琼 痉 竞 经 绠 胫 茕 轻 迳 镜 顷 颈 颎 鲠 鲸 鹒 䌹 儝 冂 冋 坕 坙 埂 婛 峺 巠 幜
|
||||
계 計 界 癸 溪 季 鷄 啓 繼 戒 係 系 階 契 桂 械 繫 悸 谿 稽 棨 誡 蹊 屆 磎 堺 雞 烓 罽 薊 堦 髻 禊 筓 綮 稧 瘈 縘 葪 届 継 洎 繋 鶏 笄 悈 紒 㻑 枅 䙆 鸂 啟 卟 呇 晵 暩 檕 甈 瘛 鍥 启 鸡 继 蓟 计 诫 锲 阶 㶉 乩 唘 啔 喺 嗘 峜 嵠 彐 彑 昋 栔 楐 檵 瀱 畍 痵 碶 筀 罊 葜 蓕 蘮 蘻 蟿 諬 銈 鎅 鑙 闙 鯚 鳮 齘 㑧 㒅 㕃 㖑 㛷 㡭 㪈 㬖 㮷 㲅 㳦 㳵 㾵 䀘 䁈 䁉 䋜 䋯 䏿 䚉 䛺 䠏 䡔 䦇 䫔 䭫 䭬 䳶
|
||||
고 古 高 告 苦 固 故 考 姑 孤 庫 鼓 稿 枯 顧 雇 賈 皐 錮 拷 痼 辜 膏 叩 股 袴 敲 呱 誥 槁 藁 攷 沽 蠱 羔 苽 睾 菰 暠 尻 犒 瞽 刳 靠 翺 罟 觚 杲 栲 槀 估 詁 盬 篙 鴣 皋 酤 櫜 糕 胯 郜 羖 槹 鈷 凅 牯 稁 箍 鷱 塙 臯 髙 皷 泒 稾 鼔 吿 䯻 楛 翶 餻 柧 侤 蛄 嵪 笟 羙 胍 搞 㟸 桍 棝 槔 橰 狜 翱 薧 藳 鼛 㛈 库 绔 蛊 裤 诂 诰 轱 钴 铐 锆 锢 顾 鲓 鲴 鸪 僱 勂 叝 咕 哌 圐 堌 夃 婟 嫴 崓 崮 怘 挎
|
||||
곡 曲 谷 穀 哭 鵠 梏 斛 轂 嚳 縠 觳 槲 糓 榖 毂 牿 瑴 喾 鹄 俈 匤 唂 唃 嘝 浀 濲 瀔 瀫 焀 焅 硞 硲 筁 粬 蔛 蛐 螜 逧 髷 﨏 㖆 㪶 㮂 㻃 䀰 䈸 䎥 䐨 䒼 䚛 䢗 䧊 䧼 䳔 䵈 䶜
|
||||
곤 困 坤 昆 棍 袞 崑 梱 鯤 滾 琨 衮 閫 悃 髡 鵾 崐 褌 捆 堃 緄 裍 錕 鶤 齫 鯀 壼 滚 髠 璭 綑 稇 晜 蔉 蓘 蜫 鮌 䃂 壸 绲 裈 辊 锟 阃 鲧 鲲 鹍 丨 堒 尡 惃 捁 棞 涃 潉 焑 熴 猑 瑻 睏 睔 睴 硱 磙 祵 臗 菎 裩 謴 輥 醌 閸 騉 髨 㒭 㙥 㡓 㨰 㫻 㬷 㱎 䎾 䖵 䙛 䜇 䠅 䵪
|
||||
골 骨 滑 汨 鶻 矻 榾 搰 汩 杚 圣 馉 鹘 傦 尳 愲 抇 縎 蓇 顝 餶 㐔 㐣 㒴 㾶 䮩 䯇
|
||||
곱 䯩
|
||||
곳 廤 蒊 㖛 㖜
|
||||
공 工 公 功 空 共 恐 恭 供 貢 孔 攻 控 拱 鞏 珙 蚣 跫 倥 崆 蛩 釭 箜 悾 栱 槓 贛 蛬 龔 筇 㓛 㳟 羾
|
||||
공 工 公 功 空 共 恐 恭 供 貢 孔 攻 控 拱 鞏 珙 蚣 跫 倥 崆 蛩 釭 箜 悾 栱 槓 贛 蛬 龔 筇 㓛 㳟 羾 鞚 廾 邛 笻 㓋 涳 卭 疘 糿 㭟 䂬 巩 贡 赣 龚 埪 塨 幊 愩 慐 拲 桏 灨 熕 硿 碽 篢 糼 舼 蒆 躻 輁 銎 髸 鵼 龏 㒶 㓚 㕬 㣉 㤟 㤨 㧬 㧭 㫒 㮪 㯯 㲁 㸜 㺬 㼦 䂵 䅃 䅝 䇨 䊄 䔈 䜤 䡗 䢼 䣏 䰸 䱋 䲲 䳍
|
||||
곶 串
|
||||
과 果 過 科 課 寡 誇 瓜 戈 菓 顆 袴 蝸 跨 鍋 裹 夸 窠 夥 媧 胯 撾 侉 踝 銙 蝌 騍 倮 堝 猓 稞 姱 薖
|
||||
과 果 過 科 課 寡 誇 瓜 戈 菓 顆 袴 蝸 跨 鍋 裹 夸 窠 夥 媧 胯 撾 侉 踝 銙 蝌 騍 倮 堝 猓 稞 姱 薖 蜾 鈛 檛 剮 堁 淉 荂 輠 剐 埚 挝 课 过 锅 锞 颗 馃 骒 㶽 䯄 冎 咵 坬 垮 嵙 恗 惈 敤 晇 棵 歄 濄 煱 犐 瘑 簻 粿 綶 腂 膼 舿 萪 諣 踻 錁 鐹 餜 骻 髁 㐄 㗻 㚌 㛻 㡁 㧓 㪙 㳀 㳡 㸯 㼫 㽿 㾧 㿆 䆼 䈑 䈖 䌀 䙨 䠸 䣬 䫚 䯞 䴹
|
||||
곽 郭 廓 槨 藿 霍 癨 躩 鞹 椁 矡 漷 钁 崞 瀖 墎 欔 籗 鞟 㗥 㸌 䣤 䦆 䧐
|
||||
관 官 觀 關 館 管 冠 寬 貫 慣 款 灌 串 莞 琯 棺 瓘 罐 菅 梡 顴 盥 祼 錧 綰 丱 鸛 筦 窾 爟 雚 髖 鑵
|
||||
관 官 觀 關 館 管 冠 寬 貫 慣 款 灌 串 莞 琯 棺 瓘 罐 菅 梡 顴 盥 祼 錧 綰 丱 鸛 筦 窾 爟 雚 髖 鑵 涫 輨 舘 関 観 䝺 欵 瘝 寛 毌 倌 窽 闗 礶 躀 䦎 宽 惯 掼 绾 观 贯 颧 馆 髋 鹳 卝 悹 悺 摜 樌 欟 歀 泴 潅 烪 痯 矔 窤 罆 蒄 覌 賗 遦 鏆 鑧 鱹 鳤 㮡 㯘 䏓 䕀 䗆 䗰 䘾 䙮 䤽 䥗 䩪 䪀 䲌 䲘
|
||||
괄 括 檜 刮 适 恝 聒 栝 筈 髺 佸 鴰 鸹 劀 懖 桰 葀 銽 頢 颳 鬠 䒷 䟯 䣶 䦚 䯏 䯺
|
||||
광 光 廣 狂 鑛 匡 曠 壙 胱 筐 洸 侊 珖 炚 絖 誑 纊 恇 獷 磺 桄 茪 框 爌 誆 礦 撗 劻 眶 迋 矌 灮 垙
|
||||
광 光 廣 狂 鑛 匡 曠 壙 胱 筐 洸 侊 珖 炚 絖 誑 纊 恇 獷 磺 桄 茪 框 爌 誆 礦 撗 劻 眶 迋 矌 灮 垙 僙 抂 銧 㼅 圹 広 旷 犷 矿 纩 诓 诳 邝 鉱 俇 儣 匩 哐 姯 懬 懭 昿 洭 炗 炛 烡 砿 硄 穬 筺 絋 臦 臩 軖 軭 輄 逛 邼 鄺 鋛 鵟 黆 黋 㤮 㫕 㫛 㹰 㾠 㿠 䇊 䊯 䒰 䖱 䯑
|
||||
괘 掛 卦 罫 挂 詿 罣 咼 喎 絓 呙 诖 㖞 叧 啩 枴 柺 褂
|
||||
괴 怪 愧 壞 塊 槐 傀 乖 魁 拐 媿 坏 恠 瑰 蒯 闠 廥 襘 璝 壊 蕢 瓌 烠 凷 欳 膭 块 蒉 墤 巜 旝 晆 櫆
|
||||
괴 怪 愧 壞 塊 槐 傀 乖 魁 拐 媿 坏 恠 瑰 蒯 闠 廥 襘 璝 壊 蕢 瓌 烠 凷 欳 膭 块 蒉 墤 巜 旝 晆 櫆 癐 箉 糩 蘾 謉 㔞 㕟 㘨 㙕 㚍 㧔 㻁 㾩 䂯 䂷 䃶 䈐 䈛 䓒 䓙 䙌 䯣 䯤
|
||||
괵 馘 虢 㶁 蟈 幗 帼 掴 蝈 剨 啯 嘓 慖 摑 漍 聝 腘 膕 䂸 䬎
|
||||
굉 宏 肱 轟 紘 閎 觥 黌 訇 浤 硡 鍠 鍧 汯 翃 翝 纮 轰 锽 闳 厷 咣 渹 竤 綋 耾 觵 軣 錓 鞃 㚚 䆖 䡌
|
||||
교 校 交 敎 橋 巧 較 郊 矯 膠 絞 僑 嬌 攪 酵 驕 喬 轎 狡 皎 蛟 咬 嶠 翹 鮫 蕎 餃 徼 榷 嘐 嚙 磽 鉸
|
||||
구 九 口 求 句 救 究 久 舊 具 苟 俱 區 丘 拘 球 構 懼 驅 狗 龜 邱 歐 鳩 購 鷗 玖 寇 仇 矩 溝 舅 衢
|
||||
국 國 局 菊 鞠 鞫 麴 匊 跼 掬 麯 国 囯 踘 閰 囗 椈 鵴 锔 侷 囶 囻 圀 婅 巈 挶 梮 檋 毩 毱 泦 淗 焗
|
||||
군 軍 君 郡 群 窘 裙 捃 皸 桾 羣 頵 涒 军 皲 鲪 侰 僒 宭 帬 攈 攟 珺 皹 莙 蜠 裠 輑 鍕 鮶 鵘 㖥 㪊
|
||||
굴 屈 窟 掘 堀 崛 詘 矻 倔 淈 朏 胐 诎 崫 弡 煀 虳 袦 誳 趉 镼 鶌 㐇 㐝 㩿 㪂 㭾 㵠 㻕 䂗 䓛 䖦 䘿
|
||||
굉 宏 肱 轟 紘 閎 觥 黌 訇 浤 硡 鍠 鍧 汯 翃 翝 纮 轰 锽 闳 厷 咣 渹 竤 綋 耾 觵 軣 錓 鞃 㚚 䆖 䡌 䡏 䧀 䩑 䵃
|
||||
교 校 交 敎 橋 巧 較 郊 矯 膠 絞 僑 嬌 攪 酵 驕 喬 轎 狡 皎 蛟 咬 嶠 翹 鮫 蕎 餃 徼 榷 嘐 嚙 磽 鉸 姣 窖 蹻 佼 趫 撟 噭 憍 晈 嘄 暞 橇 骹 鵁 齩 教 皦 挍 轇 屩 嵺 鄥 恔 敫 珓 穚 茭 蟜 譑 儌 墽 漖 獟 礉 窌 簥 跤 蹺 鞽 頝 鷮 㝯 㶀 䮦 啮 乔 侨 娇 峤 挢 搅 桥 矫 硗 绞 翘 荞 跷 轿 较 邬 铰 鞒 饺 骄 鲛 䴔 丂 啋 嘂 嘦 嘺 噛 嬓 孂 屫 峧 撹 撽 敽 敿 曒 槗 滘 灚 烄 燆 犞 璬 硚 礄 繑 纐 臫 荍 虠
|
||||
구 九 口 求 句 救 究 久 舊 具 苟 俱 區 丘 拘 球 構 懼 驅 狗 龜 邱 歐 鳩 購 鷗 玖 寇 仇 矩 溝 舅 衢 軀 鉤 廐 柩 灸 謳 垢 駒 嘔 臼 嶇 毆 枸 咎 耉 勾 毬 坵 晷 瞿 逑 絿 柾 銶 搆 扣 裘 詬 疚 遘 媾 屨 嫗 劬 捄 駈 甌 摳 窶 彀 颶 漚 韭 鸜 傴 榘 糗 蚯 覯 癯 韝 姤 匶 璆 釦 鷇 俅 佝 岣 昫 篝 胊 坸 冓 佉 傋 厹 叴 戵 痀 窛 蒟 韮 鬮 惧 旧 厩 乆 敺 廏 耇 沟 臞 緱 玽 雊 耈 呴 䪷 倶 賕 亀 冦 㡱 抅 氍
|
||||
국 國 局 菊 鞠 鞫 麴 匊 跼 掬 麯 国 囯 踘 閰 囗 椈 鵴 锔 侷 囶 囻 圀 婅 巈 挶 梮 檋 毩 毱 泦 淗 焗 粷 蘜 諊 趜 躹 輂 鋦 陱 駶 驧 㘲 㥌 㩴 㹼 㽤 䆐 䋰 䎤 䏱 䕮 䗇 䜯 䡞 䪕 䱡
|
||||
군 軍 君 郡 群 窘 裙 捃 皸 桾 羣 頵 涒 军 皲 鲪 侰 僒 宭 帬 攈 攟 珺 皹 莙 蜠 裠 輑 鍕 鮶 鵘 㖥 㪊 㴫 㿏 䝍 䭽
|
||||
굴 屈 窟 掘 堀 崛 詘 矻 倔 淈 朏 胐 诎 崫 弡 煀 虳 袦 誳 趉 镼 鶌 㐇 㐝 㩿 㪂 㭾 㵠 㻕 䂗 䓛 䖦 䘿 䞷 䠇
|
||||
굿 㖌
|
||||
궁 弓 宮 窮 躬 穹 芎 躳 宫 穷 藭 匔 杛 竆 㑋 匑 宆 焪 熍 㴦 䓖 䛪 䠻 䤝
|
||||
궉 鴌
|
||||
권 權 卷 勸 券 拳 圈 眷 倦 捲 淃 惓 綣 睠 蜷 棬 勌 勧 劵 巻 踡 鬈 帣 䄅 劝 权 権 绻 锩 勬 啳 圏 埢
|
||||
권 權 卷 勸 券 拳 圈 眷 倦 捲 淃 惓 綣 睠 蜷 棬 勌 勧 劵 巻 踡 鬈 帣 䄅 劝 权 権 绻 锩 勬 啳 圏 埢 奆 婘 孉 巏 慻 搼 桊 椦 牶 犈 箞 絭 菤 蔨 虇 蠸 裷 觠 錈 韏 飬 餋 鬳 齤 㒽 㟡 㢧 㩲 㪻 㬺 䄐 䅚 䊎 䌯 䑏 䖭 䟒 䠰
|
||||
궐 厥 闕 蹶 蕨 獗 劂 撅 橛 蹷 橜 鱖 欮 灍 蟨 鷢 镢 阙 鳜 亅 噘 孒 孓 嶡 嶥 憠 氒 瘚 蟩 鐝 㙭 㵐 䙠
|
||||
궤 軌 蹶 潰 机 詭 櫃 饋 几 跪 匱 樻 簋 餽 憒 劂 繢 闠 佹 氿 麂 撅 簣 䡄 匭 㧪 垝 觤 鐀 陒 馈 匦 匮
|
||||
귀 貴 句 歸 鬼 龜 晷 巋 帰 䢜 宄 瞶 劌 撌 㱕 嘳 尯 腃 蘬 騩 䝿 刿 归 瞆 贵 椢 槶 櫷 皈 聭 㸵 䁛 䌆
|
||||
규 規 奎 圭 糾 叫 揆 珪 閨 窺 葵 逵 硅 繆 畦 竅 赳 槻 虬 跬 睽 虯 闚 樛 巋 糺 楏 刲 嬀 潙 暌 頍 馗
|
||||
궤 軌 蹶 潰 机 詭 櫃 饋 几 跪 匱 樻 簋 餽 憒 劂 繢 闠 佹 氿 麂 撅 簣 䡄 匭 㧪 垝 觤 鐀 陒 馈 匦 匮 愦 溃 篑 缋 诡 轨 阓 㱮 凧 厬 圚 姽 嬇 恑 攱 殨 祪 簂 籄 蔮 蛫 鑎 鞼 鱥 㔲 㔳 㙺 㞦 䃽 䅪 䍯 䕚 䠩 䣀 䣒 䤥
|
||||
귀 貴 句 歸 鬼 龜 晷 巋 帰 䢜 宄 瞶 劌 撌 㱕 嘳 尯 腃 蘬 騩 䝿 刿 归 瞆 贵 椢 槶 櫷 皈 聭 㸵 䁛 䌆 䙡 䞈 䰎 䤥
|
||||
규 規 奎 圭 糾 叫 揆 珪 閨 窺 葵 逵 硅 繆 畦 竅 赳 槻 虬 跬 睽 虯 闚 樛 巋 糺 楏 刲 嬀 潙 暌 頍 馗 邽 鮭 嫢 呌 䂓 戣 煃 摎 槼 恘 芤 頯 妫 沩 岿 窍 窥 纠 规 闺 乣 喹 媯 恷 摫 朻 椝 楑 湀 溈 犪 玌 瞡 窐 紤 聧 胿 芁 茥 藈 蝰 袿 訆 蹞 郌 鄈 鍨 鍷 頄 鬶 鬹 黊 鼼 㙓 㧃 㨒 䅅 䆻 䖯 䙵 䞨 䟸 䠑 䤆 䯓 䲅 䳏 䳫 䶯
|
||||
균 均 菌 龜 鈞 筠 勻 畇 囷 麏 匀 稛 麕 袀 钧 呁 峮 抣 汮 碅 箘 箟 蚐 覠 銁 銞 麇 㚬 㟒 䇹 䐃
|
||||
귤 橘 蹫 䤎 䰬
|
||||
극 極 克 劇 隙 棘 戟 剋 亟 屐 郄 尅 殛 郤 㦸 茍 鋴 隟 㞃 蕀 㥛 剧 丮 勀 勊 娔 撠 橶 氪 裓 襋 㘌 㳳
|
||||
근 近 根 勤 謹 斤 僅 筋 饉 瑾 槿 覲 懃 芹 菫 劤 跟 靳 卺 蘄 厪 觔 墐 巹 釿 廑 漌 堇 㘦 殣 慬 斳 䈽
|
||||
극 極 克 劇 隙 棘 戟 剋 亟 屐 郄 尅 殛 郤 㦸 茍 鋴 隟 㞃 蕀 㥛 剧 丮 勀 勊 娔 撠 橶 氪 裓 襋 㘌 㳳 㻷 䓧 䧍 䩯 䪂
|
||||
근 近 根 勤 謹 斤 僅 筋 饉 瑾 槿 覲 懃 芹 菫 劤 跟 靳 卺 蘄 厪 觔 墐 巹 釿 廑 漌 堇 㘦 殣 慬 斳 䈽 觐 谨 馑 伒 哏 嫤 懄 歏 瘽 荕 菦 蓳 螼 赾 馸 㝻 㞬 㢙 㧆 㨷 㮗 㹏 䈥 䌍 䒺 䤺
|
||||
글 契 訖 㐎 㔕 䞘 䰴
|
||||
금 金 今 禁 錦 禽 琴 襟 衾 擒 衿 鈐 芩 檎 昑 妗 噤 嶔 笒 唫 黅 紟 琹 顉 菳 赺 㯲 嵚 钅 锦 僸 凎 凚
|
||||
금 金 今 禁 錦 禽 琴 襟 衾 擒 衿 鈐 芩 檎 昑 妗 噤 嶔 笒 唫 黅 紟 琹 顉 菳 赺 㯲 嵚 钅 锦 僸 凎 凚 噙 坅 庈 惍 扲 捦 搇 澿 珡 琻 砛 耹 肣 蚙 蠄 軡 釒 鈙 鍂 鐢 雂 靲 鳹 鵭 齽 㕋 㦗 㩒 㪁 㱈 㱽 㲐 㾣 䃢 䋮 䌝 䑤 䔷 䘳 䥅 䥆 䦦 䫴 䶖
|
||||
급 給 急 及 級 汲 扱 伋 岌 皀 笈 礏 圾 芨 彶 级 给 喼 嬆 忣 礘 觙 鷑 﨤 㤂 㧀 㬤 㽺 䏜 䏠 䲯
|
||||
긍 肯 兢 矜 亘 亙 殑 矝 堩 絚 緪 肎 刯 奟 掯 揯 搄 縆 肻 褃 錹 鹶 㥤 㮓 䱍 䱎 䱭 䱴
|
||||
기 己 記 氣 技 起 期 基 其 旣 幾 豈 器 奇 機 旗 紀 棄 忌 企 欺 飢 祈 乞 騎 寄 畿 冀 箕 耆 岐 沂 驥
|
||||
기 己 記 氣 技 起 期 基 其 旣 幾 豈 器 奇 機 旗 紀 棄 忌 企 欺 飢 祈 乞 騎 寄 畿 冀 箕 耆 岐 沂 驥 琦 麒 璣 琪 騏 淇 汽 譏 妓 羈 机 嗜 綺 杞 畸 朞 肌 棋 伎 崎 枳 圻 饑 祇 夔 祁 磯 祺 碁 錡 玘 璂 埼 几 覬 耭 錤 歧 弃 曁 旂 欹 頎 墍 棊 鬐 蟣 跂 剞 掎 蘄 攲 芰 猉 綦 忮 屺 愭 芪 炁 羇 覉 僛 庋 禨 肵 虁 暣 綥 蜝 鰭 隑 黖 芑 竒 覊 㡬 旡 惎 跽 鞿 怾 碕 踦 丌 徛 既 禥 蘷 衹 倛 庪 懻 暨 榿 㙨
|
||||
긴 緊 紧 菣
|
||||
길 吉 拮 佶 桔 姞 鴶 蛣 洁 趌 鲒 咭 狤 郆 銡 鞊 鮚 㐞 㐟 㣟 䓀 䟌
|
||||
김 金
|
||||
끗 㖝
|
||||
끝 唜
|
||||
끽 喫 噄
|
||||
나 羅 那 奈 拿 裸 懶 儺 懦 螺 拏 邏 癩 蘿 喇 娜 挐 挪 糯 梛 糥 郍 砈 穤 傩 镎 乸 哪 嗱 懧 橠 稬 蒘
|
||||
나 羅 那 奈 拿 裸 懶 儺 懦 螺 拏 邏 癩 蘿 喇 娜 挐 挪 糯 梛 糥 郍 砈 穤 傩 镎 乸 哪 嗱 懧 橠 稬 蒘 誽 鎿 㑚 㔮 㖠 㡅 㧱 㰙 䛔 柰 䏧
|
||||
낙 落 樂 諾 洛 駱 烙 酪 珞 诺 掿
|
||||
난 難 暖 卵 亂 蘭 欄 爛 鸞 煖 赧 煗 偄 餪 㬉 难 奻 戁 渜 㫱 㬮 䎡 愞
|
||||
날 捺 捏 涅 陧 苶 䖓 䖧
|
||||
남 南 男 濫 藍 嵐 楠 枏 湳 襤 喃 柟 娚 揇 㑲 侽 婻 暔 煵 畘 罱 腩 莮 萳 蝻 諵 遖 㓓 㣮 㭷 㵜 㽖 䈒
|
||||
남 南 男 濫 藍 嵐 楠 枏 湳 襤 喃 柟 娚 揇 㑲 侽 婻 暔 煵 畘 罱 腩 莮 萳 蝻 諵 遖 㓓 㣮 㭷 㵜 㽖 䈒 䊖
|
||||
납 納 拉 臘 蠟 衲 蒳 鈉 纳 钠 妠 笝 軜 靹 魶 㨥 䈫 䪏
|
||||
낭 浪 娘 廊 狼 囊 朗 郎 曩 嚢 乪 儾 囔 擃 攮 欜 灢 饢 齉 㚂 㶞 䁸
|
||||
내 內 來 乃 耐 奈 柰 迺 鼐 内 匂 嬭 奶 廼 㮏 褦 㮈 撆 耏 釢 孻 摨 氖 氝 渿 熋 疓 萘 螚 錼 㐻 㚷 㜨
|
||||
내 內 來 乃 耐 奈 柰 迺 鼐 内 匂 嬭 奶 廼 㮏 褦 㮈 撆 耏 釢 孻 摨 氖 氝 渿 熋 疓 萘 螚 錼 㐻 㚷 㜨 㭆 㲡 㴎 㾍 䯮 䱞 䲎
|
||||
냉 冷
|
||||
냑 蹃 逽
|
||||
냥 娘 孃
|
||||
@@ -115,19 +115,19 @@
|
||||
녈 涅 圼 篞 夨 㖏 㘿 䂼
|
||||
념 念 恬 拈 捻 鮎 埝 焾 艌 踗 㑫 䧔 䬯
|
||||
녑 鑷 喦 惗 䳖 帇 幉 敜 鈢 鉩 錜 㚔 㩶 㵊 䇣 䌰 䎎
|
||||
녕 寧 獰 寗 佞 濘 嚀 儜 侫 寕 寜 甯 擰 聹 咛 拧 聍 嬣 寍 橣 檸 澝 矃 薴 鑏 鬡 鸋 㝕 㣷 㲌 㲰 㿦 䆨
|
||||
녕 寧 獰 寗 佞 濘 嚀 儜 侫 寕 寜 甯 擰 聹 咛 拧 聍 嬣 寍 橣 檸 澝 矃 薴 鑏 鬡 鸋 㝕 㣷 㲌 㲰 㿦 䆨 䔭 䗿 䭢
|
||||
녘 榒
|
||||
녜 禰 祢 䄲
|
||||
노 老 路 勞 怒 露 奴 努 盧 魯 爐 駑 蘆 鷺 虜 擄 弩 帑 櫓 瑙 孥 呶 猱 笯 臑 峱 砮 巙 怓 鑥 镥 驽 䜧
|
||||
노 老 路 勞 怒 露 奴 努 盧 魯 爐 駑 蘆 鷺 虜 擄 弩 帑 櫓 瑙 孥 呶 猱 笯 臑 峱 砮 巙 怓 鑥 镥 驽 䜧 伖 伮 夒 巎 獿 粩 胬 蛯 詉 鴑 㚢 㞪 㺁 㺜 䛝 䜀
|
||||
녹 綠 錄 祿 鹿 碌 菉 傉 搙
|
||||
논 論 嫩 㯎
|
||||
놀 㐐 㐗
|
||||
놈 㖈
|
||||
농 農 弄 籠 濃 膿 聾 壟 穠 儂 醲 噥 禯 癑 侬 农 哝 浓 秾 脓 㶶 嶩 檂 欁 燶 繷 蕽 襛 辳 鬞 齈 䙶 䢉
|
||||
농 農 弄 籠 濃 膿 聾 壟 穠 儂 醲 噥 禯 癑 侬 农 哝 浓 秾 脓 㶶 嶩 檂 欁 燶 繷 蕽 襛 辳 鬞 齈 䙶 䢉 䵜
|
||||
놔 雫
|
||||
놜 豽 貀
|
||||
뇌 雷 腦 惱 賂 牢 磊 餒 餧 脳 挼 憹 垴 恼 悩 脑 馁 匘 堖 娞 捼 碯 腇 鮾 鯘 㑎 㛴 㼏
|
||||
뇨 溺 尿 撓 鬧 橈 嫋 裊 淖 鐃 嬲 嬈 磠 閙 褭 譊 䙚 袅 铙 闹 嫐 嬝 硇 硵 脲 㒟 㜵 㞙 㠡 㳮 䃩 䃵 䦊
|
||||
뇨 溺 尿 撓 鬧 橈 嫋 裊 淖 鐃 嬲 嬈 磠 閙 褭 譊 䙚 袅 铙 闹 嫐 嬝 硇 硵 脲 㒟 㜵 㞙 㠡 㳮 䃩 䃵 䦊 䮍 䴃
|
||||
누 樓 漏 淚 累 屢 陋 壘 縷 耨 啂 㝅 鎒 槈 檽 獳 羺 譨 譳 鐞 㜌 㝹 䅶 䨲
|
||||
눈 嫩 媆 嫰 㜛 㡪 㶧
|
||||
눌 訥 吶 肭 呐 讷 抐 㕯 䟜
|
||||
@@ -137,288 +137,288 @@
|
||||
늠 凜
|
||||
능 能 陵 凌 綾 菱 稜 䏻 㴰 䘅
|
||||
늦 莻
|
||||
니 泥 尼 禰 怩 膩 呢 柅 祢 你 旎 埮 濔 臡 妳 檷 蚭 迡 腻 铌 伱 倷 坭 妮 屔 您 抳 狔 秜 胒 苨 跜 鈮
|
||||
니 泥 尼 禰 怩 膩 呢 柅 祢 你 旎 埮 濔 臡 妳 檷 蚭 迡 腻 铌 伱 倷 坭 妮 屔 您 抳 狔 秜 胒 苨 跜 鈮 鉨 鑈 隬 馜 㞾 㦐 䕥 䘦 䛏 䝚
|
||||
닉 溺 匿 搦 嫟 糑 嬺 愵 搻 氼 㥾 㲻
|
||||
닐 昵 暱 䁥 痆 䘌 䵑 䵒
|
||||
님 拰 䋻 䌾
|
||||
닙 㘝 囡
|
||||
다 多 茶 爹 嗲 夛 茤 觰 跢 鯺 䫂 䯬
|
||||
단 單 短 端 但 丹 斷 段 團 壇 檀 旦 湍 鍛 緞 簞 蛋 亶 蓴 袒 彖 鄲 坍 担 胆 椴 慱 漙 靼 癉 蜑 耑 澶
|
||||
달 達 撻 疸 獺 澾 怛 闥 韃 靼 妲 㺚 逹 橽 鐽 呾 燵 㼀 炟 狚 哒 挞 荙 跶 闼 鞑 噠 垯 墶 笪 繨 羍 薘
|
||||
담 談 擔 淡 潭 膽 譚 痰 澹 曇 憺 湛 聃 覃 啖 蕁 坍 錟 担 胆 墰 禫 啗 毯 郯 噉 儋 薝 黮 壜 忐 罎 黵
|
||||
답 答 踏 畓 遝 沓 荅 剳 蹋 涾 溚 褡 譶 匒 崉 榙 濌 畣 眔 褟 誻 蹹 躢 鎉 龖 龘 㙮 㜓 㟷 㧺 㭼 㯚 㳫
|
||||
당 堂 當 唐 黨 糖 塘 撞 棠 螳 幢 戇 倘 儻 党 讜 搪 璫 瑭 瞠 鐺 襠 鏜 溏 餳 檔 礑 蟷 餹 螗 簹 戅 嵣
|
||||
대 大 代 待 對 帶 隊 貸 臺 台 戴 垈 袋 擡 岱 黛 玳 錞 坮 懟 碓 儓 汏 隶 旲 鐓 㐲 䑓 帯 帒 憝 襨 濧
|
||||
단 單 短 端 但 丹 斷 段 團 壇 檀 旦 湍 鍛 緞 簞 蛋 亶 蓴 袒 彖 鄲 坍 担 胆 椴 慱 漙 靼 癉 蜑 耑 澶 腶 断 单 摶 槫 泹 襌 㫁 瑖 猯 褖 鏄 偳 潬 煓 毈 鴠 㫜 团 坛 钽 単 団 抟 瘅 箪 簖 缎 郸 锻 刐 剬 勯 匰 唌 塅 媏 枬 柦 檲 湪 煅 玬 璮 疍 砃 碫 籪 糰 繵 葮 袇 褍 褝 襢 觛 貒 貚 踹 躖 鉭 鍴 駳 驙 鶨 鷒 鷤 鷻 㟨 㠆 㡺 㣋 㣶 㩛 㱭 䃪 䉡 䊜 䐷 䒟 䕊 䜝 䜥 䝎 䞡 䠪 䢷 䦔 䩥 䵎
|
||||
달 達 撻 疸 獺 澾 怛 闥 韃 靼 妲 㺚 逹 橽 鐽 呾 燵 㼀 炟 狚 哒 挞 荙 跶 闼 鞑 噠 垯 墶 笪 繨 羍 薘 蟽 詚 躂 㒓 㣵 㳠 㿹 䃮 䵣
|
||||
담 談 擔 淡 潭 膽 譚 痰 澹 曇 憺 湛 聃 覃 啖 蕁 坍 錟 担 胆 墰 禫 啗 毯 郯 噉 儋 薝 黮 壜 忐 罎 黵 惔 耼 聸 紞 倓 甔 萏 䃫 橝 窞 餤 㗖 埿 澸 罈 腅 菼 蟫 霮 顃 驔 髧 䨵 荨 谈 谭 锬 偡 啿 嘾 嚪 墵 妉 姏 婒 媅 帎 憛 曋 氮 瓭 癚 緂 舑 舕 藫 衴 贉 醈 醰 饏 馾 黕 㕪 㘱 㚮 㜤 㲜 㲭 㲷 㳩 㴂 㻼 㽎 㽑 䆦 䆱 䉞 䊤 䐺 䑙 䔜 䗊 䜖 䨢 䭛 埮 炎
|
||||
답 答 踏 畓 遝 沓 荅 剳 蹋 涾 溚 褡 譶 匒 崉 榙 濌 畣 眔 褟 誻 蹹 躢 鎉 龖 龘 㙮 㜓 㟷 㧺 㭼 㯚 㳫 㾑 㿯 㿴 䂿 䈋 䌋 䍇 䍝 䎓 䐛 䓠 䜚 䠌 䪚 䳴 䵬 䶀 䶁
|
||||
당 堂 當 唐 黨 糖 塘 撞 棠 螳 幢 戇 倘 儻 党 讜 搪 璫 瑭 瞠 鐺 襠 鏜 溏 餳 檔 礑 蟷 餹 螗 簹 戅 嵣 戃 䅯 儅 嘡 噇 摚 曭 澢 煻 膛 钂 闣 鞺 当 傥 垱 戆 挡 档 珰 筜 裆 谠 铛 镋 镗 饧 㓥 傏 凼 劏 啺 噹 坣 壋 憆 擋 攩 榶 欓 氹 漟 灙 爣 瓽 瞊 矘 磄 禟 篖 糃 糛 耥 膅 艡 蓎 譡 赯 蹚 躺 鄌 醣 鎕 鎲 闛 隚 饄 鶶 鼞 㑽 㙶 㜍 㜭 㭻 㲥 㼕 㼺 㽆 㿩 䃥 䉎 䌅 䕋 䚒 䣊 䣘 䣣 䧜 䭚
|
||||
대 大 代 待 對 帶 隊 貸 臺 台 戴 垈 袋 擡 岱 黛 玳 錞 坮 懟 碓 儓 汏 隶 旲 鐓 㐲 䑓 帯 帒 憝 襨 濧 薱 襶 㙜 檯 祋 薹 蹛 㘆 㭖 䒫 䨴 对 対 带 怼 贷 轪 镦 队 亣 嬯 廗 憞 懛 柋 瀩 瀻 瑇 甙 簤 籉 艜 譈 譵 軑 軚 轛 鐜 鮘 黱 㒗 㓫 㙵 㚎 㛿 㝳 㞭 㠚 㣕 㦠 㬃 㬣 㯂 㳔 㷘 㸀 㻖 䃍 䈆 䙊 䚞 䚟 䯟 䲦 抬
|
||||
댁 宅
|
||||
덕 德 悳 徳 恴 惪 㤫 㥁
|
||||
도 刀 道 度 圖 島 都 到 徒 逃 導 渡 盜 途 倒 桃 跳 陶 塗 稻 挑 禱 悼 燾 濤 蹈 屠 滔 睹 鍍 掉 堵 兜
|
||||
독 讀 獨 督 毒 篤 瀆 禿 牘 犢 纛 黷 櫝 髑 独 讟 秃 凟 匵 殰 蝳 錖 韣 椟 渎 牍 犊 笃 読 读 黩 㱩 嬻
|
||||
돈 敦 豚 頓 惇 沌 燉 褪 遯 旽 暾 墩 焞 潡 噸 弴 瓲 躉 鐓 炖 橔 獤 㬿 㹠 㻻 礅 伅 囤 墪 忳 扥 撉 趸
|
||||
도 刀 道 度 圖 島 都 到 徒 逃 導 渡 盜 途 倒 桃 跳 陶 塗 稻 挑 禱 悼 燾 濤 蹈 屠 滔 睹 鍍 掉 堵 兜 搗 萄 賭 淘 覩 棹 韜 嶋 櫂 叨 菟 鼗 擣 饕 涂 絛 杲 闍 忉 弢 稌 鞱 搯 檮 慆 壔 洮 掏 酴 鞀 盗 謟 迯 禂 荼 翿 䆃 图 熖 舠 夲 幍 咷 釖 綯 裪 㨶 朷 縚 噵 鞉 䵚 唋 捈 暏 槝 瘏 衜 跿 鳭 䂽 䄻 捣 図 导 岛 梼 涛 焘 祷 稲 绹 赌 镀 阇 韬 鱽 凃 刂 匋 啕 喥 嘟 圗 嫍 屶 峹 嵞 嶀 嶌 嶹 帾 庩 廜 捯 旫
|
||||
독 讀 獨 督 毒 篤 瀆 禿 牘 犢 纛 黷 櫝 髑 独 讟 秃 凟 匵 殰 蝳 錖 韣 椟 渎 牍 犊 笃 読 读 黩 㱩 嬻 涋 涜 瓄 痜 皾 碡 裻 豄 贕 鋵 鑟 韇 韥 騳 鵚 㒔 㞘 㸿 㾄 䄣 䈞 䐁 䓯 䛢 䢱 䫳 䮷
|
||||
돈 敦 豚 頓 惇 沌 燉 褪 遯 旽 暾 墩 焞 潡 噸 弴 瓲 躉 鐓 炖 橔 獤 㬿 㹠 㻻 礅 伅 囤 墪 忳 扥 撉 趸 顿 饨 鲀 崸 庉 扽 撴 朜 犜 砘 豘 蹾 軘 逇 韕 飩 驐 魨 黗 㢯 㥫 㩐 㪟 䃦 䔻 䪃 䵊 䵍
|
||||
돌 突 乭 咄 堗 宊 腯 湥 揬 柮 㐑 㟮 㻠 饳 嚉 怢 葖 迌 鈯 鍎 飿 鶟 鼵 㷝 䃐 䠈
|
||||
돗 㘏
|
||||
동 同 東 冬 動 洞 童 銅 凍 董 桐 棟 憧 疼 瞳 胴 仝 潼 錬 僮 蕫 彤 侗 峒 佟 蝀 涷 垌 朣 橦 艟 茼 哃
|
||||
두 頭 讀 斗 豆 杜 兜 痘 竇 逗 荳 枓 斁 蠹 肚 陡 蚪 抖 脰 吋 紏 蠧 餖 㞳 㪷 兠 㺶 阧 﨣 䇺 䢏 䲧 头
|
||||
동 同 東 冬 動 洞 童 銅 凍 董 桐 棟 憧 疼 瞳 胴 仝 潼 錬 僮 蕫 彤 侗 峒 佟 蝀 涷 垌 朣 橦 艟 茼 哃 苳 鮗 諌 働 埬 曈 㨂 穜 詷 膧 㠉 挏 烔 絧 餇 懂 湩 獞 罿 鮦 鼕 岽 东 冻 动 栋 胨 铜 鲖 鸫 倲 偅 勭 咚 墥 姛 娻 嬞 峂 峝 崠 崬 庝 戙 昸 晍 氃 氡 氭 浵 炵 燑 犝 狪 眮 砼 硐 秱 笗 箽 粡 腖 菄 蘔 蚒 衕 赨 迵 酮 鉖 鉵 霘 駧 鯟 鶇 㑈 㓊 㖦 㗢 㚵 㠽 㢥 㣠 㤏 㫡 㯵 㲇 㷲 㸗 㼧 㼯 㼿 䂈 䂢 䆚 䍶
|
||||
두 頭 讀 斗 豆 杜 兜 痘 竇 逗 荳 枓 斁 蠹 肚 陡 蚪 抖 脰 吋 紏 蠧 餖 㞳 㪷 兠 㺶 阧 﨣 䇺 䢏 䲧 头 窦 钭 饾 亠 侸 剅 吺 唗 唞 梪 橷 殬 毭 浢 篼 荰 蔸 螙 郖 酘 鈄 鋀 閗 鬥 㓱 㛒 㟕 㢄 㨮 㰯 㿡 䄈 䇆 䕆 䕱 䚵 䬦 䱏 䵉
|
||||
둑 㪲
|
||||
둔 鈍 屯 遁 臀 芚 遯 窀 迍 吨 钝 坉 臋 踲 霕 㩔 㼊 䜳 䤜
|
||||
둘 乧 㐈 㐙
|
||||
둣 㖍
|
||||
둥 㪳
|
||||
득 得 淂 嘚 锝 棏 鍀 㝶 䙷 䙸
|
||||
등 等 登 燈 騰 藤 鄧 嶝 謄 橙 滕 鐙 灯 磴 縢 螣 墱 凳 籐 䓁 蹬 䒭 簦 豋 驣 㽅 籘 膯 隥 㔁 腾 誊 邓
|
||||
등 等 登 燈 騰 藤 鄧 嶝 謄 橙 滕 鐙 灯 磴 縢 螣 墱 凳 籐 䓁 蹬 䒭 簦 豋 驣 㽅 籘 膯 隥 㔁 腾 誊 邓 镫 僜 儯 噔 嬁 幐 戥 櫈 漛 璒 竳 艠 虅 覴 邆 霯 駦 鰧 鼟 㲪 䒅 䔲 䕨 䗳 䙞 䠬 䠮 䮴 䲍 䲢 䳾
|
||||
똥 㖯 㖰
|
||||
뜰 㐢
|
||||
라 羅 裸 懶 螺 拏 邏 癩 蘿 喇 騾 囉 鑼 瘰 臝 砢 倮 曪 驘 覼 蠃 灕 覶 玀 躶 㞅 纙 㼈 䇔 罗 懒 椤 猡
|
||||
라 羅 裸 懶 螺 拏 邏 癩 蘿 喇 騾 囉 鑼 瘰 臝 砢 倮 曪 驘 覼 蠃 灕 覶 玀 躶 㞅 纙 㼈 䇔 罗 懒 椤 猡 癞 箩 脶 萝 逻 锣 镙 骡 㑩 亽 儸 剆 啰 嚹 囖 摞 攞 攭 旯 欏 籮 罖 腡 蓏 覙 鎯 鏍 饠 鸁 㒩 㦬 㩡 㰁 㱻 㽋 㿚 䉓 䊨 䌱 䌴 䯁
|
||||
락 落 樂 諾 絡 洛 駱 烙 酪 珞 犖 嗠 硌 濼 鉻 雒 泺 络 荦 铬 饹 骆 峈 笿 餎 鮥 鴼 㓢 㦡 䀩 䈷 䎊
|
||||
란 丹 卵 亂 蘭 欄 爛 鸞 懶 瀾 欒 闌 鑾 攔 襴 嬾 幱 灓 乱 䦨 滦 灤 斕 圝 圞 灡 籣 兰 拦 斓 栏 栾 澜
|
||||
란 丹 卵 亂 蘭 欄 爛 鸞 懶 瀾 欒 闌 鑾 攔 襴 嬾 幱 灓 乱 䦨 滦 灤 斕 圝 圞 灡 籣 兰 拦 斓 栏 栾 澜 烂 谰 銮 镧 阑 鸾 亪 囒 孄 孏 曫 欗 燗 爤 瓓 癵 糷 羉 膥 虊 襕 襽 譋 讕 躝 釠 鑭 钄 韊 鵉 㘓 㡩 㦨 㱍 㱫 㳕 䃹 䑌 䖂 䪍
|
||||
랄 剌 辣 喇 埒 辢 捋 獭 垃 埓 揦 揧 攋 楋 瓎 瘌 蝲 頱 鬎 鯻 㸊 㻋 㻝 䀳 䏀 䓥 䓶 䱫 䶛
|
||||
람 覽 濫 藍 籃 攬 嵐 纜 欖 擥 襤 婪 惏 覧 灠 壈 㲯 䰐 㩜 囕 㑣 岚 揽 榄 滥 漤 篮 缆 蓝 褴 览 䍀 儖
|
||||
람 覽 濫 藍 籃 攬 嵐 纜 欖 擥 襤 婪 惏 覧 灠 壈 㲯 䰐 㩜 囕 㑣 岚 揽 榄 滥 漤 篮 缆 蓝 褴 览 䍀 儖 啉 嚂 懢 浨 灆 燣 燷 爁 爦 璼 礷 繿 葻 蘫 顲 㘕 㛦 㜮 㞩 㧛 㨫 㰓 㰖 䆾 䌫 䨬
|
||||
랍 拉 臘 蠟 摺 鑞 臈 爉 䗶 镴 啦 搚 柆 磖 翋 菈 蝋 邋 鞡 㕇 㡴 䂰 䃳 䇱
|
||||
랑 郞 浪 廊 狼 朗 琅 郎 螂 瀧 瑯 踉 閬 蜋 稂 榔 硠 莨 桹 烺 蓈 鋃 欴 艆 㫰 锒 阆 勆 啷 埌 塱 嫏 崀
|
||||
래 來 萊 賚 崍 徠 来 淶 騋 顂 勑 徕 莱 䅘 猍 琜 倈 箂 棶 睞 錸 鵣 䋱 䚅 崃 俫 梾 涞 睐 铼 儽 唻 婡
|
||||
랑 郞 浪 廊 狼 朗 琅 郎 螂 瀧 瑯 踉 閬 蜋 稂 榔 硠 莨 桹 烺 蓈 鋃 欴 艆 㫰 锒 阆 勆 啷 埌 塱 嫏 崀 朖 朤 樃 筤 蒗 蓢 誏 躴 郒 駺 㓪 㙟 㟍 㢃 㮾 㱢 㾿 䆡 䍚 䕞 䡙 䯖 䱶
|
||||
래 來 萊 賚 崍 徠 来 淶 騋 顂 勑 徕 莱 䅘 猍 琜 倈 箂 棶 睞 錸 鵣 䋱 䚅 崃 俫 梾 涞 睐 铼 儽 唻 婡 庲 逨 郲 鯠 鶆 麳 㚓 㴺 㾢 䂾 䧒
|
||||
랭 冷 唥
|
||||
략 略 掠 畧 攊 圙 撂 洜 㑼 㗉 㨼 䂮 䌎 䛚
|
||||
량 兩 場 良 量 凉 梁 糧 諒 亮 樑 輛 粱 倆 粮 涼 魎 踉 椋 悢 喨 两 両 惊 㾗 湸 緉 裲 輌 輬 俍 俩 唡
|
||||
려 旅 慮 麗 勵 呂 廬 礪 驪 閭 戾 黎 侶 濾 犁 驢 藜 儷 蠣 櫚 厲 臚 癘 膂 蠡 糲 唳 梠 鑢 儢 邌 吕 沴
|
||||
력 力 歷 曆 瀝 礫 轢 靂 櫟 櫪 鬲 酈 攊 癧 轣 歴 暦 䔉 厯 皪 屴 朸 檪 磿 礰 躒 历 呖 坜 枥 栎 沥 疬
|
||||
련 連 練 鍊 憐 戀 聯 蓮 漣 輦 煉 怜 璉 攣 錬 臠 孌 鰱 楝 鏈 孿 湅 鰊 恋 堜 摙 䏈 縺 聨 㜻 链 娈 挛
|
||||
렬 列 烈 劣 裂 冽 洌 捩 颲 挒 茢 迾 鋢 鴷 㭩 锊 䴕 劽 咧 哷 姴 挘 栵 浖 烮 煭 爄 睙 脟 蛚 蛶 趔 迣
|
||||
렴 廉 濂 斂 簾 殮 歛 籟 瀲 奩 獫 磏 帘 㢘 嬚 鐮 搛 溓 薕 蘝 蠊 奁 敛 潋 莶 蔹 镰 亷 劆 匲 匳 澰 濓
|
||||
량 兩 場 良 量 凉 梁 糧 諒 亮 樑 輛 粱 倆 粮 涼 魎 踉 椋 悢 喨 两 両 惊 㾗 湸 緉 裲 輌 輬 俍 俩 唡 谅 辆 辌 魉 哴 啢 墚 掚 斏 晾 煷 簗 綡 羪 脼 蜽 㒳 㔀 㔝 㝗 㹁 䀶 䁁 䓣 䝶 䠃 䣼 䩫 䭪 駺
|
||||
려 旅 慮 麗 勵 呂 廬 礪 驪 閭 戾 黎 侶 濾 犁 驢 藜 儷 蠣 櫚 厲 臚 癘 膂 蠡 糲 唳 梠 鑢 儢 邌 吕 沴 荔 丽 侣 盭 瓈 欐 蟸 黧 棙 穭 廲 藘 攦 櫖 爈 茘 蜧 㑦 䕻 励 俪 厉 庐 榈 滤 疠 砺 稆 粝 胪 蛎 铝 闾 驴 骊 鲡 勴 囇 婯 巁 悷 挔 捛 曞 曥 栛 沵 焒 珕 矋 祣 禲 穞 絽 綟 膐 蛠 蠇 郘 鋁 鏫 鑗 馿 騹 鱱 鱺 鵹 㒧 㓯 㛎 㠟 㠠 㭚 㰀 㱺 㴝 㾐 㾔 㿛 䉫 䊪 䓞 䔣 䔧 䕡 䗍 䘈 䣓 䥨 䥿 䮉 䰕 䴡
|
||||
력 力 歷 曆 瀝 礫 轢 靂 櫟 櫪 鬲 酈 攊 癧 轣 歴 暦 䔉 厯 皪 屴 朸 檪 磿 礰 躒 历 呖 坜 枥 栎 沥 疬 砾 苈 跞 轹 郦 镉 雳 儮 厤 叻 嚦 壢 擽 爏 瓅 瓑 砳 秝 蒚 藶 蚸 蝷 觻 讈 赲 鎘 靋 韷 鱳 鳨 㔏 㠣 㧰 㬏 㯤 㱹 㷴 㺡 㻺 㽁 㿨 䃯 䍥 䍽 䟏 䟐 䤙 䥶 䮥 䰛 䰜
|
||||
련 連 練 鍊 憐 戀 聯 蓮 漣 輦 煉 怜 璉 攣 錬 臠 孌 鰱 楝 鏈 孿 湅 鰊 恋 堜 摙 䏈 縺 聨 㜻 链 娈 挛 撵 梿 涟 炼 琏 练 联 脔 莲 裢 辇 连 鲢 僆 嗹 噒 奱 媡 嫾 徚 慩 攆 槤 櫣 瑓 癴 纞 翴 聫 聮 膦 萰 褳 謰 蹥 鄻 㜃 㜕 㝈 㟀 㥕 㦁 㪝 㰈 㶌 㺦 㼑 䃛 䜌
|
||||
렬 列 烈 劣 裂 冽 洌 捩 颲 挒 茢 迾 鋢 鴷 㭩 锊 䴕 劽 咧 哷 姴 挘 栵 浖 烮 煭 爄 睙 脟 蛚 蛶 趔 迣 鋝 鮤 㤠 㬯 㭞 㸹 㽟 䅀 䟹 䮋
|
||||
렴 廉 濂 斂 簾 殮 歛 籟 瀲 奩 獫 磏 帘 㢘 嬚 鐮 搛 溓 薕 蘝 蠊 奁 敛 潋 莶 蔹 镰 亷 劆 匲 匳 澰 濓 熑 燫 稴 籢 籨 羷 臁 薟 蘞 螊 覝 鬑 㝺 㡘 㪘 㯬 㱨 㶑 㼓 䁠 䆂 䌞 䙺 䥥 䭑
|
||||
렵 獵 鬣 躐 儠 擸 猟 巤 犣 聗 鱲 㯿 㲱 㼃 㼲 䁽 䉭 䜲 䝓 䪉
|
||||
령 令 領 寧 靈 嶺 零 鈴 玲 囹 齡 逞 聆 怜 伶 翎 羚 岺 笭 另 岭 泠 苓 鴒 蛉 欞 呤 昤 姈 秢 軨 鹷 灵
|
||||
령 令 領 寧 靈 嶺 零 鈴 玲 囹 齡 逞 聆 怜 伶 翎 羚 岺 笭 另 岭 泠 苓 鴒 蛉 欞 呤 昤 姈 秢 軨 鹷 灵 坽 㱓 櫺 醽 霛 瓴 狑 舲 彾 竛 跉 霝 燯 詅 酃 棂 铃 霊 领 鸰 齢 龄 刢 孁 拎 朎 柃 澪 炩 爧 皊 砱 紷 蕶 蘦 衑 袊 閝 阾 霗 駖 魿 麢 龗 㚑 㡵 㦭 㩕 㪮 㬡 㯪 㲆 㸳 㻏 㾉 䄥 䉁 䉖 䉹 䌢 䍅 䕘 䖅 䙥 䠲 䡼 䡿 䨩 䯍 䰱 䴇 䴒 䴫
|
||||
례 禮 例 豊 隷 醴 澧 隸 鱧 礼 㘑 鳢 櫔 欚 濿 犡 瓥 砅 㡂 㤡 㽝 䅄 䵩
|
||||
로 老 路 勞 怒 露 盧 魯 爐 蘆 鷺 虜 擄 撈 輅 鹵 潞 芦 櫓 瀘 潦 鑪 鱸 壚 騾 艪 顱 艫 鸕 轤 玈 窂 滷
|
||||
록 綠 錄 祿 鹿 麓 碌 菉 淥 漉 彔 騄 簏 轆 圥 录 盝 籙 禄 醁 緑 廘 録 㖨 睩 蔍 逯 绿 渌 箓 辘 塶 娽
|
||||
로 老 路 勞 怒 露 盧 魯 爐 蘆 鷺 虜 擄 撈 輅 鹵 潞 芦 櫓 瀘 潦 鑪 鱸 壚 騾 艪 顱 艫 鸕 轤 玈 窂 滷 癆 磠 鐪 髗 劳 㳣 澇 鏴 璐 獹 罏 攎 簵 䳓 咾 嫪 櫨 珯 矑 簬 艣 髝 卤 橹 炉 労 卢 唠 噜 垆 崂 捞 掳 撸 栌 氇 泸 涝 痨 耢 舻 虏 轳 辂 铑 铹 颅 鲁 鲈 鸬 鹭 僗 哰 嘮 嚕 嚧 塷 嶗 恅 憥 憦 擼 朥 栳 樐 橯 氌 浶 澛 瀂 狫 瓐 硓 磱 簩 籚 纑 耂 耮 蓾 蕗 虂 蟧 蠦 軂 銠 鏀 鐒 魲 鮱 黸 㔧 㔪 㗦 㟉 㢚
|
||||
록 綠 錄 祿 鹿 麓 碌 菉 淥 漉 彔 騄 簏 轆 圥 录 盝 籙 禄 醁 緑 廘 録 㖨 睩 蔍 逯 绿 渌 箓 辘 塶 娽 摝 梺 椂 樚 氯 熝 琭 甪 簶 粶 膔 螰 觮 趢 蹗 錴 騼 鵦 龣 㜙 㟤 㦇 㪖 㫽 㯟 㲶 㼾 䃙 䌒 䍡 䎑 䎼 䐂 䘵 䚄 䟿 䩮 䱚 䴪
|
||||
론 論 掄 惀 碖 菕 论 婨 溣 㤻
|
||||
롱 弄 籠 龐 聾 壟 瓏 朧 瀧 隴 礱 曨 蘢 儱 攏 龎 櫳 壠 巃 哢 礲 䏊 嚨 竉 㺯 胧 厐 咙 垄 垅 拢 昽 栊
|
||||
뢰 雷 賴 賂 儡 牢 磊 賚 瀨 罍 耒 籟 酹 誄 礧 纇 蕾 顂 攂 礌 頼 瀬 㵢 䨓 藾 鐳 傫 櫑 瓃 礨 䒹 濑 籁
|
||||
료 料 了 僚 遼 療 聊 寮 燎 瞭 廖 寥 鬧 蓼 潦 醪 繚 膋 撩 暸 獠 鐐 嘹 嫽 飂 飉 憀 憭 鷯 䝤 璙 炓 㵳
|
||||
롱 弄 籠 龐 聾 壟 瓏 朧 瀧 隴 礱 曨 蘢 儱 攏 龎 櫳 壠 巃 哢 礲 䏊 嚨 竉 㺯 胧 厐 咙 垄 垅 拢 昽 栊 泷 滝 珑 砻 笼 聋 茏 陇 巄 挊 挵 梇 槞 硦 篭 蠪 裃 襱 豅 鏧 鑨 靇 鸗 龓 㑝 㙙 㛞 㟖 㢅 㰍 㳥 䆍 䡁 䪊
|
||||
뢰 雷 賴 賂 儡 牢 磊 賚 瀨 罍 耒 籟 酹 誄 礧 纇 蕾 顂 攂 礌 頼 瀬 㵢 䨓 藾 鐳 傫 櫑 瓃 礨 䒹 濑 籁 诔 赂 赉 赖 镭 壨 擂 檑 櫴 洡 珱 畾 癗 磥 筙 蠝 襰 讄 轠 銇 錑 鑘 鑸 靁 頛 頪 颣 鱩 㑍 㒦 㔣 㠥 㲕 㵽 㼍 㿔 䄤 䉪 䍣 䐯 䛶 䠭 䢮 䣂 䮑 䲚
|
||||
료 料 了 僚 遼 療 聊 寮 燎 瞭 廖 寥 鬧 蓼 潦 醪 繚 膋 撩 暸 獠 鐐 嘹 嫽 飂 飉 憀 憭 鷯 䝤 璙 炓 㵳 尞 橑 爎 轑 㝋 疗 缭 辽 镣 鹩 佬 叾 尥 尦 屪 嶚 嶛 廫 敹 曢 熮 爒 爳 窲 窷 竂 簝 翏 膫 藔 蟉 蟟 豂 賿 蹘 蹽 鄝 镽 顟 髎 㙩 㞠 㟹 㡻 㶫 䄦 䉼 䎆 䑠 䒿 䕩 䜍 䢧 䨅 䩍 䭜
|
||||
룡 龍 龒 矓 眬 龙 徿 爖 竜 蠬 贚 躘 驡 㡣
|
||||
루 樓 漏 淚 累 屢 陋 壘 縷 婁 鏤 褸 蔞 瘻 螻 僂 髏 嶁 慺 嘍 耬 楼 屡 泪 蝼 塁 摟 塿 䮫 嫘 屚 廔 䫫
|
||||
류 流 留 柳 類 劉 謬 溜 琉 瘤 繆 瑠 旒 榴 硫 瀏 纍 鶹 縲 瑬 遛 橊 飂 霤 騮 鏐 旈 䄂 鎏 罶 飀 藟 鎦
|
||||
루 樓 漏 淚 累 屢 陋 壘 縷 婁 鏤 褸 蔞 瘻 螻 僂 髏 嶁 慺 嘍 耬 楼 屡 泪 蝼 塁 摟 塿 䮫 嫘 屚 廔 䫫 瘘 偻 喽 垒 娄 嵝 搂 篓 缕 耧 蒌 褛 镂 髅 䁖 厽 櫐 氀 涙 溇 漊 熡 甊 瘺 癳 瞜 簍 膢 艛 謱 貗 軁 遱 鞻 鷜 鼺 㒍 㔷 㙼 㜢 㟺 㡞 㥪 㪹 㲎 㴃 㶟 㹎 㺏 㻲 䄛 䅹 䉂 䝏 䣚 䱾 䴎
|
||||
류 流 留 柳 類 劉 謬 溜 琉 瘤 繆 瑠 旒 榴 硫 瀏 纍 鶹 縲 瑬 遛 橊 飂 霤 騮 鏐 旈 䄂 鎏 罶 飀 藟 鎦 飅 絫 羀 蘽 虆 蹓 駠 駵 䖻 刘 浏 类 绺 缧 蔂 谬 铆 锍 镏 镠 飗 馏 骝 鹨 嚠 塯 媹 嬼 嵧 廇 懰 栁 桺 樏 橮 欙 漻 澑 灅 熘 珋 璢 畄 畱 疁 癅 磂 禷 綹 纝 艈 蒥 蓅 蕌 藰 蘱 蘲 裗 鉚 鋶 鐂 雡 飹 餾 驑 鬸 鰡 鷚 鸓 麍 㐬 㙧 㧕 㳅 㶯 㽌 㽞 㾋 䉧 䗜 䚧 䝀 䣦 䬟 䰘 䱖 䶉
|
||||
륙 六 陸 戮 勠 僇 稑 穋 陆 剹 勎 坴 淕 磟 踛 鯥 鵱 㓐 㛬 䡜
|
||||
륜 倫 輪 崙 綸 淪 侖 掄 錀 踚 崘 圇 棆 仑 伦 囵 抡 沦 纶 轮 嗧 埨 磮 稐 耣 腀 蜦 陯 鯩 㓆 㖮 㷍 䈁
|
||||
률 律 率 栗 慄 嵂 溧 篥 㟳 硉 瑮 膟 㮚 峍 侓 傈 凓 垏 塛 寽 徍 搮 箻 繂 葎 鷅 麜 㖀 㗚 㪐 䔁 䔞 䡃
|
||||
륜 倫 輪 崙 綸 淪 侖 掄 錀 踚 崘 圇 棆 仑 伦 囵 抡 沦 纶 轮 嗧 埨 磮 稐 耣 腀 蜦 陯 鯩 㓆 㖮 㷍 䈁 䑳
|
||||
률 律 率 栗 慄 嵂 溧 篥 㟳 硉 瑮 膟 㮚 峍 侓 傈 凓 垏 塛 寽 徍 搮 箻 繂 葎 鷅 麜 㖀 㗚 㪐 䔁 䔞 䡃 䬆
|
||||
륭 隆 瀜 癃 窿 烿 嶐 蕯 湰 霳 㚅 㝫 㦕 䃧 䥢
|
||||
륵 勒 肋 泐 玏 仂 扐 鳓 嘞 忇 椚 氻 竻 簕 艻 阞 鰳
|
||||
를 㔹
|
||||
름 凜 廩 凛 澟 檩 廪 懍 懔 檁 癛 癝 菻 㐭 㨆 䕲
|
||||
릉 陵 楞 凌 綾 菱 稜 倰 蔆 棱 崚 淩 夌 庱 掕 睖 鯪 绫 鲮 堎 婈 愣 琌 碐 祾 蓤 薐 裬 踜 輘 錂 㖫 㘄
|
||||
리 里 利 理 異 李 吏 離 履 裏 梨 驪 罹 裡 籬 厘 俚 痢 悧 羸 釐 狸 犁 璃 鯉 唎 莉 浬 漓 莅 离 螭 俐
|
||||
린 鄰 隣 麟 鱗 吝 躪 燐 璘 藺 潾 獜 磷 粼 嶙 驎 轔 繗 悋 躙 鏻 粦 遴 恡 瞵 䗲 疄 撛 僯 厸 轥 蔺 躏
|
||||
릉 陵 楞 凌 綾 菱 稜 倰 蔆 棱 崚 淩 夌 庱 掕 睖 鯪 绫 鲮 堎 婈 愣 琌 碐 祾 蓤 薐 裬 踜 輘 錂 㖫 㘄 㥄 㱥 䈊 䉄 䔖 䧙 䬋 䮚
|
||||
리 里 利 理 異 李 吏 離 履 裏 梨 驪 罹 裡 籬 厘 俚 痢 悧 羸 釐 狸 犁 璃 鯉 唎 莉 浬 漓 莅 离 螭 俐 嫠 魑 貍 邐 涖 哩 蜊 黐 剺 詈 纚 鸝 摛 醨 蒞 蘺 褵 縭 盠 劙 峛 彲 悝 樆 犂 蔾 俬 娌 棃 氂 䍦 䙰 篱 缡 蓠 逦 锂 鲤 鹂 兣 刕 剓 喱 嚟 囄 娳 孋 孷 峢 峲 彨 悡 攡 梩 梸 浰 犛 猁 琍 瓼 甅 睝 瞝 矖 穲 竰 筣 粚 粴 糎 繌 脷 艃 荲 菞 蟍 蠫 謧 鋫 鋰 錅 鬁 鯏 鯬 麶 㒿 㛤 㤦 㥎 㦒 㰚 㲠 㷰 㸚 㹈 㻳
|
||||
린 鄰 隣 麟 鱗 吝 躪 燐 璘 藺 潾 獜 磷 粼 嶙 驎 轔 繗 悋 躙 鏻 粦 遴 恡 瞵 䗲 疄 撛 僯 厸 轥 蔺 躏 辚 邻 鳞 亃 壣 斴 暽 橉 焛 甐 翷 蹸 閵 麐 㔂 㖁 㷠 䉮 䚏 䚬 䢯 䫰 䮼 䱯
|
||||
림 林 臨 淋 琳 霖 痳 惏 冧 崊 晽 棽 瀮 瀶 碄 箖 醂 㝝 㣩 㵉 䫐 玪
|
||||
립 立 笠 粒 砬 岦 雴 苙 鴗 湁 鉝 㕸 䇉 䢂 䲞 䶘
|
||||
마 馬 麻 磨 摩 魔 痲 瑪 碼 麽 麿 劘 媽 螞 蟇 麼 蟆 亇 嗎 禡 䔍 嘛 鎷 㦄 吗 唛 妈 嬷 杩 犸 玛 码 祃
|
||||
마 馬 麻 磨 摩 魔 痲 瑪 碼 麽 麿 劘 媽 螞 蟇 麼 蟆 亇 嗎 禡 䔍 嘛 鎷 㦄 吗 唛 妈 嬷 杩 犸 玛 码 祃 蚂 马 傌 吀 哶 嘜 嚒 嚤 嚰 嫲 嬤 尛 懡 戂 擵 榪 溤 犘 獁 礳 耱 蔴 藦 蘑 饝 髍 鬕 鰢 鷌 㐃 㐷 㾺 䃺 䗫 䘞 䜆 䣕 䣖 䧞 䩋 䭩 䯢 䲈 䳸
|
||||
막 莫 幕 漠 膜 寞 邈 藐 鏌 瞙 瘼 幙 暯 嗼 鄚 饃 馍 镆 劰 塻 瞐 縸 㠳 㦝 㱳 㷬 䒬 䮬
|
||||
만 萬 滿 晩 慢 漫 蠻 灣 娩 挽 蔓 瞞 輓 彎 饅 鰻 卍 万 曼 巒 謾 幔 懣 嫚 蹣 墁 悗 縵 鏝 鬘 鏋 湾 晚
|
||||
말 末 靺 抹 襪 沫 唜 茉 帕 秣 昩 袜 妺 韈 眜 枺 帓 怽 瀎 皌 睰 砞 礣 粖 袹 閁 韤 魩 㑻 㨸 㭐 㶬 䍪
|
||||
만 萬 滿 晩 慢 漫 蠻 灣 娩 挽 蔓 瞞 輓 彎 饅 鰻 卍 万 曼 巒 謾 幔 懣 嫚 蹣 墁 悗 縵 鏝 鬘 鏋 湾 晚 満 熳 㬅 鬛 澫 脕 僈 摱 睌 矕 蟎 鋔 㡢 䐽 蛮 峦 弯 满 瞒 缦 螨 谩 蹒 镘 颟 馒 鳗 卐 塆 壪 屘 慲 梚 槾 樠 潫 澷 濷 獌 蔄 蘰 蟃 襔 贎 鄤 鞔 顢 鬗 㒼 㗄 㗈 㙢 㵘 㸘 㻴 㿸 䅋 䅼 䊡 䒥 䕕 䘎 䜱 䝡 䝢 䡬 䤍 䯶 䰋
|
||||
말 末 靺 抹 襪 沫 唜 茉 帕 秣 昩 袜 妺 韈 眜 枺 帓 怽 瀎 皌 睰 砞 礣 粖 袹 閁 韤 魩 㑻 㨸 㭐 㶬 䍪 䏞 䬴 䯦 䱅 䴲
|
||||
맘 鋄 錽 鎫 㛧 䥑
|
||||
망 亡 望 忘 忙 妄 罔 茫 網 芒 莽 邙 輞 惘 魍 莾 蟒 漭 汒 网 亾 鋩 蠎 㒺 㤀 兦 㕵 䋄 恾 朢 杗 蘉 誷
|
||||
매 賣 買 每 妹 梅 媒 埋 枚 魅 昧 寐 罵 邁 煤 呆 楳 眛 莓 玫 沬 苺 酶 霉 浼 霾 䨪 禖 毎 抺 痗 佅 凂
|
||||
망 亡 望 忘 忙 妄 罔 茫 網 芒 莽 邙 輞 惘 魍 莾 蟒 漭 汒 网 亾 鋩 蠎 㒺 㤀 兦 㕵 䋄 恾 朢 杗 蘉 誷 蝄 釯 㟿 㬒 䰶 纲 辋 铓 吂 壾 奀 娏 杧 棢 焹 牤 琞 痝 盳 硥 硭 笀 罒 罓 茻 莣 菵 蛧 㟌 㟐 䁳 䅒 䋞 䍏 䒎 䒽 䓼 䥈 䰣 䱩 䵨
|
||||
매 賣 買 每 妹 梅 媒 埋 枚 魅 昧 寐 罵 邁 煤 呆 楳 眛 莓 玫 沬 苺 酶 霉 浼 霾 䨪 禖 毎 抺 痗 佅 凂 勱 湈 脄 韎 駡 鬽 骂 买 劢 卖 売 荬 迈 呅 嘪 坆 塺 挴 槑 珻 脢 腜 蕒 薶 跊 鋂 鷶 黣 㙁 㜥 㦟 㭑 㵋 㺳 䀛 䀜 䁲 䆀 䊈 䍙 䔦 䚑 䜕 䜸 䤂 䰨 䰪 䵢
|
||||
맥 麥 脈 貊 陌 驀 脉 貃 貘 霡 貉 霢 眽 覛 麦 蓦 帞 眿 絈 蛨 衇 㵹 㹮 䘑 䨫 䮮 䳮
|
||||
맹 孟 盟 猛 盲 萌 氓 黽 甍 甿 虻 儚 蝱 艋 蕄 鄳 锰 勐 掹 擝 橗 瓾 莔 蜢 錳 鯭 鼆 䁅 䇇 䈍 䉚 䓝 䖟
|
||||
맹 孟 盟 猛 盲 萌 氓 黽 甍 甿 虻 儚 蝱 艋 蕄 鄳 锰 勐 掹 擝 橗 瓾 莔 蜢 錳 鯭 鼆 䁅 䇇 䈍 䉚 䓝 䖟 䗈 䥰
|
||||
먀 乜
|
||||
며 旀 厼 㢱
|
||||
멱 覓 汨 蓂 冪 糸 幎 羃 覔 幂 纟 觅 冖 塓 幦 淿 漞 濗 簚 糹 鼏 㯒 䈿 䌐 䖑 䣾 䮭
|
||||
면 面 免 勉 眠 綿 冕 沔 娩 緬 眄 麵 棉 俛 緜 麪 糆 湎 靣 丏 偭 㴐 勔 愐 櫋 莬 葂 麫 绵 缅 腼 丆 喕
|
||||
면 面 免 勉 眠 綿 冕 沔 娩 緬 眄 麵 棉 俛 緜 麪 糆 湎 靣 丏 偭 㴐 勔 愐 櫋 莬 葂 麫 绵 缅 腼 丆 喕 婂 媔 嬵 宀 檰 汅 矈 矊 矏 臱 芇 蝒 鮸 麺 㒙 㛯 㝃 㝰 㨠 㨺 㮌 㰃 䀎 䃇 䛉 䛲 䤄 䫵 䰓
|
||||
멸 滅 蔑 衊 篾 㓕 蠛 搣 櫗 灭 孭 幭 懱 揻 薎 鑖 鱴 鴓 㒝 㩢 䁾 䈼 䌩 䘊 䡸 䩏
|
||||
명 明 命 名 鳴 銘 冥 溟 暝 皿 螟 酩 瞑 蓂 茗 椧 黽 洺 㝠 眀 凕 㫥 詺 䏃 铭 鸣 佲 姳 嫇 慏 掵 朙 榠
|
||||
명 明 命 名 鳴 銘 冥 溟 暝 皿 螟 酩 瞑 蓂 茗 椧 黽 洺 㝠 眀 凕 㫥 詺 䏃 铭 鸣 佲 姳 嫇 慏 掵 朙 榠 焽 熐 猽 眳 笽 覭 鄍 鵈 㓁 㗮 㟰 䄙 䆩 䊅 䒌 䫤 䳟
|
||||
몌 袂
|
||||
모 母 毛 暮 慕 募 模 某 謀 冒 侮 貌 謨 牟 帽 茅 矛 耗 摸 牡 眸 摹 瑁 芼 姆 糢 耄 侔 髦 旄 媢 蟊 嫫
|
||||
모 母 毛 暮 慕 募 模 某 謀 冒 侮 貌 謨 牟 帽 茅 矛 耗 摸 牡 眸 摹 瑁 芼 姆 糢 耄 侔 髦 旄 媢 蟊 嫫 眊 皃 姥 蝥 粍 鉾 恈 冐 麰 㡌 謩 氁 秏 䫉 橅 䖥 牦 谋 谟 兞 冃 哞 娒 峔 慔 枆 栂 毟 毪 毮 毷 洠 獏 玥 瓱 砪 竓 罞 艒 萺 蓩 蛑 蝐 蟱 覒 軞 酕 魹 鴾 鶜 㑄 㒵 㒻 㖼 㘪 㛌 㝟 㟂 㧇 㧌 㪞 㴘 㹸 㺺 㿞 䋃 䗋 䡚 䭷
|
||||
목 目 木 牧 睦 穆 沐 鶩 苜 凩 楘 莯 霂 䳱 㜈 毣 炑 钼 鹜 坶 杢 狇 蚞 鉬 雮 㣎 㾇 䀲 䊾 䑵 䜼 䲷
|
||||
몰 沒 歿 没 殁 圽 朰 歽 歾 莈
|
||||
몸 鯍
|
||||
몽 夢 蒙 朦 矇 濛 曚 瞢 幪 雺 懞 艨 儚 溕 鸏 梦 懵 冡 懜 䑃 蠓 㠓 饛 矒 霿 䤓 䝉 鹲 夣 檬 氋 獴 甮
|
||||
묘 卯 妙 廟 墓 秒 苗 猫 昴 描 畝 渺 杳 竗 錨 眇 藐 淼 貓 庙 緲 畒 玅 茆 篎 缈 锚 鹋 冇 喵 媌 嫹 峁
|
||||
몽 夢 蒙 朦 矇 濛 曚 瞢 幪 雺 懞 艨 儚 溕 鸏 梦 懵 冡 懜 䑃 蠓 㠓 饛 矒 霿 䤓 䝉 鹲 夣 檬 氋 獴 甮 礞 鄸 霥 靀 㒱 㙹 㜴 㝱 㩚 㴳 䀄 䏵 䑅 䒐 䙦 䙩 䟥 䠢 䥂 䰒 䲛 䴌 䴿 䵆
|
||||
묘 卯 妙 廟 墓 秒 苗 猫 昴 描 畝 渺 杳 竗 錨 眇 藐 淼 貓 庙 緲 畒 玅 茆 篎 缈 锚 鹋 冇 喵 媌 嫹 峁 庿 戼 泖 渵 畆 瞄 笷 緢 覅 鱙 鶓 㑤 㚹 䁧 䅦 䏚 䖢
|
||||
묠 乮
|
||||
무 無 武 務 戊 茂 舞 貿 霧 誣 巫 毋 撫 畝 蕪 憮 拇 无 懋 繆 鵡 珷 楙 廡 騖 膴 儛 嘸 抚 瞀 袤 鍪 婺
|
||||
무 無 武 務 戊 茂 舞 貿 霧 誣 巫 毋 撫 畝 蕪 憮 拇 无 懋 繆 鵡 珷 楙 廡 騖 膴 儛 嘸 抚 瞀 袤 鍪 婺 嫵 碔 幠 甒 倵 堥 瞴 莁 鉧 㒇 亩 务 呒 妩 庑 怃 缪 芜 诬 贸 雾 骛 鹀 鹉 仫 劺 呣 墲 奦 娬 嵍 愗 敄 暓 柕 橆 潕 熃 牳 璑 畞 畮 胟 袰 誈 譕 踇 躌 鄮 錻 霚 鞪 髳 鵐 鷡 㡔 㣳 㭌 㮊 㮘 㱐 㳇 㵲 㶃 㷻 㽗 䀤 䉑 䋷 䌗 䍢 䏬 䒉 䓮 䟼 䥐 䥻 䨁 䱕 䳇 橅
|
||||
묵 墨 黙 默 嘿 纆 嚜 爅 嫼 濹 癦 蟔 㩏 㫯 㷵 䁼 䁿 䘃
|
||||
문 門 問 文 聞 紊 汶 紋 蚊 吻 刎 們 雯 捫 懣 抆 忞 悗 炆 匁 忟 璊 闻 问 门 懑 㦖 脗 暪 絻 蚉 閿 馼
|
||||
문 門 問 文 聞 紊 汶 紋 蚊 吻 刎 們 雯 捫 懣 抆 忞 悗 炆 匁 忟 璊 闻 问 门 懑 㦖 脗 暪 絻 蚉 閿 馼 们 扪 纹 钔 阌 伆 勽 呡 妏 彣 渂 珳 璺 肳 芠 菛 虋 螡 蟁 鈫 鍆 閅 閺 闅 駇 魰 鳼 鴍 鼤 㗃 㡈 㭣 㵍 㻊 䊟 䎹 䎽 䘇 䟂 䠺 䦩 䫒 䰚
|
||||
물 物 勿 沕 岉 昒 粅 㫚 䥼
|
||||
미 美 米 未 味 尾 微 迷 眉 彌 靡 媚 薇 楣 湄 嵋 渼 謎 黴 梶 弥 弭 糜 縻 亹 瀰 麋 敉 媺 獼 娓 蘼 媄
|
||||
민 民 敏 閔 憫 悶 旻 旼 珉 玟 愍 泯 岷 緡 黽 閩 忞 暋 緍 湣 忟 罠 苠 鰵 黾 僶 渑 澠 慜 潣 敃 燘 痻
|
||||
미 美 米 未 味 尾 微 迷 眉 彌 靡 媚 薇 楣 湄 嵋 渼 謎 黴 梶 弥 弭 糜 縻 亹 瀰 麋 敉 媺 獼 娓 蘼 媄 苿 㫆 眯 冞 洣 罙 㣲 麛 㜷 䞔 郿 㜫 㳽 浘 眫 㞑 猕 芈 谜 镁 镅 鹛 侎 咩 咪 堳 嬍 孊 嵄 嶶 徾 捤 擟 攗 攠 斖 渳 溦 灖 煝 爢 猸 瑂 癓 睂 睸 瞇 矀 祙 穈 篃 羋 脒 艉 荱 菋 葞 葿 蒾 蔝 蘪 蝞 覹 詸 躾 醚 醾 醿 釄 銤 鎂 鎇 镾 霺 鮇 鶥 麊 㜆 㝥 㟜 㠧 㣆 㥝 㴹 㵟 㸏 䅏 䆊 䇻 䉋 䉠 䉲 䊊 䊳 䋛 䌕
|
||||
민 民 敏 閔 憫 悶 旻 旼 珉 玟 愍 泯 岷 緡 黽 閩 忞 暋 緍 湣 忟 罠 苠 鰵 黾 僶 渑 澠 慜 潣 敃 燘 痻 砇 悯 焖 缗 闵 闷 闽 鳘 冺 刡 勄 垊 姄 崏 怋 抿 捪 敯 燜 琘 琝 瑉 盿 碈 笢 簢 鈱 錉 鍲 鴖 㞶 㟩 㟭 㥃 㥗 㥸 㨉 㫣 㬆 䁕 䂥 䃉 䋋 䝧 䟨 䡑 䡻 䪸 䲄 脗
|
||||
밀 密 蜜 謐 宓 樒 滵 宻 谧 嘧 峚 櫁 淧 蔤 藌 蠠 㫘 㳴 㴵 㵥 䁇 䌏 䛑 䤉
|
||||
박 朴 迫 博 泊 拍 薄 駁 舶 剝 縛 撲 搏 樸 珀 箔 粕 膊 雹 璞 亳 駮 鎛 墣 欂 牔 髆 剥 敀 礴 襮 廹 愽
|
||||
반 半 反 飯 班 般 返 伴 盤 叛 頒 槃 斑 潘 搬 磻 攀 畔 蟠 絆 礬 拌 泮 盼 磐 瘢 胖 蹣 扳 搫 頖 攽 朌
|
||||
발 發 髮 拔 跋 鉢 潑 渤 撥 勃 魃 醱 孛 浡 鈸 鵓 哱 脖 钵 発 彂 綍 髪 沷 㪍 墢 茇 㧞 䟦 袚 襏 抜 犮
|
||||
박 朴 迫 博 泊 拍 薄 駁 舶 剝 縛 撲 搏 樸 珀 箔 粕 膊 雹 璞 亳 駮 鎛 墣 欂 牔 髆 剥 敀 礴 襮 廹 愽 䪨 礡 檏 擈 煿 鑮 彴 窇 㩧 瞨 䥬 缚 钋 铂 镈 镤 飑 馎 驳 啪 噗 嚗 圤 懪 炇 烞 犦 狛 猼 瓟 砶 簙 胉 艊 萡 蒪 趵 釙 鉑 鏷 颮 餺 骲 髉 㕷 㗘 㙸 㬍 㴖 㵡 㹒 㺪 㼎 㿺 䂍 䃗 䄸 䍸 䎅 䑈 䗚 䙏 䥤 䨌 䨔 䨣 䨰 䪇 䪙 䬪 䭦 䮀 䰊 䶈
|
||||
반 半 反 飯 班 般 返 伴 盤 叛 頒 槃 斑 潘 搬 磻 攀 畔 蟠 絆 礬 拌 泮 盼 磐 瘢 胖 蹣 扳 搫 頖 攽 朌 仮 媻 肦 螌 盘 襻 鞶 癍 憣 詊 斒 牉 㜶 㮽 䛀 矾 绊 颁 饭 冸 坢 奤 姅 婏 嬎 嬔 幋 怑 柈 沜 溿 瀊 炍 秚 縏 蒰 虨 褩 跘 辬 鉡 鋬 鎜 鑻 闆 靽 飰 魬 鳻 㐴 㚘 㢖 㪵 㫠 㭧 㶗 㽃 㽹 㿀 䃑 䃲 䆺 䈲 䉊 䉽 䊩 䙪 䡊 䣲 䬳 䰉 䰔 豳
|
||||
발 發 髮 拔 跋 鉢 潑 渤 撥 勃 魃 醱 孛 浡 鈸 鵓 哱 脖 钵 発 彂 綍 髪 沷 㪍 墢 茇 㧞 䟦 袚 襏 抜 犮 軷 㗶 柭 秡 胈 鏺 馞 䣮 发 拨 泼 袯 酦 钹 饽 鲅 鹁 侼 叐 坺 妭 愂 挬 桲 溌 炦 癶 癹 盋 砵 缽 荸 菝 葧 詙 蹳 郣 醗 鋍 餑 馛 驋 鮁 鱍 鼥 㔇 㔜 㛘 㛲 㞈 㟑 㧊 㴾 㶿 㺴 䍨 䘠 䚨 䟛 䢌 䣪 䥽 䪬 䭯 䮂 䯋 䳁 䳊
|
||||
밤 湴
|
||||
방 方 放 防 房 訪 邦 妨 芳 傍 倣 旁 彷 紡 龐 坊 榜 昉 謗 棒 枋 幇 肪 尨 膀 滂 蚌 磅 舫 蒡 仿 牓 髣
|
||||
배 北 拜 杯 輩 背 配 倍 培 排 裵 俳 賠 陪 徘 胚 湃 裴 盃 褙 焙 坏 坯 琲 扒 蓓 軰 环 桮 辈 拝 醅 禙
|
||||
방 方 放 防 房 訪 邦 妨 芳 傍 倣 旁 彷 紡 龐 坊 榜 昉 謗 棒 枋 幇 肪 尨 膀 滂 蚌 磅 舫 蒡 仿 牓 髣 厖 魴 搒 幫 螃 徬 旊 梆 舽 鎊 龎 霶 綁 逄 鈁 雱 狵 㮄 庬 哤 埅 胮 駹 昘 汸 浝 淓 眆 膖 蚄 㧍 䅭 䨦 帮 庞 纺 绑 访 谤 钫 镑 鲂 鳑 匚 嗙 垹 堏 塄 塝 嫎 幚 挷 氆 炐 牥 牻 玤 瓬 稖 篣 縍 耪 肨 艕 蛖 蜯 覫 趽 邡 邫 錺 鞤 髈 鰟 鴋 鶭 㑂 㕩 㕫 㝑 㤃 㤶 㥬 㨍 㫄 㭋 㭶 㿶 䂜 䄱 䏺 䏾 䒍 䖫 䠙 䢍
|
||||
배 北 拜 杯 輩 背 配 倍 培 排 裵 俳 賠 陪 徘 胚 湃 裴 盃 褙 焙 坏 坯 琲 扒 蓓 軰 环 桮 辈 拝 醅 禙 柸 毰 肧 㚰 赔 伓 俖 偝 啡 妚 嶏 掰 揹 炋 猅 琣 碚 荖 蓜 衃 輫 阫 韛 㔨 㗑 㟝 㠔 㤳 㮎 㯁 㰆 㶔 㶨 㶪 㻗 㾦 䋔 䋳 䒔 䔒 䣙 䪹 䫊 貝
|
||||
백 百 白 伯 栢 帛 魄 柏 佰 粨 苩 銆 洦 鲌 兡 岶 湐 瓸 竡 絔 鮊 㓦 㡍 㼟 㼣 㿟 䞟 䳆 珀
|
||||
뱀 㖱
|
||||
번 番 煩 繁 飜 磻 蕃 藩 燔 幡 樊 翻 蘩 繙 膰 袢 畨 璠 旛 轓 旙 瀿 笲 蹯 墦 橎 㺕 嬏 籓 蠜 烦 僠 勫
|
||||
번 番 煩 繁 飜 磻 蕃 藩 燔 幡 樊 翻 蘩 繙 膰 袢 畨 璠 旛 轓 旙 瀿 笲 蹯 墦 橎 㺕 嬏 籓 蠜 烦 僠 勫 噃 奿 棥 瀪 緐 羳 薠 襎 鐇 鑁 鱕 鷭 㸋 䉒 䋣 䋦 䌓 䕰 䪛 䪤 䫶 䮳
|
||||
벌 伐 罰 閥 筏 橃 罸 罚 阀 傠 垡 栰 浌 瞂 藅 㕹 㘺 䇅 䑔 䣹
|
||||
범 凡 犯 範 范 汎 泛 帆 梵 氾 颿 笵 訉 凢 軓 杋 㠶 舤 㕨 䭵 钒 凣 忛 柉 滼 盕 舧 諚 釩 㴀 䀀 䑺 䒦
|
||||
범 凡 犯 範 范 汎 泛 帆 梵 氾 颿 笵 訉 凢 軓 杋 㠶 舤 㕨 䭵 钒 凣 忛 柉 滼 盕 舧 諚 釩 㴀 䀀 䑺 䒦 䒮 䟪 䬚 渢
|
||||
법 法 琺 㳒 疺 珐 佱 灋 鍅
|
||||
벽 壁 碧 僻 闢 璧 癖 劈 擘 霹 蘗 檗 辟 擗 甓 椑 襞 疈 鷿 鼊 薜 皕 躄 躃 湢 澼 綼 鐴 䌟 鲾 䴙 噼 堛
|
||||
변 便 變 邊 辯 辨 卞 弁 騈 玭 籩 抃 徧 忭 汴 辮 駢 腁 匾 賆 萹 骿 鴘 拚 邉 変 釆 变 辺 昪 㺹 跰 胼
|
||||
별 別 瞥 鱉 鼈 鷩 彆 别 撇 蹩 莂 潎 䠥 瘪 鳖 丿 嫳 徶 憋 暼 癟 虌 蛂 蟞 襒 覕 鐅 龞 㔡 㢼 㿜 䇷 䋢
|
||||
병 兵 病 丙 竝 屛 秉 炳 柄 倂 昺 昞 甁 餠 幷 騈 浜 輧 棅 並 迸 駢 缾 搒 塀 絣 鈵 鉼 鋲 并 屏 併 餅
|
||||
보 步 保 報 寶 補 普 譜 輔 甫 逋 堡 潽 菩 洑 褓 珤 湺 靌 簠 俌 黼 葆 捗 鞴 鴇 盙 宝 寳 莆 歩 踄 椺
|
||||
복 福 服 復 伏 卜 腹 覆 複 馥 僕 撲 輻 匐 鰒 洑 宓 茯 蔔 輹 仆 扑 幞 濮 鵩 蝮 墣 蝠 箙 菔 袱 㒒 菐
|
||||
벽 壁 碧 僻 闢 璧 癖 劈 擘 霹 蘗 檗 辟 擗 甓 椑 襞 疈 鷿 鼊 薜 皕 躄 躃 湢 澼 綼 鐴 䌟 鲾 䴙 噼 堛 孹 廦 憵 挀 揊 檘 礔 礕 稫 糪 繴 釽 銢 鰏 鸊 㘠 㨽 㱸 㵨 㷶 䑀 䤨 䧗 䮠
|
||||
변 便 變 邊 辯 辨 卞 弁 騈 玭 籩 抃 徧 忭 汴 辮 駢 腁 匾 賆 萹 骿 鴘 拚 邉 変 釆 变 辺 昪 㺹 跰 胼 䪻 䮁 笾 苄 辩 辫 边 骈 匥 峅 汳 炞 犿 玣 甂 稨 糄 藊 覍 辡 閞 㛹 㝸 㣐 㦚 㭓 㲢 㳎 㵷 㻘 䉸 䐔 䒪 䛒 䟍
|
||||
별 別 瞥 鱉 鼈 鷩 彆 别 撇 蹩 莂 潎 䠥 瘪 鳖 丿 嫳 徶 憋 暼 癟 虌 蛂 蟞 襒 覕 鐅 龞 㔡 㢼 㿜 䇷 䋢 䌘 䏟 䘷 䟤 䤢 䥕 䨆 䭱 䳤 炦
|
||||
병 兵 病 丙 竝 屛 秉 炳 柄 倂 昺 昞 甁 餠 幷 騈 浜 輧 棅 並 迸 駢 缾 搒 塀 絣 鈵 鉼 鋲 并 屏 併 餅 逬 瓶 帲 邴 傡 陃 帡 怲 栟 竮 聠 㨀 乒 幈 恲 洴 甹 苪 蛃 蛢 軿 頩 䈂 饼 偋 垪 寎 庰 抦 捠 摒 椪 氞 琕 眪 窉 箳 簈 艵 荓 誁 踫 郱 鞆 鮩 鵧 㔙 㙃 㻂 䋑 䍈 䔊 䗒 䨻 䰃 䴵 䶄
|
||||
보 步 保 報 寶 補 普 譜 輔 甫 逋 堡 潽 菩 洑 褓 珤 湺 靌 簠 俌 黼 葆 捗 鞴 鴇 盙 宝 寳 莆 歩 踄 椺 賲 堢 藵 㙛 报 补 谱 辅 镨 鸨 埗 媬 寚 暜 歨 烳 煲 緥 蕔 蚥 諩 鐠 駂 鮬 鳵 㙅 㷛 㻄 㻉 㾟 䀯 䋠 䑰 䩉 䯙 䲕 䳈 䳰 䴐
|
||||
복 福 服 復 伏 卜 腹 覆 複 馥 僕 撲 輻 匐 鰒 洑 宓 茯 蔔 輹 仆 扑 幞 濮 鵩 蝮 墣 蝠 箙 菔 袱 㒒 菐 攴 葍 攵 栿 覄 楅 襆 醭 澓 复 辐 鳆 垘 棴 椱 獛 畐 畗 癁 稪 穙 絥 纀 蕧 虙 虲 襥 諨 贌 踾 蹼 轐 鍑 鍢 鮲 鳪 鶝 鸔 㓀 㙏 㚆 㠅 㬼 㯷 㲫 䃼 䇚 䈏 䑑 䕎 䕐 䗱 䞳 䟮 䧤 䨱 䪁 䫝 䴆 䵗
|
||||
본 本 呠 楍 絊 㡷 㮺 㯻 㶱 䬱
|
||||
볼 乶
|
||||
봉 奉 逢 封 鳳 峯 蜂 蓬 俸 縫 捧 鋒 烽 棒 峰 熢 琫 丰 綘 篷 葑 夆 菶 鴌 篈 鳯 塜 賵 芃 捀 艂 蠭 髼
|
||||
부 父 不 夫 部 富 復 否 婦 浮 扶 府 副 付 負 賦 培 赴 附 簿 符 腐 釜 傅 敷 訃 阜 膚 俯 賻 斧 剖 芙
|
||||
봉 奉 逢 封 鳳 峯 蜂 蓬 俸 縫 捧 鋒 烽 棒 峰 熢 琫 丰 綘 篷 葑 夆 菶 鴌 篈 鳯 塜 賵 芃 捀 艂 蠭 髼 唪 崶 淎 莑 鏠 凤 缝 赗 锋 仹 埄 埲 塳 妦 摓 桻 槰 樥 浲 湗 溄 漨 焨 犎 琒 甭 盽 竼 纄 蘕 覂 鎽 鑝 鞛 韸 韼 鬔 㛔 㜂 㠮 㡝 㦀 㶻 㷨 㷭 㷯 㸼 㻱 䀱 䆇 䋽 䒠 䗦 䗬 䙜 䡫 䧏 䩬 䩼 䭰 䮾 䳞 䴶
|
||||
부 父 不 夫 部 富 復 否 婦 浮 扶 府 副 付 負 賦 培 赴 附 簿 符 腐 釜 傅 敷 訃 阜 膚 俯 賻 斧 剖 芙 駙 腑 埠 咐 孵 孚 溥 鳧 缶 莩 趺 俛 艀 仆 祔 俘 鈇 拊 裒 桴 蔀 榑 掊 苻 鮒 蜉 袝 頫 抔 涪 麩 玞 跗 罘 蚨 筟 胕 罦 芣 媍 専 仅 痡 瓿 冨 棓 郛 釡 巭 滏 鳬 尃 怤 捊 柎 咅 暊 砆 秿 㭪 嚩 弣 捬 枎 犃 璷 萯 詂 錇 㓡 㠸 㷌 䔰 䝾 䫍 凫 妇 怀 肤 讣 负 赋 赙 锫 驸 鲋 麸 伕 偩 剻 勏 吥 呋 哹 坿 垺
|
||||
북 北 踣 僰 鉳
|
||||
분 分 憤 奮 粉 紛 奔 墳 頒 芬 焚 盆 忿 糞 噴 雰 扮 吩 賁 汾 昐 体 氛 棼 坌 犇 濆 蕡 枌 畚 朌 湓 轒
|
||||
불 不 佛 拂 弗 彿 黻 紱 髴 祓 茀 芾 岪 艴 韍 咈 巿 紼 怫 昢 刜 坲 帗 煘 笰 仏 払 绂 绋 韨 乀 冹 柫
|
||||
분 分 憤 奮 粉 紛 奔 墳 頒 芬 焚 盆 忿 糞 噴 雰 扮 吩 賁 汾 昐 体 氛 棼 坌 犇 濆 蕡 枌 畚 朌 湓 轒 帉 棻 砏 笨 肦 膹 黺 鼢 僨 蚡 黂 翂 衯 玢 秎 羵 鼖 燌 瓫 訜 豶 躮 鐼 饙 偾 喷 坟 愤 粪 纷 豮 贲 锛 鲼 倴 兝 喯 坋 奙 妢 岎 幩 弅 捹 撪 朆 桳 梤 橨 歕 泍 渀 瀵 炃 燓 獖 瓰 竕 羒 翉 翸 聁 苯 葐 蒶 蚠 軬 輽 逩 酚 鈖 錛 隫 餴 馚 馩 魵 鱝 㖹 㞣 㤋 㤓 㥹 㨧 㮥 㯣 㱵 㷊 㸮 㿎 䩿 䭻 䴅
|
||||
불 不 佛 拂 弗 彿 黻 紱 髴 祓 茀 芾 岪 艴 韍 咈 巿 紼 怫 昢 刜 坲 帗 煘 笰 仏 払 绂 绋 韨 乀 冹 柫 梻 氟 炥 甶 翇 鉘 鮄 㘬 㚕 㞸 㪄 㺻 䞞 䭮
|
||||
붓 㖚
|
||||
붕 朋 崩 鵬 棚 硼 繃 鬅 堋 漰 萠 弸 塴 掤 椖 綳 绷 镚 鹏 倗 傰 嘣 焩 痭 蹦 鏰 㥊 㬟 㵯 㻚 䙖 䨜
|
||||
비 非 鼻 悲 備 比 飛 秘 費 妃 卑 碑 肥 批 婢 匪 丕 毖 泌 毘 鄙 扉 譬 庇 臂 裨 沸 憊 緋 脾 誹 痺 蜚
|
||||
빈 貧 賓 頻 彬 嬪 殯 濱 嚬 瀕 贇 牝 斌 浜 玭 檳 鬢 擯 儐 蘋 邠 苹 顰 繽 豳 霦 鑌 鬂 矉 臏 賔 䎙 份
|
||||
비 非 鼻 悲 備 比 飛 秘 費 妃 卑 碑 肥 批 婢 匪 丕 毖 泌 毘 鄙 扉 譬 庇 臂 裨 沸 憊 緋 脾 誹 痺 蜚 琵 匕 砒 翡 秕 賁 毗 菲 斐 粃 榧 枇 妣 俾 祕 閟 霏 轡 圮 沘 騑 羆 髀 棐 睥 痞 鼙 邳 紕 貔 贔 悱 椑 腓 庳 陴 仳 芘 淝 篦 芾 濞 蚍 狒 剕 埤 萆 鞴 鵯 痹 屁 淠 狉 蓖 郫 騛 篚 畀 疪 禆 庀 痱 裶 啚 媲 鞞 伾 奰 秠 柲 糒 豼 䀝 嚭 渄 蠯 䚹 俻 偹 厞 婓 嫓 屝 昲 箄 聛 膍 陫 馡 㩌 䤵 备 惫 纰 绯 罴
|
||||
빈 貧 賓 頻 彬 嬪 殯 濱 嚬 瀕 贇 牝 斌 浜 玭 檳 鬢 擯 儐 蘋 邠 苹 顰 繽 豳 霦 鑌 鬂 矉 臏 賔 䎙 份 馪 璸 髩 薲 蠙 濵 梹 傧 嫔 宾 摈 槟 殡 滨 濒 缤 膑 贫 镔 频 颦 髌 鬓 㺍 娦 朩 椕 獱 瑸 礗 穦 豩 顮 驞 髕 㟗 㡦 㯽 㰋 㻞 䚔 䧬 䨈
|
||||
빙 氷 聘 馮 憑 騁 冰 凭 娉 凴 俜 砯 䀻 骋 仌 冫 慿 泵 涄 淜 靐 㓈 㵗 䛣
|
||||
뿐 兺 哛
|
||||
사 四 事 食 士 死 思 使 史 私 寺 師 仕 絲 舍 謝 巳 射 司 辭 似 賜 社 斯 祀 査 沙 詞 寫 捨 邪 詐 斜
|
||||
사 四 事 食 士 死 思 使 史 私 寺 師 仕 絲 舍 謝 巳 射 司 辭 似 賜 社 斯 祀 査 沙 詞 寫 捨 邪 詐 斜 蛇 赦 祠 泗 埈 獅 飼 唆 嗣 徙 紗 些 奢 瀉 娑 麝 簑 肆 俟 乍 伺 蓑 砂 莎 駟 裟 梭 篩 柶 渣 僿 槎 竢 蜡 糸 笥 榭 騃 姒 卸 闍 撕 楂 駛 缷 剚 咋 汜 傞 覗 鯊 崽 痧 皶 魦 鰤 辞 查 賖 涘 耜 蓰 屣 冩 簁 賒 挲 辤 禠 舎 虵 辝 髿 禩 肂 衺 柤 厙 嗄 漇 蹝 戺 柌 蒒 蕬 鍦 䣃 写 泻 丝 厍 咝 师 狮 筛 纱
|
||||
삭 數 削 索 朔 鑠 槊 爍 箾 蒴 矟 搠 烁 铄 嗍 揱 溹 獡 鎙 㩂 㮶 䀥 䁻 䅴 䌇 䖛
|
||||
산 山 産 散 算 酸 傘 刪 珊 疝 霰 蒜 汕 繖 訕 餕 潸 毿 狻 鏟 澘 剷 橵 姍 孿 閊 產 筭 删 㪚 跚 滻 㦃
|
||||
산 山 産 散 算 酸 傘 刪 珊 疝 霰 蒜 汕 繖 訕 餕 潸 毿 狻 鏟 澘 剷 橵 姍 孿 閊 產 筭 删 㪚 跚 滻 㦃 仐 痠 隡 产 铲 伞 姗 孪 毵 浐 讪 闩 馓 匴 厁 圸 壭 嵼 摌 杣 潵 狦 祘 笇 簅 糤 舢 藖 赸 軕 邖 鏾 閂 饊 㛽 㢫 㪔 㮼 㯆 㹌 㹪 䃟 䈀 䉈 䊲 䐮 䚲 䝜 䡲 䨷 䴮
|
||||
살 殺 蔡 薩 撒 煞 乷 杀 摋 㐊 蔱 萨 榝 樧 脎 虄 閷 䊛
|
||||
삼 三 參 蔘 森 滲 渗 衫 芟 杉 叅 纔 摻 毿 糝 鬖 椙 櫼 釤 彡 縿 槮 襂 穇 穼 糁 钐 仨 俕 剼 嘇 幓 弎
|
||||
삽 揷 澁 颯 鈒 歃 翣 鍤 卅 霎 霅 唼 挿 澀 鎝 插 牐 箑 靸 趿 涩 渋 钑 锸 飒 偛 喢 帹 歰 甴 箿 翜 臿
|
||||
상 上 相 想 常 賞 商 尙 喪 傷 霜 狀 嘗 詳 祥 象 床 桑 像 償 裳 庠 箱 爽 觴 翔 孀 廂 湘 牀 橡 嫦 瀧
|
||||
삼 三 參 蔘 森 滲 渗 衫 芟 杉 叅 纔 摻 毿 糝 鬖 椙 櫼 釤 彡 縿 槮 襂 穇 穼 糁 钐 仨 俕 剼 嘇 幓 弎 曑 椮 檆 毝 涁 犙 甧 糂 糣 罧 葠 蓡 薓 贂 鏒 閐 㡎 㣌 㧲 㭅 㰑 㵕 㺑 䀐 䅟 䊉 䊏 䥇 䦂 䫅 䫩 䫮 䵇
|
||||
삽 揷 澁 颯 鈒 歃 翣 鍤 卅 霎 霅 唼 挿 澀 鎝 插 牐 箑 靸 趿 涩 渋 钑 锸 飒 偛 喢 帹 歰 甴 箿 翜 臿 萐 譅 遪 雭 馺 㒎 㚫 㛼 㩑 㪪 㰱 㴙 㵤 㽂 䈉 䔼 䙣 䛽 䝊 䬃 䬊 䮜 䮢
|
||||
상 上 相 想 常 賞 商 尙 喪 傷 霜 狀 嘗 詳 祥 象 床 桑 像 償 裳 庠 箱 爽 觴 翔 孀 廂 湘 牀 橡 嫦 瀧 峠 倘 甞 殤 顙 徜 緗 晌 塽 鎟 鬺 状 尚 愓 恦 垧 厢 瑺 礵 丧 鋿 鏛 扄 謪 鸘 䘮 鱨 樉 贘 驦 鮝 㫾 绱 鲞 伤 偿 墒 尝 殇 缃 觞 详 赏 颡 骦 鲿 鹴 丄 仩 傸 嗓 嚐 嶑 慡 慯 搡 桒 桛 槡 滳 漡 漺 潒 灀 磉 絴 緔 縔 葙 蔏 螪 蟐 蟓 蠰 褬 襐 鏯 鐌 鑜 騻 鯗 鱌 鱶 鷞 㜀 㟄 㦂 㦼 䉘 䌮 䒙 䔗 䔪 䗮 䡦 䫙
|
||||
새 塞 璽 賽 鰓 崽 壐 玺 赛 鳃 噻 愢 瓕 簺 㗷 㘔 䈢 䚡 䰄 䵘
|
||||
색 色 塞 索 嗇 薔 穡 愬 槭 濇 瀒 賾 摵 矠 繬 鎍 啬 穑 赜 铯 嗦 懎 擌 栜 歮 歵 泎 洓 瘷 穯 轖 銫 齚
|
||||
색 色 塞 索 嗇 薔 穡 愬 槭 濇 瀒 賾 摵 矠 繬 鎍 啬 穑 赜 铯 嗦 懎 擌 栜 歮 歵 泎 洓 瘷 穯 轖 銫 齚 齰 㣱 㥶 㩙 㮦 㱇 㾊 䂹 䉢 䊂 䞽
|
||||
생 生 省 牲 甥 笙 眚 鉎 泩 栍 鼪 殅 湦 珄 苼 貹 﨡 㗂 㽒 㽓 䚇 䲼 䴤
|
||||
서 西 書 序 暑 庶 徐 署 恕 緖 誓 敍 逝 瑞 舒 曙 棲 胥 黍 鼠 犀 薯 嶼 抒 壻 栖 筮 鋤 絮 墅 捿 婿 叙
|
||||
석 石 夕 席 昔 惜 釋 析 錫 奭 碩 晳 潟 汐 淅 蓆 腊 舃 蜥 鉐 晰 矽 鼫 释 穸 螫 祏 裼 焬 皙 襫 邜 桞
|
||||
선 先 仙 善 單 線 船 選 鮮 洗 宣 旋 禪 膳 繕 扇 煽 璿 璇 瑄 癬 腺 亘 羨 銑 蟬 詵 饍 嬋 霰 僊 蘚 跣
|
||||
설 雪 說 設 舌 泄 薛 卨 屑 洩 渫 褻 楔 齧 偰 揲 爇 紲 稧 媟 碟 暬 挈 絏 䙝 枻 囓 焫 躠 緤 褉 樰 㻡
|
||||
섬 暹 纖 蟾 陝 殲 閃 贍 剡 憸 摻 銛 譫 睒 韱 孅 笘 殱 爓 纎 棎 掞 㴸 䃸 纤 掺 歼 繊 谵 赡 铦 闪 嬐
|
||||
섭 葉 涉 攝 燮 躡 懾 歙 囁 聶 鑷 灄 顳 爕 摂 讋 屧 籋 渉 夑 鍱 欇 鞢 躞 嗫 慑 摄 滠 聂 蹑 镊 颞 屟
|
||||
성 成 星 省 性 姓 城 誠 聖 聲 盛 晟 醒 宬 惺 腥 珹 猩 筬 騂 餳 瑆 渻 垶 峸 狌 晠 煋 郕 鋮 偗 箵 絾
|
||||
세 世 說 歲 細 洗 勢 稅 貰 笹 洒 繐 蛻 帨 税 嵗 歳 挩 裞 檅 涗 䵻 岁 细 贳 丗 亗 卋 朑 涚 祱 蔧 跩
|
||||
소 小 少 所 笑 消 素 召 昭 訴 掃 蘇 燒 騷 疎 蔬 巢 紹 邵 沼 蕭 宵 逍 簫 梢 搔 遡 梳 塑 甦 瘙 疏 嘯
|
||||
속 速 俗 續 束 屬 粟 贖 涑 謖 洬 遬 属 蔌 餗 觫 鋉 梀 誎 簌 続 续 谡 赎 僳 憟 樕 殐 潥 藗 藚 趚 㑛
|
||||
서 西 書 序 暑 庶 徐 署 恕 緖 誓 敍 逝 瑞 舒 曙 棲 胥 黍 鼠 犀 薯 嶼 抒 壻 栖 筮 鋤 絮 墅 捿 婿 叙 紓 噬 撕 鉏 湑 澨 芧 揟 耡 敘 遾 閪 溆 醑 諝 蜍 㥠 漵 潻 庻 糈 緒 偦 俆 圕 垿 屖 悇 䋒 䑕 书 屿 纾 绪 谞 锄 卥 唑 嫬 徆 恓 楈 樨 氥 汿 沀 潊 濖 瑹 癙 硒 稰 簭 粞 糬 縃 聓 聟 舾 蒣 藇 蝑 蠴 觢 迉 鏣 魣 鱪 鱮 鱰 鵨 鼡 㐨 㓾 㘧 㛉 㜿 㠘 㢴 㣽 㭀 㯕 㵂 㵰 㷂 㽰 㾷 䇴 䈝 䉀 䋡 䍱 䓚 䤱 䦽 䧈
|
||||
석 石 夕 席 昔 惜 釋 析 錫 奭 碩 晳 潟 汐 淅 蓆 腊 舃 蜥 鉐 晰 矽 鼫 释 穸 螫 祏 裼 焬 皙 襫 邜 桞 棤 檡 炻 睗 舄 硕 釈 锡 佦 冟 匇 唽 嬕 惁 扸 沯 澙 焟 磶 緆 菥 蕮 褯 釸 鮖 㒪 㙽 㛫 㛭 㝜 㫺 㭊 㱤 䁺 䂖 䂙 䂞 䄷 䖨 䨛 䲽
|
||||
선 先 仙 善 單 線 船 選 鮮 洗 宣 旋 禪 膳 繕 扇 煽 璿 璇 瑄 癬 腺 亘 羨 銑 蟬 詵 饍 嬋 霰 僊 蘚 跣 鐥 渲 琁 敾 湺 尠 燹 譔 綫 鏇 騸 愃 墡 筅 脧 駪 熯 鱻 姍 姺 嫙 尟 屳 歚 譱 鱓 縇 鲜 缮 墠 獮 躚 僎 蟺 嬗 揎 蟮 襈 軐 㶍 㷽 䆄 僐 漩 舩 鍌 鱔 㵛 禅 线 铣 婵 狝 癣 籼 藓 蝉 诜 跹 选 镟 骟 鳝 傓 僲 塇 奾 嫸 廯 搧 暶 檈 毨 氙 灗 灷 烍 珗 碹 磰 禒 秈 箲 絤 縼 繏 缐 苮 蔙 蜁 褼 謆 赻 跴 蹮 鄯
|
||||
설 雪 說 設 舌 泄 薛 卨 屑 洩 渫 褻 楔 齧 偰 揲 爇 紲 稧 媟 碟 暬 挈 絏 䙝 枻 囓 焫 躠 緤 褉 樰 㻡 说 亵 绁 设 鳕 乴 伳 僁 哾 抴 揳 榍 疶 禼 糏 絬 膤 艝 蔎 蛥 説 轌 辥 辪 鱈 㐥 㓭 㔎 㞕 㡜 㣯 㨝 㳿 㴮 㴽 㿱 䝟 䥹 䨮 䱑 契 㨹
|
||||
섬 暹 纖 蟾 陝 殲 閃 贍 剡 憸 摻 銛 譫 睒 韱 孅 笘 殱 爓 纎 棎 掞 㴸 䃸 纤 掺 歼 繊 谵 赡 铦 闪 嬐 撡 攕 晱 炶 熌 燅 襳 覢 讝 㚒 㣣 㨛 㩥 㪎 㰇 㰊 㺤 䀹 䁡 䃱 䆎 䊱 䊹 䕭 䠾 䯭 䯹
|
||||
섭 葉 涉 攝 燮 躡 懾 歙 囁 聶 鑷 灄 顳 爕 摂 讋 屧 籋 渉 夑 鍱 欇 鞢 躞 嗫 慑 摄 滠 聂 蹑 镊 颞 屟 弽 徢 疌 蠂 詟 讘 踂 踙 銸 韘 㒤 㦪 㰔 㰼 㴇 㸉 㸎 㽊 䜓 䢡 䤮 䯀 䯅
|
||||
성 成 星 省 性 姓 城 誠 聖 聲 盛 晟 醒 宬 惺 腥 珹 猩 筬 騂 餳 瑆 渻 垶 峸 狌 晠 煋 郕 鋮 偗 箵 絾 荿 謃 声 胜 诚 铖 骍 墭 娍 曐 殸 皨 睲 窚 篂 觪 觲 鍟 鮏 鯎 鯹 㙚 㝭 㨘 㮐 㷣 㼩 㼳 㽮 㾪 䁞 䃏 䇈 䗌 䚖 䫆 貹
|
||||
세 世 說 歲 細 洗 勢 稅 貰 笹 洒 繐 蛻 帨 税 嵗 歳 挩 裞 檅 涗 䵻 岁 细 贳 丗 亗 卋 朑 涚 祱 蔧 跩 軎 轊 銴 齛 齥 㑕 㔟 㔺 㢷 㱑 㸷 䬽 忕 彗
|
||||
소 小 少 所 笑 消 素 召 昭 訴 掃 蘇 燒 騷 疎 蔬 巢 紹 邵 沼 蕭 宵 逍 簫 梢 搔 遡 梳 塑 甦 瘙 疏 嘯 銷 韶 瀟 炤 溯 釗 篠 踈 艘 霄 泝 埽 翛 愬 鞘 酥 繅 卲 劭 蛸 俏 捎 魈 佋 箾 嗉 筱 塐 樔 膆 鮹 鰺 䟽 㪽 咲 穌 焇 焼 销 熽 筲 謏 蠨 傃 㴑 玿 痟 矂 喿 璅 騒 㗛 愫 慅 旓 櫹 歗 溸 琑 苏 关 啸 巣 扫 潇 烧 稣 箫 绍 缫 萧 蟏 诉 骚 鲹 鳋 嘨 囌 娋 嫊 尕 巶 弰 彇 掻 柖 榡 櫯 毜 氉 溞 漅 潲 牊 窼 笤
|
||||
속 速 俗 續 束 屬 粟 贖 涑 謖 洬 遬 属 蔌 餗 觫 鋉 梀 誎 簌 続 续 谡 赎 僳 憟 樕 殐 潥 藗 藚 趚 㑛 㔄 㬘 㯈 䅇 䌚 䔎 䔩 䞖 䥔 䲇 䴰
|
||||
손 孫 損 遜 巽 飡 蓀 飧 飱 喰 噀 孙 损 狲 荪 逊 巺 愻 搎 槂 潠 猻 蕵 薞 顨 㢲 䁚 䐣 䕖
|
||||
솔 率 蟀 窣 卛 乺 摔 甩 碿 蜶 㲞 䢦 帥
|
||||
솟 㕾
|
||||
송 送 松 訟 誦 頌 宋 悚 淞 竦 柗 鬆 菘 荣 㩳 㞞 讼 诵 锶 颂 倯 凇 庺 彸 愯 憽 捒 枀 枩 梥 楤 檧 蜙
|
||||
송 送 松 訟 誦 頌 宋 悚 淞 竦 柗 鬆 菘 荣 㩳 㞞 讼 诵 锶 颂 倯 凇 庺 彸 愯 憽 捒 枀 枩 梥 楤 檧 蜙 鍶 鎹 餸 駷 㧐 㨦 㬝 㮤 㮸 㹐 䛦 䢠 䯳
|
||||
솨 惢 䐝
|
||||
솰 唰
|
||||
쇄 殺 刷 鎖 灑 碎 洒 瑣 曬 惢 晒 鎻 鏁 锁 縰 鎩 嗩 埣 㬠 唢 琐 铩 嗮 涮 溑 砕 繀 㕞 䈗 䞆 䣔 䵀
|
||||
쇠 衰 釗 钊 哸 夊 鞖 㲤 䪎
|
||||
수 水 手 數 守 首 樹 受 授 收 雖 修 壽 須 誰 秀 愁 遂 隨 囚 殊 輸 需 獸 垂 帥 洙 睡 搜 隋 髓 銖 蒐
|
||||
숙 宿 叔 淑 孰 熟 肅 夙 菽 塾 璹 潚 琡 倏 俶 橚 儵 鷫 驌 婌 䎘 璛 埱 蓿 倐 焂 鱐 玊 掓 㶖 䃤 䨹 粛
|
||||
순 順 純 脣 巡 循 旬 殉 瞬 舜 淳 珣 盾 洵 筍 醇 馴 荀 唇 詢 諄 徇 恂 蓴 楯 錞 焞 蕣 栒 橓 肫 鶉 紃
|
||||
수 水 手 數 守 首 樹 受 授 收 雖 修 壽 須 誰 秀 愁 遂 隨 囚 殊 輸 需 獸 垂 帥 洙 睡 搜 隋 髓 銖 蒐 酬 羞 繡 粹 袖 瘦 讐 竪 嗾 狩 嫂 穗 脩 戍 綏 藪 綬 邃 鬚 岫 燧 隧 涑 嗽 漱 琇 峀 茱 璲 蓚 銹 叟 讎 售 豎 晬 睟 陲 睢 祟 莠 瞍 颼 殳 酥 廋 溲 膄 脺 泅 濉 籔 膸 饈 随 収 数 捜 薮 虽 髄 綉 襚 瀡 倕 滫 䢘 詶 繻 鎪 䜿 誶 潄 稤 篲 酧 䏂 惥 擻 橾 櫢 穂 菙 訹 醻 扌 檖 腄 㒸 䆳 夀 栦 澻 痩 禭 雔
|
||||
숙 宿 叔 淑 孰 熟 肅 夙 菽 塾 璹 潚 琡 倏 俶 橚 儵 鷫 驌 婌 䎘 璛 埱 蓿 倐 焂 鱐 玊 掓 㶖 䃤 䨹 粛 肃 骕 鹔 儴 尗 摍 珟 莤 虪 諔 跾 鮛 㑉 㑐 㓘 㜚 㝛 㩋 㪩 㳤 㴋 㴼 䃞 䏋 䑿 䱙
|
||||
순 順 純 脣 巡 循 旬 殉 瞬 舜 淳 珣 盾 洵 筍 醇 馴 荀 唇 詢 諄 徇 恂 蓴 楯 錞 焞 蕣 栒 橓 肫 鶉 紃 狥 鬊 眴 侚 盹 駨 笋 峋 廵 輴 漘 㥧 㦏 䋸 䥎 莼 纯 询 谆 顺 驯 鹑 鹠 伨 偱 咰 奞 姰 憌 揗 杊 榫 槆 毥 浱 湻 滣 潃 犉 畃 瞚 瞤 碷 磭 箰 簨 芛 蒓 衠 賐 郇 醕 陙 鯙 㒐 㔼 㚝 㝄 㝇 㡄 㨚 㫬 㬀 㰬 㵌 㵮 㶷 㸪 㽦 䀢 䀵 䏛 䐇 䑞 䓐 䔚 䖲 䘩 䙉 䜦 䣨 䴄
|
||||
술 戌 述 術 鉥 卹 潏 絉 銊 㺷 荗 珬 沭 欰 蒁 袕 錰 鶐 㖅 㞊 㾁 䢞 䢤 䮅 䳳
|
||||
숭 崇 嵩 崧 滕 菘 硹 㓽 㟣 㣝 䯷
|
||||
쉬 倅 晬 淬 焠 伜 琗 㳃 䘹
|
||||
슬 瑟 膝 蝨 虱 飋 㻭 厀 鲺 濏 璱 鯴 㺩 㻎
|
||||
습 習 拾 襲 濕 褶 隰 慴 熠 湿 霫 謵 习 袭 鳛 啃 嶍 槢 溼 漝 飁 騽 鰼 㒊 㗩 㠄 㦻 㿇 䏉 䒁 䗑
|
||||
승 勝 承 乘 僧 昇 升 繩 丞 陞 蠅 塍 鬙 乗 蝇 䋲 鱦 椉 氶 㱡 嵊 憴 縄 绳 冼 呏 堘 抍 斘 枡 桝 榺 橳
|
||||
시 十 時 始 示 市 寺 視 詩 試 是 施 侍 矢 柴 屍 諡 弑 匙 猜 豺 媤 柿 尸 恃 蓍 謚 豕 翅 屎 嘶 蒔 昰
|
||||
식 食 式 植 識 息 飾 湜 殖 軾 蝕 拭 熄 寔 埴 篒 栻 喰 媳 鉽 烒 蚀 轼 饣 饰 煶 瘜 禃 籂 蒠 遈 鄎 鎴
|
||||
신 身 信 臣 新 神 申 辰 辛 愼 伸 晨 紳 訊 迅 腎 燼 宸 薪 呻 蜃 娠 藎 莘 侁 汕 矧 贐 哂 汛 頣 脤 駪
|
||||
승 勝 承 乘 僧 昇 升 繩 丞 陞 蠅 塍 鬙 乗 蝇 䋲 鱦 椉 氶 㱡 嵊 憴 縄 绳 冼 呏 堘 抍 斘 枡 桝 榺 橳 洆 溗 焺 畻 竔 脀 蕂 蝿 譝 阩 陹 騬 鵿 㐼 㞼 㮱 㴍 䒏 䮪 䱆
|
||||
시 十 時 始 示 市 寺 視 詩 試 是 施 侍 矢 柴 屍 諡 弑 匙 猜 豺 媤 柿 尸 恃 蓍 謚 豕 翅 屎 嘶 蒔 昰 枾 啻 厮 緦 澌 廝 柹 偲 禔 撕 諟 兕 豉 塒 顋 枲 諰 釃 翤 鍉 絁 腮 颸 弒 䌤 眎 鳲 凘 翨 䲭 乨 毸 漦 襹 揌 犲 褷 䂠 埘 时 缌 莳 视 试 诗 酾 铈 飔 鲥 鸤 兘 呞 咶 宩 崼 嵵 徥 戠 揓 旹 榯 毢 沶 湤 溡 眂 礻 祡 笶 簛 罳 翄 膪 葈 葹 邿 鈰 鉃 鰣 鸍 鼭 㒋 㒮 㒾 㔭 㖷 㚸 㞔 㣈 㫭 㮛 㳏 㹝 㺇 㻢 䈕
|
||||
식 食 式 植 識 息 飾 湜 殖 軾 蝕 拭 熄 寔 埴 篒 栻 喰 媳 鉽 烒 蚀 轼 饣 饰 煶 瘜 禃 籂 蒠 遈 鄎 鎴 飠 餝 㥀 㮩 㴧 㵓 䤭 䭒
|
||||
신 身 信 臣 新 神 申 辰 辛 愼 伸 晨 紳 訊 迅 腎 燼 宸 薪 呻 蜃 娠 藎 莘 侁 汕 矧 贐 哂 汛 頣 脤 駪 噺 囟 姺 慎 抻 賮 兟 伩 柛 璶 㾕 峷 鋠 㜪 烬 绅 肾 荩 讯 识 赆 卂 吲 妽 孞 屾 弞 扟 敒 昚 曟 榊 氠 烥 燊 珅 甡 眒 眘 矤 砷 祳 笉 籶 籸 胂 茞 莀 裑 訙 訠 訫 訷 鉮 阠 顖 鯓 鰰 鵢 鷐 麎 㐰 㑗 㚨 㛙 㛛 㢹 㫳 㭡 㰮 㳯 㶦 㶳 䅸 䆣 䊁 䒖 䚱 䛜 䛨 䝲 䢅 䢻 䣅 䪿 䭀 䯂 䰠
|
||||
실 室 失 實 悉 蟋 飋 窸 实 実 怸 榁 螅 鏭 鰘 㗭 㣰 㺙 䊝 䶡
|
||||
심 心 甚 深 沈 尋 審 瀋 沁 諶 芯 沉 潯 梣 椹 燖 葚 鐔 鱏 淰 愖 諗 杺 伈 蕈 㴱 襑 㝷 宷 枔 鬵 煁 瞫
|
||||
심 心 甚 深 沈 尋 審 瀋 沁 諶 芯 沉 潯 梣 椹 燖 葚 鐔 鱏 淰 愖 諗 杺 伈 蕈 㴱 襑 㝷 宷 枔 鬵 煁 瞫 覾 㻣 吣 婶 审 寻 浔 渖 谂 谌 镡 鲟 㖊 侺 吢 噚 堔 嬸 忄 憳 攳 桪 樳 橬 璕 瘎 蟳 讅 谉 邥 鄩 鈊 魫 鱘 㔤 㚞 㚯 㜄 㜦 㣺 㤈 㰂 㶒 㽸 䉇 䚓 䤁
|
||||
십 十 拾 什 辻 兙 卌 椞 瓧 竍 籵 趇 䦹
|
||||
쌀 㐘
|
||||
쌍 雙 双 䨇 艭 孇 欆 﨎 㕠 䉶 䝄
|
||||
씨 氏
|
||||
씻 㘒
|
||||
아 兒 我 雅 亞 餓 芽 牙 阿 衙 俄 訝 啞 鴉 峨 娥 鵝 莪 蛾 迓 疴 哦 婀 丫 峩 娿 砑 笌 錏 鵞 痾 児 婭
|
||||
악 樂 惡 岳 握 愕 堊 顎 嶽 幄 渥 鰐 鄂 鍔 齷 萼 諤 鶚 咢 喔 噩 卾 腭 偓 覨 齶 楽 鸑 遌 崿 蕚 㮙 堮
|
||||
안 安 案 眼 顔 岸 雁 鞍 按 晏 鮟 鴈 犴 贋 桉 顏 鷃 贗 䬶 楌 㷳 䅁 赝 颜 铵 顸 侒 偐 垵 堓 妟 姲 婩
|
||||
알 謁 閼 軋 斡 遏 訐 戞 頞 揠 鴶 嘎 穵 戛 歹 嶭 猰 圠 捾 堨 钀 䵝 讦 谒 轧 阏 劜 咹 嘠 屵 挖 擜 歺
|
||||
암 暗 巖 癌 庵 闇 岩 菴 俺 唵 諳 黯 嵓 頷 馣 晻 媕 啽 腤 葊 蓭 嵒 碞 韽 揞 黭 痷 萻 鵪 黤 䳺 埯 谙
|
||||
압 壓 押 鴨 岬 狎 礏 罨 鸭 庒 岋 姶 㔩 䑪 压 圧 儑 匎 圔 庘 曱 炠 砐 硆 鞥 鰪 㕎 㛕 㳌 㷈 㾎 䆘 䑥
|
||||
앙 仰 殃 央 怏 昻 鴦 秧 盎 鞅 泱 卬 坱 昂 枊 䭹 抰 胦 㟅 䀚 鸯 佒 傟 咉 姎 岇 岟 慃 炴 眏 紻 羻 詇
|
||||
애 愛 哀 涯 礙 埃 艾 喝 崖 隘 靄 曖 厓 碍 藹 欸 騃 睚 皚 瞹 挨 僾 薆 靉 捱 獃 磑 娭 唉 啀 噯 崕 漄
|
||||
액 額 厄 液 腋 扼 縊 掖 阨 搤 戹 呝 詻 軛 頟 缢 轭 额 峉 涱 砨 磀 苊 蚅 豟 貖 軶 鈪 阸 㟯 㧖 䘸 䝈
|
||||
아 兒 我 雅 亞 餓 芽 牙 阿 衙 俄 訝 啞 鴉 峨 娥 鵝 莪 蛾 迓 疴 哦 婀 丫 峩 娿 砑 笌 錏 鵞 痾 児 婭 亜 椏 稏 妸 鵶 俹 聣 䋍 亚 哑 垭 娅 挜 桠 氩 痖 讶 钶 铔 锕 饿 鸦 鹅 䅉 伢 厊 吖 唖 啊 妿 孲 屙 庌 掗 氬 涐 犽 玡 珴 疨 瘂 皒 睋 硪 蕥 蚜 襾 覀 誐 鈳 錒 鐚 騀 齖 㚳 㝞 㢌 㢐 㦱 㧎 㧴 㰳 㿿 䃁 䄉 䄰 䋩 䋪 䍓 䖸 䢝 䪵 䳗 䳘 猗 枒
|
||||
악 樂 惡 岳 握 愕 堊 顎 嶽 幄 渥 鰐 鄂 鍔 齷 萼 諤 鶚 咢 喔 噩 卾 腭 偓 覨 齶 楽 鸑 遌 崿 蕚 㮙 堮 恶 鳄 乐 垩 悪 谔 锷 颚 鹗 龌 偔 僫 捳 楃 櫮 湂 琧 腛 蝁 讍 鑩 鱷 㓵 㖾 㗁 㟧 㠋 㦍 㹊 䠎 䣞 䥃 䫷
|
||||
안 安 案 眼 顔 岸 雁 鞍 按 晏 鮟 鴈 犴 贋 桉 顏 鷃 贗 䬶 楌 㷳 䅁 赝 颜 铵 顸 侒 偐 垵 堓 妟 姲 婩 峖 暥 氨 洝 矸 荌 軅 銨 錌 鞌 頇 騴 鳫 鴳 黫 㗴 㘖 㟁 㫨 㸩 䀂 䢿 䨃 䮗 䯃 䳛 䭓
|
||||
알 謁 閼 軋 斡 遏 訐 戞 頞 揠 鴶 嘎 穵 戛 歹 嶭 猰 圠 捾 堨 钀 䵝 讦 谒 轧 阏 劜 咹 嘠 屵 挖 擜 歺 濣 焥 窫 聐 胺 錷 齃 齾 﨟 㖕 㖖 㧉 㩵 㷎 䀑 䁊 䔾 䥟 䮸 䯉 䯵 䰲
|
||||
암 暗 巖 癌 庵 闇 岩 菴 俺 唵 諳 黯 嵓 頷 馣 晻 媕 啽 腤 葊 蓭 嵒 碞 韽 揞 黭 痷 萻 鵪 黤 䳺 埯 谙 颔 鹌 匼 啱 堷 壧 巌 巗 盦 盫 礹 罯 裺 誝 隌 雸 鶕 麙 麣 黬 㘙 㛺 㜝 㞄 㽢 䅖 䖗 䨄 䫡 䬓 䯥
|
||||
압 壓 押 鴨 岬 狎 礏 罨 鸭 庒 岋 姶 㔩 䑪 压 圧 儑 匎 圔 庘 曱 炠 砐 硆 鞥 鰪 㕎 㛕 㳌 㷈 㾎 䆘 䑥 䜙
|
||||
앙 仰 殃 央 怏 昻 鴦 秧 盎 鞅 泱 卬 坱 昂 枊 䭹 抰 胦 㟅 䀚 鸯 佒 傟 咉 姎 岇 岟 慃 炴 眏 紻 羻 詇 軮 醠 鉠 雵 駚 㒕 㭿 㹧 㼜 㿮 䄃 䇦 䒋 䒢 䘧 䩕 䬬 䭺 䱀
|
||||
애 愛 哀 涯 礙 埃 艾 喝 崖 隘 靄 曖 厓 碍 藹 欸 騃 睚 皚 瞹 挨 僾 薆 靉 捱 獃 磑 娭 唉 啀 噯 崕 漄 瑷 敱 爱 閡 㝵 壒 䔽 譪 呃 毐 璦 㣻 嗳 嫒 暧 皑 硙 蔼 锿 阂 霭 伌 叆 哎 唲 嘊 堐 塧 娾 嬡 嵦 懓 懝 敳 昹 濭 燰 猚 皧 砹 硋 賹 銰 鎄 鑀 餲 馤 鱫 鴱 㕌 㗒 㘷 㤅 㦈 㱯 㶼 㾏 㿄 䅬 䕏 䝽 䨠 䬵 䶣
|
||||
액 額 厄 液 腋 扼 縊 掖 阨 搤 戹 呝 詻 軛 頟 缢 轭 额 峉 涱 砨 磀 苊 蚅 豟 貖 軶 鈪 阸 㟯 㧖 䘸 䝈 䩹 䱮
|
||||
앳 厑
|
||||
앵 鶯 櫻 鸚 罌 嚶 鷪 罃 嫈 鸎 甖 䴍 嘤 桜 樱 罂 莺 鹦 奣 甇 譻 㴄 㹙 㹚 䁝 䨉
|
||||
야 夜 野 也 若 射 耶 惹 倻 椰 爺 冶 揶 埜 捓 枒 喏 琊 鋣 嘢 焲 瑘 䤳 爷 铘 乛 亱 偌 吔 壄 婼 擨 漜
|
||||
약 弱 藥 約 若 略 躍 掠 葯 蒻 鑰 籥 禴 爚 篛 鰯 龠 鶸 瀹 礿 㵸 箬 箹 蘥 㿑 药 哟 约 薬 跃 叒 喲 嵶
|
||||
양 羊 兩 陽 洋 良 養 量 讓 揚 凉 梁 樣 糧 楊 壤 諒 亮 襄 孃 瘍 攘 釀 恙 禳 痒 佯 暘 穰 瀁 敭 煬 癢
|
||||
어 魚 語 漁 於 御 禦 圄 瘀 馭 齬 圉 飫 敔 淤 茣 扵 蘓 峿 籞 菸 鋙 渔 语 铻 饫 驭 鱼 龉 仒 唹 棜 秗
|
||||
야 夜 野 也 若 射 耶 惹 倻 椰 爺 冶 揶 埜 捓 枒 喏 琊 鋣 嘢 焲 瑘 䤳 爷 铘 乛 亱 偌 吔 壄 婼 擨 漜 祂 釾 鎁 鵺 㖡 㘃 㙒 㭨 䓉 䥺
|
||||
약 弱 藥 約 若 略 躍 掠 葯 蒻 鑰 籥 禴 爚 篛 鰯 龠 鶸 瀹 礿 㵸 箬 箹 蘥 㿑 药 哟 约 薬 跃 叒 喲 嵶 楉 洂 渃 纅 藞 鄀 鰙 鸙 㒢 㜰 㰛 䋤 䐞 䖃 䚥 䟑 䠯 䤀 䶳
|
||||
양 羊 兩 陽 洋 良 養 量 讓 揚 凉 梁 樣 糧 楊 壤 諒 亮 襄 孃 瘍 攘 釀 恙 禳 痒 佯 暘 穰 瀁 敭 煬 癢 驤 漾 颺 徉 鑲 瀼 烊 眻 蘘 輰 譲 様 㨾 样 昜 鍚 懹 勷 䑋 崵 㐮 纕 壌 饟 䉴 垟 欀 瓖 諹 嚷 禓 阦 㔦 扬 镶 阳 养 嬢 旸 杨 炀 疡 让 酿 醸 钖 飏 馕 骧 劷 勨 奍 忀 懩 攁 檨 氜 氧 氱 爙 獽 珜 瓤 礢 穣 羏 羕 蛘 蝆 躟 鐊 霷 鬤 鰑 鴹 鸉 㠤 㦹 㬕 㳱 㺊 䁑 䍩 䑆 䖆 䖹 䬗 䬺 䭐 䭥 䵮 椋
|
||||
어 魚 語 漁 於 御 禦 圄 瘀 馭 齬 圉 飫 敔 淤 茣 扵 蘓 峿 籞 菸 鋙 渔 语 铻 饫 驭 鱼 龉 仒 唹 棜 秗 箊 篽 蓹 蘌 醧 饇 鯲 鷠 䁩 䏸 䐳 䔡 䗨 䢩 䥏 䰻 䱷 䲣 䘘
|
||||
억 億 憶 抑 臆 檍 薏 嶷 繶 澺 亿 忆 悥 癔 肊 訲 餩 㫇 䖁 䗷
|
||||
언 言 焉 彦 諺 堰 偃 讞 嫣 鄢 鼴 匽 鼹 傿 彥 琂 唁 漹 蔫 喭 蝘 讠 谚 谳 嘕 墕 娮 嵃 愝 椻 牪 甗 篶
|
||||
언 言 焉 彦 諺 堰 偃 讞 嫣 鄢 鼴 匽 鼹 傿 彥 琂 唁 漹 蔫 喭 蝘 讠 谚 谳 嘕 墕 娮 嵃 愝 椻 牪 甗 篶 萒 褗 訁 躽 遃 郾 隁 鰋 鶠 齞 齴 㢇 㫃 㰽 䁙 䇾 䓂 䗡 䞁 䤷
|
||||
얼 蘖 孼 臬 孽 糱 乻 臲 闑 隉 嵲 糵 枿 㜸 櫱 蠥 镍 巕 槷 鎳 㙞 㮆 㴪 䡾
|
||||
엄 嚴 掩 奄 儼 俺 淹 广 閹 晻 崦 弇 罨 曮 醃 釅 渰 揜 顩 㤿 㛪 隒 严 俨 厂 腌 觃 阉 剦 厈 厳 噞 孍
|
||||
엄 嚴 掩 奄 儼 俺 淹 广 閹 晻 崦 弇 罨 曮 醃 釅 渰 揜 顩 㤿 㛪 隒 严 俨 厂 腌 觃 阉 剦 厈 厳 噞 孍 嶖 欕 硽 覎 龑 㚧 㢂 㢛 㭺 䄋 䉷 䕾 䛳 䣍 䲓 䶫 䶮
|
||||
업 業 嶪 鄴 嶫 业 㡤 澲 邺 殗 餣 驜 鸈 㗼 㡋 㪑 㱉 㸣 䁆 䌜 䎨 䤶 䧨 䱒 䲜
|
||||
엇 旕
|
||||
엉 㫈
|
||||
에 恚 曀 饐 殪 㤬
|
||||
엔 円
|
||||
여 女 如 餘 與 余 汝 旅 予 麗 輿 勵 呂 廬 礪 驪 閭 黎 濾 歟 轝 茹 璵 艅 礖 舁 与 欤 畬 洳 旟 䂊 蕷
|
||||
역 力 亦 易 逆 歷 役 驛 譯 曆 域 疫 繹 轢 斁 嶧 懌 鬩 閾 淢 埸 蜮 晹 㴒 湙 帟 棫 燡 睪 鶂 坄 歝 琙
|
||||
연 年 然 連 煙 練 硏 鍊 宴 延 緣 演 憐 戀 聯 蓮 燃 軟 淵 燕 沿 鉛 咽 衍 捐 硯 漣 姸 筵 輦 懦 椽 鳶
|
||||
여 女 如 餘 與 余 汝 旅 予 麗 輿 勵 呂 廬 礪 驪 閭 黎 濾 歟 轝 茹 璵 艅 礖 舁 与 欤 畬 洳 旟 䂊 蕷 鴽 伃 妤 澦 籹 馀 滪 畲 舆 蓣 铷 侞 嬩 忬 悆 懙 桇 櫲 狳 玙 畭 硢 礜 穥 筎 籅 肗 蕠 邚 銣 雓 鮽 鸒 㒜 㦛 㶛 㺞 㼂 㾒 䂛 䑂
|
||||
역 力 亦 易 逆 歷 役 驛 譯 曆 域 疫 繹 轢 斁 嶧 懌 鬩 閾 淢 埸 蜮 晹 㴒 湙 帟 棫 燡 睪 鶂 坄 歝 琙 魊 峄 怿 绎 訳 译 阈 駅 驿 鹝 鹟 伇 圛 垼 墿 屰 戫 曎 棭 炈 痬 硛 緎 縌 罭 虉 豛 醳 鈠 鯣 鶃 鷊 㑊 㘁 㚜 㣂 㴁 㶠 㻛 㽣 䍞 䓈 䓪 䖌 䡛 䦴 䧕 䭞 䮙 䰥 䳬
|
||||
연 年 然 連 煙 練 硏 鍊 宴 延 緣 演 憐 戀 聯 蓮 燃 軟 淵 燕 沿 鉛 咽 衍 捐 硯 漣 姸 筵 輦 懦 椽 鳶 撚 煉 烟 涓 璉 沇 娟 涎 堧 嚥 挻 秊 縯 讌 臙 莚 吮 掾 悁 兗 埏 渷 蠕 鷰 嬿 囦 曣 櫞 蜵 葕 研 兖 缘 壖 蜒 輭 鈆 碝 㮒 愞 耎 綖 鋋 妍 珚 縁 㳂 肰 蜎 蝝 㳙 撋 抁 灁 瑌 瓀 礝 醼 䂩 橼 渊 砚 软 铅 鸢 剈 嘫 噮 姢 娫 嬊 嬽 揅 昖 棩 椼 樮 橪 渁 渆 渕 溎 烻 狿 緛 繎 肙 胭 蔅 裫 觾 蹨 轋 郔 酀 驠
|
||||
열 說 熱 列 悅 烈 劣 裂 閱 咽 涅 爇 噎 拽 揑 潱 閲 恱 湼 阅 悦 热 蠮 㖶 䆕 䆢 䊦 䭇
|
||||
염 念 炎 鹽 染 廉 厭 閻 焰 簾 艶 殮 苒 髥 琰 剡 捻 枏 錟 塩 冉 灎 饜 魘 灩 靨 黶 扊 檿 焱 懕 檶 釅
|
||||
엽 葉 獵 燁 曄 叶 揲 楪 魘 爗 熀 靨 曅 饁 瞱 曗 擛 晔 烨 靥 馌 偞 僷 嚈 擪 擫 枼 枽 殜 皣 瞸 鐷 㒯
|
||||
염 念 炎 鹽 染 廉 厭 閻 焰 簾 艶 殮 苒 髥 琰 剡 捻 枏 錟 塩 冉 灎 饜 魘 灩 靨 黶 扊 檿 焱 懕 檶 釅 燄 髯 曕 猒 㷔 袡 豔 灧 偣 冄 厴 呥 嬮 蚦 䀋 艳 厌 厣 恹 殓 滟 盐 酽 闫 阎 餍 魇 鲶 黡 乵 壛 夵 姌 媣 懨 抩 敥 棪 櫩 灔 焔 珃 艷 蒅 蚺 衻 豓 酓 醶 閆 鯰 㚩 㦔 㱘 㶄 㷋 㷺 㿕 䅧 䌪 䎃 䎦 䒣 䛁 䣸 䤡 䦲 䫇 䶲
|
||||
엽 葉 獵 燁 曄 叶 揲 楪 魘 爗 熀 靨 曅 饁 瞱 曗 擛 晔 烨 靥 馌 偞 僷 嚈 擪 擫 枼 枽 殜 皣 瞸 鐷 㒯 㩎 㷸 䈎 䭎 䭟
|
||||
엿 㖲 㖳
|
||||
영 永 英 令 領 榮 迎 營 寧 靈 嶺 影 詠 映 泳 零 盈 塋 鈴 瑛 玲 囹 暎 穎 纓 瑩 聆 怜 楹 瀛 潁 嶸 霙
|
||||
예 禮 例 藝 豫 譽 銳 隷 預 睿 泄 醴 濊 芮 詣 曳 裔 穢 洩 刈 乂 倪 霓 猊 汭 蘂 叡 隸 翳 繄 睨 蕊 鯢
|
||||
오 五 午 惡 吾 誤 烏 悟 吳 嗚 汚 娛 傲 奧 梧 墺 伍 寤 懊 禑 澳 鰲 鼇 晤 敖 塢 熬 獒 筽 蜈 俉 旿 忤
|
||||
영 永 英 令 領 榮 迎 營 寧 靈 嶺 影 詠 映 泳 零 盈 塋 鈴 瑛 玲 囹 暎 穎 纓 瑩 聆 怜 楹 瀛 潁 嶸 霙 獰 濚 渶 瀯 羚 煐 瓔 鍈 嬰 咏 贏 縈 嬴 郢 癭 韺 浧 濴 碤 蠑 营 営 嵘 栐 禜 攖 頴 灜 覮 籝 籯 偀 媖 溋 濙 瀴 蘡 婴 撄 栄 狞 璎 瘿 绬 缨 萦 蝾 赢 锳 颍 颕 颖 嬫 孆 孾 巆 巊 廮 怺 愥 摬 攍 攚 朠 柍 柡 梬 楧 櫿 溁 爃 璄 盁 矨 礯 緓 萾 蝧 蠳 謍 賏 醟 鐛 鑍 鶧 㘇 㜲 㡕 㢍 㨕 㮠 㲟 㵬 㶈 㿘 䀴
|
||||
예 禮 例 藝 豫 譽 銳 隷 預 睿 泄 醴 濊 芮 詣 曳 裔 穢 洩 刈 乂 倪 霓 猊 汭 蘂 叡 隸 翳 繄 睨 蕊 鯢 獩 曀 蛻 枘 拽 蜺 薉 鷖 蕋 蚋 囈 瞖 麑 嫛 惢 掜 苅 瘞 羿 勩 蜹 曵 䓲 䛖 輗 蓺 貎 䃜 埶 堄 晲 㡼 枍 瑿 蘙 觬 鋭 㙯 誉 锐 勚 呓 瘗 秽 艺 诣 预 鲵 鹥 兿 呭 壡 婗 嫕 寱 帠 怈 悘 捙 栧 棿 槸 橤 殹 淣 玴 瘱 秇 繠 蘃 袣 褹 襼 詍 讛 跇 踅 郳 鏏 靾 饖 黳 齯 㖂 㙠 㙪 㛳 㜒 㝣 㡫 㤤 㨅 㪒 㪫 㲊 㲼
|
||||
오 五 午 惡 吾 誤 烏 悟 吳 嗚 汚 娛 傲 奧 梧 墺 伍 寤 懊 禑 澳 鰲 鼇 晤 敖 塢 熬 獒 筽 蜈 俉 旿 忤 嗷 媪 汙 遨 鏖 襖 迃 驁 媼 窹 迕 鼯 聱 唔 慠 謷 隩 捂 鏊 仵 俣 圬 噁 嫯 茣 杇 悞 䫨 吴 污 牾 呉 洿 厫 噢 鄔 嗸 扷 浯 螯 廒 璈 㹳 圫 嵨 嶅 擙 珸 祦 郚 鏕 䎸 磝 鋘 隖 鷔 㥿 鳌 乌 呜 坞 奥 娱 岙 悮 袄 误 钨 骜 乄 啎 埡 奡 娪 娯 嶴 弙 忢 摀 摮 歍 汻 洖 溩 滶 澚 焐 熓 爊 獓 玝 瑦 痦 窏 繺 肟
|
||||
옥 玉 屋 獄 沃 鈺 鋈 狱 莹 钰 剭 媉 砡 箼 軉 鳿 㓇 䑁 䞝
|
||||
온 溫 穩 蘊 縕 瘟 瑥 醞 慍 氳 韞 媼 昷 薀 轀 熅 饂 鰮 温 愠 藴 蕰 氲 醖 馧 鞰 榲 蒕 塭 瘒 鎾 蕴 酝
|
||||
온 溫 穩 蘊 縕 瘟 瑥 醞 慍 氳 韞 媼 昷 薀 轀 熅 饂 鰮 温 愠 藴 蕰 氲 醖 馧 鞰 榲 蒕 塭 瘒 鎾 蕴 酝 揾 榅 煴 缊 辒 韫 鳁 搵 桽 殟 緼 蒀 褞 豱 輼 闦 鰛 㝧 㬈 㼔 䅱 䦟 䦷 䭓 媪 稳
|
||||
올 兀 膃 嗢 腽 杌 卼 屼 扤 矹 㐏 乯 䪲 淴 瞃 阢 靰 鼿 齀 㐚 㐳 㽾 䑢 䦍
|
||||
옹 翁 擁 雍 壅 甕 邕 瓮 饔 癰 顒 喁 蓊 雝 滃 罋 禺 廱 癕 痈 臃 䩺 㙲 㝘 拥 颙 勜 嗈 嗡 噰 塕 嵡 暡
|
||||
와 臥 瓦 訛 蝸 渦 蛙 窩 鍋 窪 娃 洼 哇 媧 涴 譌 萵 囮 枙 窊 婐 猧 卧 鼃 吪 䨟 捰 漥 鈋 涡 窝 莴 蜗
|
||||
옹 翁 擁 雍 壅 甕 邕 瓮 饔 癰 顒 喁 蓊 雝 滃 罋 禺 廱 癕 痈 臃 䩺 㙲 㝘 拥 颙 勜 嗈 嗡 噰 塕 嵡 暡 澭 灉 瞈 聬 蕹 螉 郺 鎓 鰅 鶲 齆 㘢 㜉 㢕 㨣 㮬 㴩 㷏 㺋 㻾 㽫 㿈 䈵 䐥 䔨 䗸 䱵
|
||||
와 臥 瓦 訛 蝸 渦 蛙 窩 鍋 窪 娃 洼 哇 媧 涴 譌 萵 囮 枙 窊 婐 猧 卧 鼃 吪 䨟 捰 漥 鈋 涡 窝 莴 蜗 讹 佤 咓 唩 搲 攨 涹 溛 畖 砙 迗 邷 頋 㘥 㛂 㧚 㹻 㼘 䂺 䆧 䠚 䰀 䵷
|
||||
왁 䢲
|
||||
완 完 緩 莞 頑 宛 婉 阮 琓 玩 浣 腕 翫 椀 琬 碗 梡 脘 豌 惋 岏 蜿 盌 刓 涴 忨 垸 妧 薍 剜 抏 輐 园
|
||||
완 完 緩 莞 頑 宛 婉 阮 琓 玩 浣 腕 翫 椀 琬 碗 梡 脘 豌 惋 岏 蜿 盌 刓 涴 忨 垸 妧 薍 剜 抏 輐 园 缓 顽 唍 啘 埦 婠 嵈 帵 捖 捥 烷 睕 羱 萖 貦 贃 㘤 㛡 㝴 㣪 㱧 㻨 㼝 䖾 䛃 䝹 䯈 䯛 鋺 杬
|
||||
왈 曰 刖 嗗 聉 䚴
|
||||
왕 王 往 旺 汪 枉 尫 迬 瀇 徃 尢 仼 㲿 尣 尩 尪 彺 忹 暀 蚟 㑌 㒬 㞷 㳝 㳹 㴏 䤑 䶭
|
||||
왜 倭 矮 娃 歪 媧 騧 娲 劸 緺 躷 㗏 㰪 䶐
|
||||
외 外 畏 猥 巍 嵬 聵 煨 隗 磈 偎 崴 碨 嵔 渨 隈 廆 桅 椳 詴 懀 揋 腲 聩 鳂 峞 徻 愄 溾 瘣 葨 鍡 鮠
|
||||
외 外 畏 猥 巍 嵬 聵 煨 隗 磈 偎 崴 碨 嵔 渨 隈 廆 桅 椳 詴 懀 揋 腲 聩 鳂 峞 徻 愄 溾 瘣 葨 鍡 鮠 鰃 㙗 㛱 㞇 㟴 㠕 䃬 䋿 䠿 䫥
|
||||
욋 夞
|
||||
요 要 樂 料 了 謠 僚 遙 陶 搖 腰 遼 堯 妖 耀 療 曜 姚 尿 擾 撓 饒 邀 夭 寮 僥 燎 窈 咬 凹 拗 窯 寥
|
||||
요 要 樂 料 了 謠 僚 遙 陶 搖 腰 遼 堯 妖 耀 療 曜 姚 尿 擾 撓 饒 邀 夭 寮 僥 燎 窈 咬 凹 拗 窯 寥 繞 瑤 蓼 嶢 繇 橈 燿 蟯 幺 徭 澆 徼 殀 軺 蕘 遶 窅 嚙 坳 鷂 祅 穾 銚 墝 偠 喓 嬈 么 揺 謡 摇 窑 瑶 窰 颻 㴭 尭 傜 宎 袎 溔 騕 䙅 窔 䁏 䆗 嶤 珧 芺 葽 訞 㨱 䁘 䢣 䯚 谣 侥 娆 尧 峣 挠 桡 浇 绕 荛 蛲 遥 飖 饶 鳐 鹞 仸 吆 嗂 垇 垚 垰 婹 媱 嫑 岆 崾 徺 愮 憿 抝 暚 枖 柪 柼 梎 楆 榚 榣 烑 熎
|
||||
욕 欲 浴 辱 慾 褥 縟 溽 蓐 鵒 鄏 昙 缛 鹆 嗕 媷 峪 輍 鋊 㦺
|
||||
용 用 容 勇 龍 庸 踊 鏞 茸 鎔 傭 溶 瑢 熔 聳 湧 蓉 涌 冗 墉 俑 甬 慂 榕 埇 舂 慵 宂 踴 憃 嵱 筩 硧
|
||||
우 右 牛 雨 友 又 宇 于 尤 憂 遇 愚 優 羽 郵 偶 佑 祐 禹 迂 虞 寓 隅 禑 雩 紆 瑀 盂 旴 亐 芋 藕 玗
|
||||
용 用 容 勇 龍 庸 踊 鏞 茸 鎔 傭 溶 瑢 熔 聳 湧 蓉 涌 冗 墉 俑 甬 慂 榕 埇 舂 慵 宂 踴 憃 嵱 筩 硧 蛹 傛 槦 佣 鄘 㼸 牗 摏 牅 髶 蹖 䢇 耸 镕 镛 鲬 鳙 傇 勈 塎 媶 嫆 嫞 彮 恿 悀 愑 愹 搈 搑 榵 氄 滽 砽 穁 穃 縙 苚 褣 軵 鯒 鰫 鱅 鴪 鷛 㐯 㛚 㝐 㟾 㣑 㦶 㦷 㫪 㯴 㲝 㲨 㶲 㺎 䄾 䇀 䇯 䈶 䗤 䞻 䠜 䡆 䡥 䢆 䤊 䧡 䩸
|
||||
우 右 牛 雨 友 又 宇 于 尤 憂 遇 愚 優 羽 郵 偶 佑 祐 禹 迂 虞 寓 隅 禑 雩 紆 瑀 盂 旴 亐 芋 藕 玗 嵎 釪 吁 亏 堣 耦 踽 盱 喁 竽 俁 迃 疣 耰 禺 麀 麌 庽 杅 鍝 俣 亴 偊 吽 謣 齲 訏 圩 穻 㝢 塸 櫌 苃 嚘 懮 訧 㘾 㚥 䁱 噳 扜 楀 瀀 纋 肬 腢 㤑 㷒 䋅 䨞 叹 优 忧 怄 扰 犹 纡 邮 鱿 龋 吘 媀 峟 怣 慪 扝 挧 桙 櫙 歶 汼 沋 湡 澞 牜 獶 盓 祤 翢 芌 荢 萭 蕅 虶 衧 訦 邘 鄅 鄾 酑 鏂 陓 駀 骬 髃 魷
|
||||
욱 旭 郁 煜 頊 昱 勖 澳 彧 栯 稶 燠 隩 勗 䤋 薁 稢 㮋 顼 喐 旮 朂 礇 㤢 㦽 㰲 䉛 䜡
|
||||
운 雲 運 云 韻 芸 暈 隕 殞 耘 蕓 澐 橒 熉 紜 韵 惲 沄 霣 篔 餫 鄆 鄖 溳 夽 愪 抎 縜 韗 䩵 殒 恽 筼
|
||||
운 雲 運 云 韻 芸 暈 隕 殞 耘 蕓 澐 橒 熉 紜 韵 惲 沄 霣 篔 餫 鄆 鄖 溳 夽 愪 抎 縜 韗 䩵 殒 恽 筼 纭 运 郓 郧 陨 喗 囩 妘 枟 涢 眃 磒 秐 繧 耺 腪 蒷 蝹 賱 齳 㚃 㛣 㜏 㟦 㩈 㻒 䆬 䇖 䉙 䚋 䞫 䢵 䨶 䫟 䲰 員
|
||||
울 鬱 蔚 亐 熨 欝 㭗 菀 鬰 爩 黦 䁌 欎 灪 㐛 㠨 䖇 䵥
|
||||
웅 雄 熊 䧺 僌 㞲 㷱
|
||||
원 元 原 遠 園 願 圓 怨 院 員 源 援 苑 瑗 袁 媛 寃 猿 鴛 阮 轅 垣 爰 愿 琬 湲 沅 嫄 洹 円 冤 圜 鵷
|
||||
월 月 越 鉞 粤 刖 粵 樾 軏 戉 抈 狘 钺 钥 仴 岄 怴 枂 泧 蚎 蚏 跀 鈅 㜧 㞽 㳉 䄴 䋐 䎳 䞲 䟠 䡇 䢁
|
||||
위 位 危 爲 威 偉 謂 衛 委 慰 圍 僞 胃 違 倭 緯 尉 魏 蔚 渭 韋 萎 瑋 葦 蝟 暐 褘 蔿 為 諉 闈 衞 逶
|
||||
유 有 流 由 油 留 猶 柳 遺 遊 酉 唯 幼 柔 惟 類 裕 悠 誘 乳 儒 維 愈 幽 劉 兪 踰 庾 楡 紐 癒 諭 游
|
||||
원 元 原 遠 園 願 圓 怨 院 員 源 援 苑 瑗 袁 媛 寃 猿 鴛 阮 轅 垣 爰 愿 琬 湲 沅 嫄 洹 円 冤 圜 鵷 蜿 黿 謜 猨 杬 楥 綩 芫 薗 鋺 騵 貟 逺 畹 晼 圎 榞 㤪 㥳 䲶 夘 眢 踠 夗 溒 茒 薳 邍 鶢 䩊 䲮 员 圆 诨 贠 辕 远 鸳 鹓 鼋 䓕 倇 傆 傊 厡 厵 塬 妴 媴 惌 朊 榬 獂 盶 禐 笎 箢 葾 蒝 蒬 蚖 蝯 螈 衏 褑 褤 諢 豲 邧 酛 鈨 鎱 闧 顐 駌 魭 鶰 㐾 㝨 㟲 㟶 㠾 㥐 㨬 㷧 㹉 㽜 䅈 䈠 䏍 䑱 䕂 䖠 䖤 䗕 䘼
|
||||
월 月 越 鉞 粤 刖 粵 樾 軏 戉 抈 狘 钺 钥 仴 岄 怴 枂 泧 蚎 蚏 跀 鈅 㜧 㞽 㳉 䄴 䋐 䎳 䞲 䟠 䡇 䢁 䤦 䬂
|
||||
위 位 危 爲 威 偉 謂 衛 委 慰 圍 僞 胃 違 倭 緯 尉 魏 蔚 渭 韋 萎 瑋 葦 蝟 暐 褘 蔿 為 諉 闈 衞 逶 喟 韙 痿 葳 撝 煒 幃 熨 韡 餧 潙 骪 喂 猬 偽 㙔 蘤 骫 罻 蝛 躗 㗸 徫 湋 硊 縅 蜲 䦱 嶎 愇 藯 躛 頠 骩 䘙 为 伪 伟 卫 囲 围 帏 涠 炜 玮 纬 苇 袆 诿 谓 违 闱 韦 韪 儰 叞 喡 喴 噅 噕 墛 媁 媙 媦 孬 寪 峗 崣 楲 洈 潿 煟 熭 犚 犩 緭 蒍 蘶 螱 蟡 褽 覣 讆 讏 贀 踒 鄬 鍏 隇 霨 韑 韚 颹 餵 鰄 鳚
|
||||
유 有 流 由 油 留 猶 柳 遺 遊 酉 唯 幼 柔 惟 類 裕 悠 誘 乳 儒 維 愈 幽 劉 兪 踰 庾 楡 紐 癒 諭 游 宥 喩 溜 諛 鍮 琉 愉 柚 蹂 揄 攸 猷 綏 逾 濡 孺 臾 瑜 侑 硫 杻 繇 萸 洧 楢 釉 籲 帷 牖 壝 囿 腴 逌 莠 糅 揉 蕕 蕤 窬 黝 瘐 緌 窳 讉 呦 鮪 蠕 褕 泑 冘 蚴 蝤 鼬 斿 瘉 蚰 鞣 龥 襦 俞 覦 喻 嚅 嵛 㓜 褎 輶 蝣 褏 婾 曘 蜼 㳺 浟 㽔 䛕 擩 槱 羑 毹 狖 薷 逰 濰 隃 媮 滺 㽥 卣 痏 婑 楺 渪 甤 㺄 䘱
|
||||
육 六 肉 育 陸 戮 堉 毓 儥 淯 錥 哊 唷 喅 宍 棛 焴 蒮 蘛 蜟 袬 逳 㣃 㥔 㻙 䋭 䘻
|
||||
윤 倫 輪 尹 允 潤 閏 崙 胤 鈗 淪 贇 勻 畇 奫 玧 昀 鋆 酳 閠 狁 蝡 㣧 阭 润 赟 闰 橍 膶 荺 蜳 馻 㠈
|
||||
윤 倫 輪 尹 允 潤 閏 崙 胤 鈗 淪 贇 勻 畇 奫 玧 昀 鋆 酳 閠 狁 蝡 㣧 阭 润 赟 闰 橍 膶 荺 蜳 馻 㠈 㽙 䤞 䦞 䪳 沇
|
||||
율 律 率 栗 慄 聿 潏 矞 茟 䢖 欥 鴥 噊 燏 繘 銉 驈 鱊 鴧 㐕 㐠 㯨 䋖 䫻 䮇 汩
|
||||
융 隆 融 戎 絨 瀜 狨 羢 漋 娀 㭜 肜 駥 绒 曧 毧 茙 螎 䘬
|
||||
은 銀 恩 隱 殷 誾 垠 圻 慇 訔 听 憖 嚚 狺 溵 檼 癮 垽 鄞 圁 珢 齗 齦 隠 隐 穏 稳 垦 㤙 檃 㥯 櫽 泿
|
||||
은 銀 恩 隱 殷 誾 垠 圻 慇 訔 听 憖 嚚 狺 溵 檼 癮 垽 鄞 圁 珢 齗 齦 隠 隐 穏 稳 垦 㤙 檃 㥯 櫽 泿 慭 濦 磤 㶏 憗 懚 煾 犾 蒑 瘾 訚 银 龂 乚 嗯 峎 峾 嶾 摁 斦 檭 猌 蒽 蘟 讔 㐆 㒚 㕶 㙬 㡥 㥼 㦩 㴈 㹜 㹞 䅰 䇵 䌥 䓄 䖐 䖜 䨸 䭡 䴦 訢
|
||||
을 乙 鳦 圪 钇 釔 䎲
|
||||
음 音 飮 陰 吟 淫 蔭 喑 愔 霪 崟 廕 隂 䕃 飲 瘖 婬 霠 阥 霒 荫 阴 饮 乑 侌 噖 噾 崯 摿 淾 烎 癊 碒
|
||||
음 音 飮 陰 吟 淫 蔭 喑 愔 霪 崟 廕 隂 䕃 飲 瘖 婬 霠 阥 霒 荫 阴 饮 乑 侌 噖 噾 崯 摿 淾 烎 癊 碒 窨 苂 荶 訡 趛 鈝 韾 鷣 㕂 㖗 㞤 㱃 㸒 䚿 䜾 䤃 䨙 䨧 䪩
|
||||
읍 邑 泣 揖 挹 悒 浥 裛 湆 䭂 俋 唈 歞 湇 㘊 㱞 㴔 㵫 䇼 䉗 䓃 䔱
|
||||
응 應 凝 鷹 膺 譍 应 応 鹰 噟 軈 㒣 㣹 㶐 㶝 䧹
|
||||
의 衣 意 義 依 醫 矣 議 宜 儀 疑 誼 擬 毅 椅 縊 倚 懿 蟻 薏 艤 錡 猗 嶷 欹 漪 儗 劓 螘 饐 凒 礒 医
|
||||
이 二 耳 里 利 理 異 以 而 李 已 移 易 吏 離 履 裏 梨 伊 夷 泥 珥 弛 貳 怡 罹 餌 裡 痍 爾 痢 姨 貽
|
||||
익 益 翼 翊 溺 翌 匿 謚 瀷 弋 鷁 熤 榏 釴 杙 嗌 艗 黓 䄩 谥 鹢 浳 熼 獈 穓 膉 芅 蛡 隿 霬 骮 齸 㔴
|
||||
인 人 仁 因 引 印 寅 忍 認 鄰 隣 姻 咽 麟 刃 湮 鱗 吝 燐 蚓 靭 璘 茵 絪 靷 藺 堙 仞 禋 夤 氤 裀 婣
|
||||
일 一 日 逸 壹 鎰 佾 溢 佚 馹 軼 泆 釰 皼 衵 壱 轶 镒 驲 劮 呹 囸 夁 嬄 弌 浂 燚 辷 鈤 鴩 鷧 㳑 䒤
|
||||
임 林 壬 任 臨 賃 姙 淋 稔 荏 恁 妊 衽 飪 絍 銋 袵 餁 紝 棯 腍 鈓 㶵 䄒 临 纴 赁 饪 栠 栣 祍 秹 菍
|
||||
의 衣 意 義 依 醫 矣 議 宜 儀 疑 誼 擬 毅 椅 縊 倚 懿 蟻 薏 艤 錡 猗 嶷 欹 漪 儗 劓 螘 饐 凒 礒 医 义 冝 扆 顗 宐 檥 毉 竩 㼁 禕 旖 犄 薿 㠜 㦤 譩 㩘 䝘 䴊 仪 拟 祎 蚁 议 谊 铱 镱 饻 亄 偯 吚 嬑 嬟 孴 寲 嶬 庡 撎 旑 椬 檹 燱 狋 畩 稦 籎 羛 萓 藙 螠 衤 觺 譺 豙 輢 轙 逘 郼 醷 鈘 銥 鐿 顡 餏 鷾 鸃 㓷 㕈 㕒 㘈 㛄 㝖 㠖 㢊 㥋 㦉 㫊 㬾 㱅 㱲 㳖 㽈 㾨 㿲 䉝 䉨 䒾 䓹 䝝 䣡 䧇 䧧 䪰 䫑 䫯 䬥
|
||||
이 二 耳 里 利 理 異 以 而 李 已 移 易 吏 離 履 裏 梨 伊 夷 泥 珥 弛 貳 怡 罹 餌 裡 痍 爾 痢 姨 貽 邇 彛 肄 苡 飴 荑 彝 迤 訑 詑 洟 咿 珆 栮 尔 隶 鮧 坨 頉 頤 羡 尒 异 圯 薾 迩 儞 匜 貮 詒 迆 洱 眤 輀 杝 敡 眙 貤 轜 尓 暆 柂 駬 彜 洏 彞 栭 酏 頥 羠 苢 蛦 黟 佁 刵 崺 弍 扅 攺 樲 潩 眱 眲 綛 胹 蛜 衈 謻 趰 迻 髵 㑆 㕥 㠯 䔬 弐 诒 贰 贻 铒 颐 饴 饵 鲕 鸸 乁 伿 佴 侇 侕 咡 咦 媐 宧 峏 峓
|
||||
익 益 翼 翊 溺 翌 匿 謚 瀷 弋 鷁 熤 榏 釴 杙 嗌 艗 黓 䄩 谥 鹢 浳 熼 獈 穓 膉 芅 蛡 隿 霬 骮 齸 㔴 㚤 㜋 㢞 䋚 䌻 䘝 䣧 䯆 䴬
|
||||
인 人 仁 因 引 印 寅 忍 認 鄰 隣 姻 咽 麟 刃 湮 鱗 吝 燐 蚓 靭 璘 茵 絪 靷 藺 堙 仞 禋 夤 氤 裀 婣 洇 扨 籾 芢 訒 軔 儿 囙 紉 闉 戭 牣 諲 韌 鏔 駰 䄄 垔 湚 釼 陻 㣼 仭 欭 歅 璌 筃 螾 纫 认 讱 轫 铟 韧 骃 亻 凐 刄 囜 屻 岃 廴 忈 忎 朄 朲 杒 梕 殥 洕 濥 秂 秵 粌 緸 肕 茚 荵 蔩 躵 鈏 銦 靱 鞇 魜 鮣 㘻 㝙 㠴 㧈 㧢 㪦 㲽 㸾 㽼 䀔 䀕 䃌 䇙 䍰 䏖 䏰 䑒 䒡 䓰 䧣 䲟
|
||||
일 一 日 逸 壹 鎰 佾 溢 佚 馹 軼 泆 釰 皼 衵 壱 轶 镒 驲 劮 呹 囸 夁 嬄 弌 浂 燚 辷 鈤 鴩 鷧 㳑 䒤 䭿 䳀
|
||||
임 林 壬 任 臨 賃 姙 淋 稔 荏 恁 妊 衽 飪 絍 銋 袵 餁 紝 棯 腍 鈓 㶵 䄒 临 纴 赁 饪 栠 栣 祍 秹 菍 軠 鵀 㤛 䇮 䋕 䏕 䚾 䛘 䭃
|
||||
입 入 立 笠 粒 卄 廿 込 圦 扖 杁 魞 鳰
|
||||
잇 㗡
|
||||
잉 剩 孕 仍 芿 媵 剰 賸 礽 陾 䞉 扔 膡 艿 辸 㑞 㚺 㞌 㭁 㺱 䄧 䚮 䵴
|
||||
자 子 自 者 字 姉 慈 資 姿 玆 紫 恣 刺 滋 疵 雌 磁 炙 諮 藉 煮 仔 蔗 瓷 炸 咨 孜 茨 薺 茲 貲 眦 孶
|
||||
작 作 昨 爵 酌 杓 灼 雀 鵲 綽 勺 嚼 芍 醋 炸 斫 酢 怍 焯 碏 柞 岝 汋 犳 斱 妁 鷟 婥 爝 皭 圴 扚 禚
|
||||
잔 殘 盞 棧 孱 潺 戔 剗 驏 残 醆 僝 刬 戋 栈 盏 骣 嶘 拃 桟 潹 琖 菚 虥 虦 輚 轏 㟞 㥇 㮍 㱚 㻵 䎒
|
||||
자 子 自 者 字 姉 慈 資 姿 玆 紫 恣 刺 滋 疵 雌 磁 炙 諮 藉 煮 仔 蔗 瓷 炸 咨 孜 茨 薺 茲 貲 眦 孶 訾 赭 粢 髭 觜 泚 柘 耔 鷓 眥 鶿 牸 胾 呰 鎡 莿 鮓 嬨 孖 茈 虸 蚱 頿 兹 姊 扻 煑 趦 朿 胔 趑 餈 孳 嵫 庛 訿 澬 㧗 榨 杍 啫 栥 秭 稵 㹀 䵭 呲 姕 橴 笫 骴 䄍 䏑 䳄 鹚 蚝 谘 赀 资 锌 镃 鲊 鲝 鹧 刾 吇 啙 嗞 垐 塖 宱 樜 毑 溂 溠 濨 熫 玼 珁 甆 痄 矷 礠 禌 秄 秶 籽 糍 緕 纃 胏 芓 茊 茡 薋 蛓
|
||||
작 作 昨 爵 酌 杓 灼 雀 鵲 綽 勺 嚼 芍 醋 炸 斫 酢 怍 焯 碏 柞 岝 汋 犳 斱 妁 鷟 婥 爝 皭 圴 扚 禚 筰 䱜 猎 绰 鹊 仢 妰 岞 烵 焳 熦 爑 皵 秨 稓 穱 繛 苲 莋 謶 趞 鈼 鐯 靍 飵 㑅 㘀 㤰 㩱 㪕 㬭 㸲 㹱 䅵 䇎 䋏 䎞 䎰 䝫 䞢 䦃 䧿 䲵 舃
|
||||
잔 殘 盞 棧 孱 潺 戔 剗 驏 残 醆 僝 刬 戋 栈 盏 骣 嶘 拃 桟 潹 琖 菚 虥 虦 輚 轏 㟞 㥇 㮍 㱚 㻵 䎒 䏼 䗃 䙁 䝳 䩆 䱠 䴼
|
||||
잘 囐 乽
|
||||
잠 潛 暫 蠶 箴 簪 湛 岑 潜 蚕 涔 梣 笒 濳 蹔 蘸 埁 簮 熸 篸 賺 撏 蠺 挦 暂 臜 赚 兂 喒 寁 揝 撍 昝
|
||||
잡 雜 閘 襍 磼 霅 卡 囃 眨 匝 迊 雑 帀 啑 煠 咂 币 杂 咔 嘁 抸 拤 沞 疀 砸 譗 鉔 雥 韴 㧜 㬁 䕹 䙄
|
||||
잠 潛 暫 蠶 箴 簪 湛 岑 潜 蚕 涔 梣 笒 濳 蹔 蘸 埁 簮 熸 篸 賺 撏 蠺 挦 暂 臜 赚 兂 喒 寁 揝 撍 昝 硶 臢 蝅 譧 趈 鐕 㔆 㞥 㟛 㣅 㺘 㻸 䁮 䃡 䅾 䍼 䐶 䗝 䗞 䘉 䟅 䣟 䣠 䤐 䫈 䭕 䰼 䲋 䳥
|
||||
잡 雜 閘 襍 磼 霅 卡 囃 眨 匝 迊 雑 帀 啑 煠 咂 币 杂 咔 嘁 抸 拤 沞 疀 砸 譗 鉔 雥 韴 㧜 㬁 䕹 䙄 䞙 䨿 䪞 䵽
|
||||
잣 㗯
|
||||
장 長 場 將 章 壯 狀 張 掌 丈 腸 帳 障 獎 葬 藏 莊 墻 裝 粧 臟 匠 庄 璋 蔣 樟 杖 仗 獐 醬 漿 檣 薔
|
||||
재 在 才 材 再 財 哉 栽 載 災 裁 宰 滓 齋 梓 縡 渽 齎 灾 纔 菑 榟 夈 扗 崽 賫 㢤 䝴 賷 㦲 捚 烖 賳
|
||||
쟁 爭 錚 諍 箏 崢 鎗 瞠 鐺 琤 猙 争 峥 筝 掁 崝 掙 䋫 挣 狰 诤 铮 噌 崡 棦 碀 趟 鏳 鏿 饓 鬇 㓌 㬹
|
||||
저 低 貯 諸 著 底 抵 沮 躇 猪 邸 咀 箸 詛 狙 苧 儲 楮 佇 這 渚 紵 杵 疽 雎 樗 菹 姐 觝 藷 齟 詆 氐
|
||||
적 赤 的 敵 適 賊 積 籍 跡 績 摘 寂 滴 蹟 笛 炙 迹 嫡 藉 謫 狄 吊 迪 勣 翟 鏑 荻 糴 逖 覿 幘 磧 樀
|
||||
전 田 前 全 電 戰 展 傳 典 錢 專 轉 殿 顚 甸 悛 奠 箭 銓 箋 塡 餞 篆 纏 剪 煎 廛 氈 輾 顫 澱 癲 栓
|
||||
절 節 絶 切 折 竊 截 浙 癤 窃 軼 晢 岊 卩 晣 蕝 嶻 絕 㔾 绝 棁 疖 节 卪 尐 幯 撧 擮 棳 楶 沏 聺 苆
|
||||
점 店 點 占 漸 粘 霑 点 拈 岾 鮎 玷 覘 佔 簟 苫 墊 黏 颭 蔪 笘 蛅 坫 阽 痁 扂 蒧 㸃 磹 鲇 垫 渐 觇
|
||||
장 長 場 將 章 壯 狀 張 掌 丈 腸 帳 障 獎 葬 藏 莊 墻 裝 粧 臟 匠 庄 璋 蔣 樟 杖 仗 獐 醬 漿 檣 薔 奬 臧 贓 牆 欌 暲 将 装 戕 瘴 嶂 鏘 糚 漳 萇 奘 賬 麞 牂 妝 嬙 廧 橦 鄣 傽 羘 餦 壮 墙 蔵 荘 膓 奨 臓 贜 塲 髒 駔 槳 慞 嶈 螿 扙 斨 粻 樁 蘠 醤 摪 賍 蹡 奖 脏 赃 场 妆 嫱 帐 张 桨 桩 樯 浆 肠 苌 蒋 蔷 螀 账 酱 锵 长 驵 鳉 丬 仉 仧 兏 匞 匨 塟 墇 墏 壵 娤 嫜 幛 幥 弉 梉 焋 爿 牄 瓺 痮 瘬
|
||||
재 在 才 材 再 財 哉 栽 載 災 裁 宰 滓 齋 梓 縡 渽 齎 灾 纔 菑 榟 夈 扗 崽 賫 㢤 䝴 賷 㦲 捚 烖 賳 㦳 赍 斋 财 载 龇 侢 傤 儎 喍 斎 洅 溨 睵 酨 釮 鉙 齜 㒲 㗍 㘽 㧳 㩟 㪰 㱰 䍉 䏁 䬩 䮨 䴭 䵧
|
||||
쟁 爭 錚 諍 箏 崢 鎗 瞠 鐺 琤 猙 争 峥 筝 掁 崝 掙 䋫 挣 狰 诤 铮 噌 崡 棦 碀 趟 鏳 鏿 饓 鬇 㓌 㬹 䍵 䟫 䦛 䦶 䱢
|
||||
저 低 貯 諸 著 底 抵 沮 躇 猪 邸 咀 箸 詛 狙 苧 儲 楮 佇 這 渚 紵 杵 疽 雎 樗 菹 姐 觝 藷 齟 詆 氐 宁 柢 瀦 苴 杼 袛 潴 牴 蛆 羝 褚 豬 罝 岨 芧 陼 伹 筯 泞 葅 趄 翥 竚 滁 䃴 眡 櫧 篨 詝 袓 怚 抯 摴 疷 砠 禇 㡳 㫝 呧 弤 疧 茋 㤖 伫 储 柠 槠 橥 纻 苎 诅 诋 贮 这 龃 仾 劯 唨 坥 坾 墸 奃 她 媎 庅 拞 掋 櫡 櫫 濐 眝 羜 聜 菧 蕏 藸 蝫 蠩 豠 趆 跙 軧 阺 骶 鯳 鴡 㑏 㓳 㝉 㡹 㪆 㫂 㭬 㭽 㯉 㵭
|
||||
적 赤 的 敵 適 賊 積 籍 跡 績 摘 寂 滴 蹟 笛 炙 迹 嫡 藉 謫 狄 吊 迪 勣 翟 鏑 荻 糴 逖 覿 幘 磧 樀 菂 馰 踖 啇 篴 靮 廸 頔 苖 逷 耤 籊 擿 讁 墑 䟱 嚁 趯 㰅 㺓 庴 樍 歒 甋 硳 襀 鸐 积 碛 籴 绩 觌 谪 贼 镝 唙 啲 嘀 廭 戝 旳 梑 浾 漃 灻 烾 熵 玓 瓋 癪 聻 肑 葃 蔋 蔐 藡 蠈 豴 躤 鰿 㒀 㠃 㢩 㣙 㭙 㯖 㹍 䁤 䊞 䊮 䚍 䚐 䢰 䣢 䤲 䨀 䨤 䮰 䯼 䰹 䳭 䴞 䵂 䵠
|
||||
전 田 前 全 電 戰 展 傳 典 錢 專 轉 殿 顚 甸 悛 奠 箭 銓 箋 塡 餞 篆 纏 剪 煎 廛 氈 輾 顫 澱 癲 栓 鐫 詮 雋 佺 佃 鈿 琠 淀 筌 塼 畑 靦 牋 畋 腆 顓 翦 戔 瑱 痊 湔 縳 旃 躔 巓 羶 荃 磚 戩 鸇 吮 甎 邅 囀 飦 澶 揃 栴 屇 餰 鱣 槇 錪 畠 籛 膞 輇 嫥 癜 鄽 鋑 靛 鬋 銭 烇 竱 瑑 戦 跧 戬 笺 闐 剸 填 氊 饘 㙉 橏 倎 纒 膻 譾 顛 淟 沺 搌 瘨 縓 謭 槙 滇 傎 瀍 牷 拴 瑼 硂 窴 篿 䇳 搷 晪 朘 瑔 㕓 㫋
|
||||
절 節 絶 切 折 竊 截 浙 癤 窃 軼 晢 岊 卩 晣 蕝 嶻 絕 㔾 绝 棁 疖 节 卪 尐 幯 撧 擮 棳 楶 沏 聺 苆 蠘 蠞 蠽 趃 镻 㐉 㑜 㔃 㔢 㗫 㦢 㪿 㮞 㸅 㸞 䁀 䏳 䓆 䕙 䗻 䘁 䟙 䩢 䪼 䫕 䲙
|
||||
점 店 點 占 漸 粘 霑 点 拈 岾 鮎 玷 覘 佔 簟 苫 墊 黏 颭 蔪 笘 蛅 坫 阽 痁 扂 蒧 㸃 磹 鲇 垫 渐 觇 飐 唸 嚸 奌 惦 掂 煔 秥 胋 螹 袩 誗 踮 鏩 頕 㓠 㝪 㶘 㶣 㼭 䀡 䍄 䘂 䘋 䛸 䟋 䤔 䩇
|
||||
접 接 蝶 渫 摺 揲 楪 鰈 慴 檝 蹀 跕 椄 蜨 艓 褋 䐑 鲽 挕 擑 聑 菨 襵 㑙 㢎 䐲 䝃 䝕 䪓
|
||||
정 正 定 丁 政 情 庭 停 精 靜 貞 井 淨 頂 廷 征 程 亭 整 鄭 訂 晶 呈 鼎 旌 挺 禎 偵 汀 楨 艇 珽 靖
|
||||
제 弟 第 題 諸 祭 除 帝 製 制 濟 提 際 齊 堤 劑 悌 蹄 梯 啼 霽 臍 薺 醍 荑 齎 儕 躋 擠 隄 娣 禔 睇
|
||||
조 朝 祖 助 鳥 調 造 早 兆 條 照 操 租 弔 潮 組 跳 趙 曺 祚 燥 措 遭 釣 彫 曹 詔 阻 粗 漕 凋 稠 藻
|
||||
정 正 定 丁 政 情 庭 停 精 靜 貞 井 淨 頂 廷 征 程 亭 整 鄭 訂 晶 呈 鼎 旌 挺 禎 偵 汀 楨 艇 珽 靖 釘 幀 錠 碇 穽 町 酊 睛 炡 霆 菁 渟 淀 晸 綎 鉦 瀞 玎 諪 鯖 檉 姃 柾 湞 鋌 証 灯 筳 靚 酲 梃 怔 叮 棖 莛 婷 琤 珵 疔 鋥 桯 佂 婧 掟 遉 静 净 㝎 鞓 浄 泟 㫌 埥 頲 矴 侹 涏 飣 凈 阱 琔 赬 仃 頳 揁 裎 烶 脡 蜓 愸 睜 誔 㵾 旍 椗 濎 竧 耵 鼮 㘫 㲂 䄇 䅍 䵺 证 侦 帧 枨 柽 桢 浈 睁 祯 蛏 订 贞
|
||||
제 弟 第 題 諸 祭 除 帝 製 制 濟 提 際 齊 堤 劑 悌 蹄 梯 啼 霽 臍 薺 醍 荑 齎 儕 躋 擠 隄 娣 禔 睇 稊 瑅 鯷 緹 踶 猘 蹏 韲 鍗 鮧 苐 剤 褆 滛 斉 隮 磾 癠 穧 虀 穄 綈 鶗 嚌 淛 傺 鵜 渧 鞮 嗁 懠 僀 泲 漈 璾 偍 偙 徲 摕 蒢 鑇 騠 㫼 䀸 侪 剂 哜 挤 济 済 绨 缇 脐 荠 蛴 诸 跻 锑 际 霁 题 鲚 鳀 鹈 齐 齑 亝 俤 厗 埞 姼 媂 媞 怟 惿 慸 憏 斊 栬 梊 櫅 漽 焍 狾 珶 睼 碮 碲 禵 罤 腣 艩 萕 蕛 蝭 蠐
|
||||
조 朝 祖 助 鳥 調 造 早 兆 條 照 操 租 弔 潮 組 跳 趙 曺 祚 燥 措 遭 釣 彫 曹 詔 阻 粗 漕 凋 稠 藻 肇 嘲 爪 眺 躁 棗 糟 詛 繰 雕 吊 俎 炤 窕 蚤 槽 晁 璪 噪 皁 祧 徂 胙 糶 竈 厝 刁 絛 譟 鵰 澡 蜩 殂 鼂 臊 嘈 佻 琱 蔦 銚 糙 找 洮 誂 傮 嬥 懆 笊 絩 艚 鈟 鋽 鯛 条 阼 皂 慥 脁 粜 罩 旐 朓 啁 爼 窵 鯈 窱 螬 曌 沠 珇 瞾 縧 蓧 垗 恌 莇 藋 釂 駋 䆴 晀 碉 竃 竨 罺 艁 趮 醩 䝖 䧂 绦 枣 灶 窎 组
|
||||
족 足 族 簇 鏃 蔟 瘯 哫 镞 踿 㞺 㵀
|
||||
존 存 尊 拵 鐏 燇 橂 袸 銌
|
||||
졸 卒 拙 猝 倅 脺 捽 㐒 稡 椊 踤 卆 炪 箤 㐍 㰵 䂐 䘚 䚝 䯿 䱣
|
||||
종 終 宗 種 從 鐘 縱 腫 琮 綜 踵 踪 慫 鍾 淙 悰 棕 倧 蹤 瘇 螽 椶 蓯 粽 伀 慒 柊 樅 瑽 従 縦 嵸 騣
|
||||
종 終 宗 種 從 鐘 縱 腫 琮 綜 踵 踪 慫 鍾 淙 悰 棕 倧 蹤 瘇 螽 椶 蓯 粽 伀 慒 柊 樅 瑽 従 縦 嵸 騣 蓗 鬃 賨 踨 徖 忪 倊 尰 朡 籦 糉 緃 緵 衳 鍐 钟 从 怂 枞 疭 纵 终 综 肿 苁 锺 骔 骙 傱 刣 喠 堫 堹 妐 婃 孮 嵏 嵕 嵷 幒 徔 徸 昮 暰 歱 汷 泈 潀 炂 煄 熧 猔 猣 瘲 瞛 碂 磫 稯 糭 緟 翪 腙 艐 葼 蔠 蝬 誴 諥 豵 賩 蹱 銿 錝 騌 騤 鬉 鬷 鯮 鯼 鴤 鶎 鼨 㗰 㙡 㚇 㜡 㡖 㢔 㣫 㣭 㥖 㨑 㯶 㹣 㻜 䁓
|
||||
좌 左 坐 佐 座 挫 髽 剉 莝 痤 㘴 脞 袏 锉 侳 咗 夎 屮 睉 矬 蓌 蓙 趖 遳 銼 㔫 㘸 㛗 㝾 㟇 㭫 䂳 䟶
|
||||
죄 罪 辠 嶵 檌 㠑 㪓
|
||||
주 主 住 走 注 晝 朱 宙 酒 州 周 舟 柱 洲 珠 株 奏 鑄 駐 疇 週 躊 誅 註 紬 做 稠 廚 紂 嗾 呪 輳 冑
|
||||
주 主 住 走 注 晝 朱 宙 酒 州 周 舟 柱 洲 珠 株 奏 鑄 駐 疇 週 躊 誅 註 紬 做 稠 廚 紂 嗾 呪 輳 冑 胄 籌 湊 綢 澍 炷 蛛 姝 酎 侏 枓 齟 霔 肘 儔 拄 裯 遒 賙 輈 侜 幬 腠 迬 霌 硃 蔟 尌 籒 蛀 詋 丟 趎 鉒 鼄 厨 蹰 躕 譸 籀 黈 㴻 邾 紸 䛆 麈 伷 噣 淍 咮 晭 跦 㕑 椆 珘 唒 妵 揍 㕀 怞 楱 疰 秼 絑 薵 軴 馵 㕏 㡡 㴤 䌧 昼 丢 俦 凑 帱 橱 畴 筹 纣 绸 诛 诪 赒 踌 辀 辏 鋳 铥 铸 驻 鸼 䌷 丶 厾 咒
|
||||
죽 竹 粥 鬻 俼
|
||||
준 準 俊 遵 准 浚 峻 駿 埈 濬 晙 樽 竣 蠢 儁 雋 畯 逡 寯 焌 罇 蹲 隼 餕 撙 惷 綧 噂 踆 皴 埻 墫 鐏
|
||||
준 準 俊 遵 准 浚 峻 駿 埈 濬 晙 樽 竣 蠢 儁 雋 畯 逡 寯 焌 罇 蹲 隼 餕 撙 惷 綧 噂 踆 皴 埻 墫 鐏 鱒 鵔 凖 睃 㕙 䥴 䞭 踳 㑺 㻐 捘 殾 陖 鵕 㡒 馂 骏 鳟 偆 僔 壿 夋 宒 嶟 懏 稕 竴 箺 繜 葰 訰 譐 迿 鎨 鶽 鷷 㑓 㒞 㝦 㷪 㻪 㼱 䐏 䔿 䣩 䮞 純 䜭
|
||||
줄 茁 乼 崒 崪 窋 笜 㤕
|
||||
줏 㗟
|
||||
중 中 重 衆 仲 眾 褈 众 妕 媑 狆 筗 舯 茽 蚛 蝩 衶 迚 鈡 隀 㐺 㑖 㣡 㲴 㴢 䌬 䝦 䦿 䳯
|
||||
@@ -426,66 +426,66 @@
|
||||
즐 櫛 騭 喞 瀄 堲 栉 骘 擳 楖 㘉
|
||||
즘 怎
|
||||
즙 輯 汁 葺 楫 檝 蕺 濈 湒 霵
|
||||
증 曾 增 證 贈 症 憎 蒸 拯 烝 甑 繒 証 嶒 矰 罾 曽 増 璔 橧 鄫 熷 驓 䎖 缯 赠 囎 撜 潧 磳 竲 篜 譄
|
||||
지 地 知 至 志 之 止 紙 持 指 識 支 只 枝 智 池 誌 遲 旨 址 芝 脂 肢 祉 咫 摯 枳 祗 趾 祇 砥 贄 漬
|
||||
증 曾 增 證 贈 症 憎 蒸 拯 烝 甑 繒 証 嶒 矰 罾 曽 増 璔 橧 鄫 熷 驓 䎖 缯 赠 囎 撜 潧 磳 竲 篜 譄 鱛 㣒 㷥 㽪 䇰 䉕 䒱 䕄 䗀 䙢 䡕 䥌 䥭 䰝
|
||||
지 地 知 至 志 之 止 紙 持 指 識 支 只 枝 智 池 誌 遲 旨 址 芝 脂 肢 祉 咫 摯 枳 祗 趾 祇 砥 贄 漬 沚 芷 蜘 墀 秪 躓 箎 舐 踟 篪 鷙 榰 坻 扺 軹 泜 阯 痣 鮨 遅 㫖 秖 祬 䘭 輊 胝 鳷 帋 䑛 搘 蚳 厎 墬 鋕 驇 彽 恉 抧 遟 䙙 䞇 䶵 挚 渍 纸 贽 轵 轾 迟 鸷 俧 倁 凪 劧 吱 哋 坁 坔 坘 埊 娡 岻 嶳 忯 杫 栺 梽 椥 汥 汦 洔 淽 潪 疻 砋 秓 竾 筂 筫 箈 綕 胑 舓 衼 覟 觗 訨 謘 貾 赿 酯 馶 鴲 鼅 㕄 㞢
|
||||
직 直 職 織 稷 稙 禝 溭 㮨 聀 犆 樴 嬂 织 职 膱 蘵 蟙 軄 㞋 㹄 䐈
|
||||
진 眞 進 盡 辰 陳 振 震 陣 珍 鎭 晉 塵 津 秦 診 嗔 疹 賑 晋 軫 唇 搢 臻 殄 縉 榛 袗 瑨 畛 璡 縝 瞋
|
||||
질 質 疾 秩 姪 迭 跌 窒 叱 嫉 帙 佚 桎 膣 侄 蛭 瓆 絰 軼 躓 騭 垤 郅 鑕 蒺 咥 耋 晊 抶 銍 袠 瓞 袟
|
||||
진 眞 進 盡 辰 陳 振 震 陣 珍 鎭 晉 塵 津 秦 診 嗔 疹 賑 晋 軫 唇 搢 臻 殄 縉 榛 袗 瑨 畛 璡 縝 瞋 溱 桭 蔯 趁 儘 瑱 稹 蓁 珒 鬒 侲 鉁 螴 真 禛 鎮 趂 疢 眹 胗 䫃 溍 珎 姫 敶 榗 眕 紾 䀌 搸 濜 薼 螓 轃 辴 㐱 䀼 尽 尘 纼 缙 缜 诊 赈 轸 进 镇 阵 陈 侭 嗪 嚍 堻 塦 壗 嫀 嫃 嬧 屒 帪 弫 抮 挋 敐 昣 枃 樄 樼 殝 浕 獉 琎 瑧 籈 紖 絼 縥 聄 臸 蒖 薽 蜄 裖 誫 謓 軙 迧 錱 靕 駗 黰 㖘 㣀 㥲 㬐 㬜
|
||||
질 質 疾 秩 姪 迭 跌 窒 叱 嫉 帙 佚 桎 膣 侄 蛭 瓆 絰 軼 躓 騭 垤 郅 鑕 蒺 咥 耋 晊 抶 銍 袠 瓞 袟 礩 祑 昳 翐 挃 妷 峌 恎 柣 耊 㗌 绖 质 铚 锧 儨 劕 厔 庢 愱 戜 槉 櫍 洷 潌 眣 眰 秷 紩 胅 腟 臷 苵 螏 螲 詄 豑 豒 貭 㑵 㗧 㘍 㜱 㜼 㩫 㲳 㲺 䏄 䑇 䜠 䟈 䬹 䱃
|
||||
짐 朕 斟 鴆 酖 酙 㯢 鸩 栚 㘰 㪸 㮳 㴨 㼉 䧵 䲴
|
||||
집 集 執 輯 什 緝 潗 鏶 戢 咠 諿 縶 㠍 慹 㙫 㠎 䌖 蓻 䐕 执 絷 缉 辑 亼 偮 卙 嶯 瓡 艥 謺 鍓 雦 雧
|
||||
집 集 執 輯 什 緝 潗 鏶 戢 咠 諿 縶 㠍 慹 㙫 㠎 䌖 蓻 䐕 执 絷 缉 辑 亼 偮 卙 嶯 瓡 艥 謺 鍓 雦 雧 㗊 㗱 㣬 㱷 㴕 䁒 䅤 䉅 䩰 䮶 楫
|
||||
짓 嗭
|
||||
징 徵 懲 澄 澂 瀓 瞪 癥 徴 憕 惩 㠞
|
||||
차 車 次 此 且 借 差 茶 叉 遮 嗟 蹉 箚 嵯 釵 磋 侘 槎 苴 瑳 佽 奓 硨 岔 偖 徣 姹 䂨 劄 汊 杈 莗 唶
|
||||
착 着 著 捉 錯 躇 搾 鑿 窄 齪 斲 戳 擉 斵 斮 諑 娖 浞 笮 娕 珿 凿 诼 错 龊 丵 剒 撯 擆 灂 穛 篧 簎
|
||||
찬 贊 讚 餐 瓚 燦 鑽 璨 撰 饌 纂 簒 竄 纘 粲 飡 澯 攢 爨 賛 欑 巑 趲 儧 儹 劗 湌 纉 㸑 攅 讃 酇 篡
|
||||
찰 察 刹 札 擦 紮 扎 拶 咱 哳 巀 剎 檫 礸 詧 鑔 铡 镲 乲 偺 嚓 攃 桚 櫒 礤 糌 紥 耫 蚻 蠿 鍘 㞉 㦫
|
||||
참 參 慘 慙 斬 站 讒 僭 讖 塹 懺 叅 慚 驂 譖 黲 巉 嶄 槧 鑱 憯 摻 攙 饞 欃 儳 毚 鏨 参 惨 僣 㕘 叁
|
||||
창 唱 窓 昌 倉 創 蒼 暢 彰 滄 敞 昶 倡 漲 槍 瘡 愴 脹 娼 猖 菖 廠 艙 悵 搶 鬯 刱 倀 蹌 鎗 閶 氅 惝
|
||||
채 採 菜 采 彩 債 蔡 埰 寨 綵 釵 寀 砦 茝 棌 瘵 蠆 瘥 倸 囆 债 虿 钗 䌽 婇 榸 睬 縩 踩 靫 㥒 㦅 㳗
|
||||
책 責 冊 策 柵 翟 簀 嘖 磔 幘 笧 蚱 栅 册 筞 䇿 㥽 舴 茦 謮 啧 帻 箦 责 厇 嫧 憡 拺 敇 皟 瞔 箣 籷
|
||||
차 車 次 此 且 借 差 茶 叉 遮 嗟 蹉 箚 嵯 釵 磋 侘 槎 苴 瑳 佽 奓 硨 岔 偖 徣 姹 䂨 劄 汊 杈 莗 唶 扯 撦 鹺 挓 訍 跐 踷 醝 㨋 砗 舣 车 鹾 佌 偧 偨 厏 唓 嗏 嗻 奲 嫅 嵳 扠 搓 搽 暛 栨 笡 紁 絘 肞 艖 芆 蒫 蔖 虘 蛼 衩 褨 譇 銟 鎈 齇 齹 㗬 㞖 㢒 㣾 㦋 㷢 㸙 㼮 㽨 㾝 䁟 䂘 䃎 䊬 䐒 䐤 䑘 䑡 䒲 䖕 䖳 䞣 䟕 䠡 䡨 䣜 䦈 䯸 䰈 䰩 䱹 䳐 䴾 䵙 䶥
|
||||
착 着 著 捉 錯 躇 搾 鑿 窄 齪 斲 戳 擉 斵 斮 諑 娖 浞 笮 娕 珿 凿 诼 错 龊 丵 剒 撯 擆 灂 穛 篧 簎 籱 糳 縒 莡 辵 辶 逪 鋜 鑡 㒂 㓸 㚟 㲋 䇥 䓎 䕴 䥘 䥣
|
||||
찬 贊 讚 餐 瓚 燦 鑽 璨 撰 饌 纂 簒 竄 纘 粲 飡 澯 攢 爨 賛 欑 巑 趲 儧 儹 劗 湌 纉 㸑 攅 讃 酇 篡 濽 穳 鑚 瓉 禶 攛 灒 籫 躥 丳 櫕 襸 鄼 赞 撺 攒 灿 瓒 窜 缵 趱 蹿 躜 酂 镩 馔 儏 囋 殩 熶 爘 篹 籑 繤 羼 薒 躦 鑹 饡 㛑 㜺 㠝 㣓 㦌 㬄 㸇 㹽 㺗 㻮 䂎 䉔 䉵 䌣 䞼 䟎 䡽 䬤 䬸 䰖 䱗
|
||||
찰 察 刹 札 擦 紮 扎 拶 咱 哳 巀 剎 檫 礸 詧 鑔 铡 镲 乲 偺 嚓 攃 桚 櫒 礤 糌 紥 耫 蚻 蠿 鍘 㞉 㦫 㱜 㳐 㳨 䃰 䓭 䕓 䥷 䶪
|
||||
참 參 慘 慙 斬 站 讒 僭 讖 塹 懺 叅 慚 驂 譖 黲 巉 嶄 槧 鑱 憯 摻 攙 饞 欃 儳 毚 鏨 参 惨 僣 㕘 叁 叄 旵 䜛 墋 謲 懴 毶 詀 醦 䜟 劖 噆 嵾 嶊 朁 瀺 覱 趻 堑 崭 惭 搀 斩 椠 瘆 碜 谗 谮 谶 錾 镵 馋 骖 黪 傪 嚵 壍 嬠 嬱 嶃 摲 獑 瘮 磛 磣 艬 譛 酁 魙 㜗 㜞 㟥 㟻 㠁 㦧 㨻 㰫 㸥 㺥 㽩 㿊 䁪 䂁 䑎 䟃 䤘 䤫 䧯 䪌 䱿 䳻 䶨
|
||||
창 唱 窓 昌 倉 創 蒼 暢 彰 滄 敞 昶 倡 漲 槍 瘡 愴 脹 娼 猖 菖 廠 艙 悵 搶 鬯 刱 倀 蹌 鎗 閶 氅 惝 窻 憃 傖 鏦 窗 瑲 鋹 鶬 凔 戧 錆 椙 囱 牕 淐 摐 剏 剙 牎 畼 瑒 篬 厰 磢 謒 錩 枪 仓 伥 伧 创 呛 怅 怆 戗 抢 沧 涨 炝 玚 玱 畅 疮 胀 舱 苍 跄 锖 锠 阊 鲳 鸧 仺 僘 刅 嗆 囪 嵢 惐 摤 晿 淌 濸 焻 熗 獊 琩 螥 裮 誯 賶 鯧 鼚 㒉 㘟 㧿 㫤 㼽 䄝 䅛 䅮 䆫 䎫 䗉 䚎 䞎 䠀 䢢 䤌 䥊 䩨 䮖 䱽 䲝
|
||||
채 採 菜 采 彩 債 蔡 埰 寨 綵 釵 寀 砦 茝 棌 瘵 蠆 瘥 倸 囆 债 虿 钗 䌽 婇 榸 睬 縩 踩 靫 㥒 㦅 㳗 䃀 䌨 䐆 䐱 䘍 䞗 䠕 䣋 䰂 琗 責
|
||||
책 責 冊 策 柵 翟 簀 嘖 磔 幘 笧 蚱 栅 册 筞 䇿 㥽 舴 茦 謮 啧 帻 箦 责 厇 嫧 憡 拺 敇 皟 瞔 箣 籷 粣 萗 蓛 蔶 虴 諎 迮 銏 鏼 頙 㖽 㟙 㨲 㩍 㩞 㳻 㿭 䜺 䞰 䟄 䯔 䶦
|
||||
처 處 妻 悽 凄 萋 淒 覷 褄 郪 䖏 处 処 覰 觑 刞 竌 紪 緀 耝 覻 霋 鶈 㜘 䁦
|
||||
척 尺 拓 斥 戚 刺 隻 陟 滌 擲 瘠 脊 倜 慽 剔 蹠 惕 跖 摭 躑 俶 蜴 塉 捗 呎 坧 褁 慼 蹐 掦 鶺 隲 蹢
|
||||
천 天 千 川 泉 淺 遷 賤 薦 踐 串 釧 擅 穿 喘 闡 阡 舛 韆 仟 玔 蚕 荐 倩 濺 洊 蕆 蒨 茜 芊 俴 儃 辿
|
||||
철 鐵 哲 徹 撤 澈 喆 轍 綴 凸 輟 銕 掇 啜 惙 餮 歠 剟 埑 錣 飻 鉄 腏 鐡 畷 䮕 醊 㯙 悊 㬚 彻 缀 辍
|
||||
첨 添 尖 瞻 僉 諂 籤 沾 詹 簽 甛 忝 簷 憸 檐 槧 襜 栝 惉 瀸 幨 櫼 甜 韂 籖 悿 湉 掭 沗 裧 冿 噡 婖
|
||||
첩 妾 諜 帖 牒 疊 貼 捷 輒 堞 睫 褶 喋 鰈 怗 褺 倢 呫 輙 㡇 疂 倿 氎 耴 䴴 婕 牃 疉 穕 䩞 钻 叠 畳
|
||||
청 靑 淸 請 聽 晴 廳 菁 鯖 倩 听 凊 圊 婧 蜻 鶄 青 清 㕔 聼 聴 廰 厅 庁 请 鲭 䴖 厛 夝 掅 暒 氰 甠
|
||||
체 體 切 遞 滯 替 逮 締 涕 綴 諦 剃 体 軆 砌 棣 彘 泚 蕞 蔕 薙 蒂 殢 靆 嚏 髰 遆 滞 逓 禘 掣 遰 杕
|
||||
초 草 初 招 超 礎 抄 肖 秒 哨 楚 焦 礁 稍 樵 貂 梢 炒 憔 硝 蕉 醋 醮 椒 酢 剿 艸 湫 苕 迢 誚 勦 悄
|
||||
촉 觸 屬 燭 促 蜀 囑 矗 鏃 躅 矚 髑 爥 曯 薥 嘱 瞩 斸 瘃 蠋 劚 歜 灟 触 烛 㔉 亍 孎 斶 欘 竐 脨 臅
|
||||
척 尺 拓 斥 戚 刺 隻 陟 滌 擲 瘠 脊 倜 慽 剔 蹠 惕 跖 摭 躑 俶 蜴 塉 捗 呎 坧 褁 慼 蹐 掦 鶺 隲 蹢 踢 墄 墌 鼜 掷 涤 踯 鹡 傶 叺 嵴 彳 徏 悐 惖 硩 磩 粎 膌 蚇 鏚 齣 㘮 㞝 㡿 㥻 㽚 䋇 䑾 䗩 䞠 䞶 䯜
|
||||
천 天 千 川 泉 淺 遷 賤 薦 踐 串 釧 擅 穿 喘 闡 阡 舛 韆 仟 玔 蚕 荐 倩 濺 洊 蕆 蒨 茜 芊 俴 儃 辿 祆 僢 瓩 粁 臶 靝 迁 遄 刋 燀 賎 栫 韉 拪 剶 奷 荈 梴 棈 洤 湶 綪 闎 韀 浅 践 钎 冁 忏 扦 溅 蒇 贱 钏 阐 鞯 鳈 侟 兛 兲 囅 圌 圱 圲 夼 巛 幝 揼 曻 杄 楾 櫏 歂 氚 汌 汘 瀳 灛 灥 牮 猭 珔 瑏 皘 硟 竏 篅 篟 繟 腨 葲 蔳 譂 谸 輤 輲 釺 閳 靔 鰁 㐪 㚈 㟫 㣤 㯌 㱛 䀒 䀖 䄹 䆤 䆥 䉦 䍎 䑶 䒶 䚶
|
||||
철 鐵 哲 徹 撤 澈 喆 轍 綴 凸 輟 銕 掇 啜 惙 餮 歠 剟 埑 錣 飻 鉄 腏 鐡 畷 䮕 醊 㯙 悊 㬚 彻 缀 辍 辙 铁 锇 勶 叕 啠 嚞 嚽 敠 敪 涰 烲 焎 爡 瞮 砓 罬 聅 蛈 蜇 裰 諁 酫 醛 鋨 驖 㙍 䄌 䒆 䚢 䞵 䟾 䥫 䫎
|
||||
첨 添 尖 瞻 僉 諂 籤 沾 詹 簽 甛 忝 簷 憸 檐 槧 襜 栝 惉 瀸 幨 櫼 甜 韂 籖 悿 湉 掭 沗 裧 冿 噡 婖 燂 鑯 㤁 䪜 签 佥 裣 谄 锘 厃 嶦 敁 瀐 灊 籡 舔 舚 菾 虃 襝 讇 酟 鋓 鍩 鐱 閚 餂 黇 㐁 㑒 㔐 㖭 㙴 㚲 㜬 㡨 㤐 㦰 㬲 㮇 㵇 㶺 㾆 䄡 䄼 䇝 䋬 䏦 䑚 䞌 䠨 䦓
|
||||
첩 妾 諜 帖 牒 疊 貼 捷 輒 堞 睫 褶 喋 鰈 怗 褺 倢 呫 輙 㡇 疂 倿 氎 耴 䴴 婕 牃 疉 穕 䩞 钻 叠 畳 谍 贴 辄 偼 媫 崨 惵 曡 淁 緁 脻 萜 蓵 誱 踕 踥 迠 鉆 鮿 鯜 㤴 㥈 㨗 㨩 㩸 㩹 㫸 㬪 㭯 㱌 㲲 㳧 䌌 䑖 䕈 䠟 䥡 䧪 䴑 䵿
|
||||
청 靑 淸 請 聽 晴 廳 菁 鯖 倩 听 凊 圊 婧 蜻 鶄 青 清 㕔 聼 聴 廰 厅 庁 请 鲭 䴖 厛 夝 掅 暒 氰 甠 碃 郬 㵙 䝼 䞍 䨝
|
||||
체 體 切 遞 滯 替 逮 締 涕 綴 諦 剃 体 軆 砌 棣 彘 泚 蕞 蔕 薙 蒂 殢 靆 嚏 髰 遆 滞 逓 禘 掣 遰 杕 躰 髢 疐 蝃 䙗 嵽 螮 餟 屜 懘 栘 褅 揥 摰 㚄 䪆 屉 缔 谛 达 递 䗖 䴘 叇 嚔 墆 崹 挮 搋 摖 楴 璏 痸 皉 矵 磜 祶 笍 迏 迖 釱 鉪 銐 鐟 霴 骵 鬀 鬄 鷈 鷉 㓹 㔸 㡗 㬱 㿃 䀙 䄟 䇧 䌡 䎮 䏅 䐭 䟷 䠠 䶏 諟 玼
|
||||
초 草 初 招 超 礎 抄 肖 秒 哨 楚 焦 礁 稍 樵 貂 梢 炒 憔 硝 蕉 醋 醮 椒 酢 剿 艸 湫 苕 迢 誚 勦 悄 綃 鈔 軺 峭 怊 愀 髫 鞘 譙 杪 噍 岧 踔 燋 鷦 顦 劭 俏 趠 僬 嶕 鍬 偢 隹 嫶 耖 鍫 齠 椘 䎐 憷 岹 弨 撨 眧 檚 膲 芀 蟭 鉊 䘯 绡 艹 诌 诮 谯 轺 钞 锹 鹪 龆 仦 仯 僺 儊 劁 劋 吵 妱 屌 嶣 巐 帩 愺 摷 櫵 欩 潐 濋 焣 煍 燞 璴 瘄 癄 睄 瞧 祒 茮 荹 菬 萔 蕱 藮 觘 訬 謅 趭 轈 釥 鍣 鐎 陗 鞩 韒 騲
|
||||
촉 觸 屬 燭 促 蜀 囑 矗 鏃 躅 矚 髑 爥 曯 薥 嘱 瞩 斸 瘃 蠋 劚 歜 灟 触 烛 㔉 亍 孎 斶 欘 竐 脨 臅 蠾 襡 襩 趗 踀 钃 鸀 㙇 㯮 㻿 䃚 䇍 䌵 䎌 䕽 䙱 䛤 䟉 䟟 䠱 䪅
|
||||
촌 寸 村 忖 邨 吋 刌 屗 澊 籿
|
||||
총 總 聰 銃 寵 叢 塚 摠 悤 蔥 憁 葱 驄 蓯 鏦 騘 冢 捴 総 揔 匆 偬 漎 怱 緫 聦 傯 棇 潨 聡 忩 璁 藂
|
||||
총 總 聰 銃 寵 叢 塚 摠 悤 蔥 憁 葱 驄 蓯 鏦 騘 冢 捴 総 揔 匆 偬 漎 怱 緫 聦 傯 棇 潨 聡 忩 璁 藂 惣 漗 篵 㷓 䓗 䤸 丛 宠 总 聪 铳 骢 埫 愡 搃 樬 樷 欉 潈 濍 灇 焧 熜 燪 爜 縂 繱 茐 蟌 謥 鍯 鏓 㞱 㹅 䆹 䈡 䉥 䐋 䕺 䗓 䡯 䧭 䰌
|
||||
촬 撮 娺 攥 窡 繓 襊 㔍 㭮 㵶 䆯 䵵
|
||||
쵀 啐 啛 祽 錊 䦤
|
||||
최 最 催 崔 摧 蕞 淬 榱 璀 漼 縗 脧 嘬 磪 㝡 嗺 墔 慛 槯 綷 缞 凗 樶 欼 熣 獕 皠 穝 紣 羧 膗 鋷 鏙
|
||||
추 秋 推 丑 追 抽 醜 趨 鄒 楸 椎 樞 墜 酋 芻 錐 槌 錘 鎚 鰍 藪 雛 騶 皺 諏 湫 杻 萩 麤 蒭 瘳 惆 簉
|
||||
축 祝 丑 築 逐 縮 畜 蓄 軸 蹴 柚 蹙 筑 竺 舳 蹜 豖 槭 妯 鼀 踧 柷 滀 拀 閦 笁 蓫 䠞 缩 轴 丒 傗 嘼
|
||||
최 最 催 崔 摧 蕞 淬 榱 璀 漼 縗 脧 嘬 磪 㝡 嗺 墔 慛 槯 綷 缞 凗 樶 欼 熣 獕 皠 穝 紣 羧 膗 鋷 鏙 㜠 㝮 㵏 䊫 䔴 䘒 䙑 䧽 䴝
|
||||
추 秋 推 丑 追 抽 醜 趨 鄒 楸 椎 樞 墜 酋 芻 錐 槌 錘 鎚 鰍 藪 雛 騶 皺 諏 湫 杻 萩 麤 蒭 瘳 惆 簉 陬 箠 啾 愀 箒 搥 帚 鞦 僦 甃 捶 縋 魋 娵 騅 鰌 縐 鍬 鶖 鶵 妯 蝤 隹 揫 鵻 麄 龝 趍 篘 鯫 麁 萑 崷 橻 鄹 㨨 寉 棰 菆 揂 棷 煼 緅 諈 貙 顀 鬌 䨨 枢 锤 刍 坠 尴 皱 绉 缒 诹 趋 邹 锥 雏 驺 骓 鲰 鳅 鹙 䝙 侴 僽 吜 埾 媝 媰 尷 憱 掫 揪 搊 棸 榋 殠 沝 渞 湬 湭 溴 煪 犓 甀 睭 瞅 矁 硾 礈 秌 穐
|
||||
축 祝 丑 築 逐 縮 畜 蓄 軸 蹴 柚 蹙 筑 竺 舳 蹜 豖 槭 妯 鼀 踧 柷 滀 拀 閦 笁 蓫 䠞 缩 轴 丒 傗 嘼 噈 敊 斣 殧 稸 篫 縬 茿 蹵 鄐 顣 鱁 㗤 㛩 㜅 㰗 㾥 䙒 䙘 䙯 䥮 䮱
|
||||
춘 春 椿 瑃 櫄 杶 橁 䞐 堾 媋 旾 暙 杽 湷 睶 萅 萶 蝽 賰 鰆 鶞 㖺 㫩 㿤 䞺 䡅 䦮 䲠
|
||||
출 出 黜 朮 秫 术 怵 絀 岀 础 绌 怷 樎 欪 泏 炢 莍 黢 㑁 㔘 䘤 䟣 䢺
|
||||
충 忠 充 蟲 衝 衷 沖 冲 虫 忡 珫 艟 种 䖝 翀 盅 揰 浺 漴 茺 㤝 茧 嘃 爞 痋 祌 㓍 㥙 㧤 㮔 㳘 㳞 䂌
|
||||
충 忠 充 蟲 衝 衷 沖 冲 虫 忡 珫 艟 种 䖝 翀 盅 揰 浺 漴 茺 㤝 茧 嘃 爞 痋 祌 㓍 㥙 㧤 㮔 㳘 㳞 䂌 䆔 䘪 䝑 䟲 䡴
|
||||
췌 悴 萃 贅 膵 瘁 揣 惴 顇 疩 赘 忰
|
||||
취 取 就 吹 趣 臭 醉 聚 炊 娶 翠 脆 驟 鷲 嘴 揣 觜 毳 冣 橇 酔 竁 翆 骤 鹫 噿 嶉 槜 檇 欈 濢 璻 脃
|
||||
취 取 就 吹 趣 臭 醉 聚 炊 娶 翠 脆 驟 鷲 嘴 揣 觜 毳 冣 橇 酔 竁 翆 骤 鹫 噿 嶉 槜 檇 欈 濢 璻 脃 膬 臎 臰 酻 龡 㪜 㯔 㯜 㱖 䕜 䶴
|
||||
측 測 側 惻 仄 厠 廁 昃 稄 厕 侧 恻 测 庂 捑 昗 汄 畟 㳁 䈟
|
||||
츤 襯 櫬 藽 齔 儭
|
||||
츰 闖
|
||||
층 層 蹭 层 䁬
|
||||
치 治 致 齒 置 恥 値 徵 雉 痴 稚 峙 馳 侈 熾 幟 嗤 緻 痔 癡 緇 蚩 穉 嵯 埴 梔 淄 輜 寘 鴟 螭 廁 巵
|
||||
치 治 致 齒 置 恥 値 徵 雉 痴 稚 峙 馳 侈 熾 幟 嗤 緻 痔 癡 緇 蚩 穉 嵯 埴 梔 淄 輜 寘 鴟 螭 廁 巵 菑 錙 褫 豸 絺 薙 哆 畤 卮 鵄 跱 阤 黐 痓 鯔 鴙 耻 歯 懥 䐉 媸 夂 偫 眵 稺 觶 乿 廌 值 椔 歭 袳 陊 騺 㨖 㱀 庤 滍 瓻 糦 胵 裭 郗 㢋 㣥 䀿 栀 帜 炽 缁 觯 辎 锱 驰 鲻 鸱 齿 俿 傂 卶 垁 垑 妛 崰 崻 徝 恀 憄 懫 拸 搱 旘 杘 湽 甾 秲 紎 耛 芖 茌 茬 荎 葘 藢 袮 襧 訵 誃 誺 跮 輺 鉹 鍿 駤 鶅 黹
|
||||
칙 則 勅 飭 敕 恜 侙 慗 遫 鶒 鷘 则 饬 淔 趩 鉓 䳵
|
||||
친 親 襯 櫬 齔 藽 嚫 瀙 䞋 亲 榇 衬 龀 儬 媇 寴 澵 齓 䠳 䠴
|
||||
칠 七 漆 柒 㓒 㭍 桼 㐂 㓼 㓿 㔑 㯃 䜉 䣛
|
||||
침 針 沈 侵 寢 浸 枕 鍼 砧 湛 琛 諶 忱 沉 駸 鋟 寖 梣 椹 郴 祲 碪 伔 寑 抌 踸 揕 枮 梫 賝 䆮 䑣 寝
|
||||
침 針 沈 侵 寢 浸 枕 鍼 砧 湛 琛 諶 忱 沉 駸 鋟 寖 梣 椹 郴 祲 碪 伔 寑 抌 踸 揕 枮 梫 賝 䆮 䑣 寝 针 锓 骎 兓 唚 埐 夦 嶜 抋 濅 綅 綝 莐 萙 葴 誛 諃 鈂 鍖 霃 鮼 鱵 㑴 㓄 㓎 㕴 㝲 㪛 㴆 㴴 㶩 㾛 䈜 䒞 䘲 䚀 䚁 䜷 䤟 䥠 䪴 䫖 䫬 䭙 棽
|
||||
칩 蟄 馽 蛰 漐 釞 㙷 㞏 㞚 㮑
|
||||
칭 稱 秤 称 穪 偁 爯 㛵 䕝
|
||||
카 㻔 佧 胩
|
||||
쾌 快 獪 夬 噲 儈 侩 哙 叏 筷
|
||||
타 他 打 妥 墮 隋 惰 唾 陀 駝 舵 楕 馱 朶 咤 拖 迤 駄 躱 沱 詑 跎 詫 佗 鼉 駞 柁 拕 橢 鮀 垞 阤 坨
|
||||
탁 度 濁 拓 托 卓 濯 託 琢 擢 鐸 坼 啄 倬 柝 晫 琸 拆 橐 涿 逴 籜 踔 趠 沰 矺 蘀 乇 槖 椓 侂 跅 魠
|
||||
탄 歎 彈 炭 誕 灘 綻 憚 呑 坦 嘆 殫 癱 攤 憻 驒 惮 殚 弹 吞 瘓 撣 僤 嘽 掸 啴 弾 摊 滩 痪 瘫 绽 诞
|
||||
타 他 打 妥 墮 隋 惰 唾 陀 駝 舵 楕 馱 朶 咤 拖 迤 駄 躱 沱 詑 跎 詫 佗 鼉 駞 柁 拕 橢 鮀 垞 阤 坨 驒 鴕 堕 陁 它 吒 酡 嚲 㙐 垜 咜 奼 沲 媠 嶞 朵 陏 㓃 亸 椭 诧 锗 驮 驼 鸵 鼍 刴 剁 咃 咑 哚 垛 埵 堶 墯 嫷 尮 岮 崜 彵 憜 扡 挅 挆 撱 桗 椯 毤 毻 涶 灹 炧 炨 牠 痑 砣 砤 碢 秅 紽 綞 袉 趓 跥 跺 躲 軃 鍺 鰖 鵎 鼧 㛆 㛊 㟎 㢉 㥩 㨊 㯐 㰐 㸰 㸱 㻧 㼠 㾃 䂝 䅜 䆛 䍫 䑨 䑮 䒳 䙃 䙤 䜏 䠤 䡐
|
||||
탁 度 濁 拓 托 卓 濯 託 琢 擢 鐸 坼 啄 倬 柝 晫 琸 拆 橐 涿 逴 籜 踔 趠 沰 矺 蘀 乇 槖 椓 侂 跅 魠 仛 斀 杔 㤞 㧻 浊 箨 讬 铎 镯 饦 伲 凙 剢 剫 劅 啅 庹 桌 槕 汑 硺 窧 萚 蠗 袥 襗 踱 鈬 鐲 飥 馲 驝 鵫 㔬 㪬 㹿 㺟 䐾 䓬 䮓
|
||||
탄 歎 彈 炭 誕 灘 綻 憚 呑 坦 嘆 殫 癱 攤 憻 驒 惮 殚 弹 吞 瘓 撣 僤 嘽 掸 啴 弾 摊 滩 痪 瘫 绽 诞 怹 擹 暺 氽 汆 湠 疃 碳 羰 譠 騨 㖔 㛶 㧷 㨏 䋎 䘺
|
||||
탈 脫 奪 侻 梲 敓 夺 捝 脱 敚 毲 痥 莌 鮵 鵽 㣞 䫄
|
||||
탐 探 貪 耽 眈 酖 嗿 忐 醓 撢 躭 贪 赕 僋 沊 賧 㤾 㴷 㵅 䏙 䏥
|
||||
탑 塔 搭 榻 鰈 搨 塌 傝 闒 嗒 撘 漯 㲮 鑉 鞜 鞳 㗳 阘 鳎 嚃 墖 毾 溻 狧 瘩 禢 耷 遢 錔 鎑 鮙 鰨 㛥
|
||||
탑 塔 搭 榻 鰈 搨 塌 傝 闒 嗒 撘 漯 㲮 鑉 鞜 鞳 㗳 阘 鳎 嚃 墖 毾 溻 狧 瘩 禢 耷 遢 錔 鎑 鮙 鰨 㛥 㯓 㲩 㹺 䈳 䌈 䑜 䑽
|
||||
탕 糖 湯 蕩 宕 帑 盪 碭 蘯 燙 逿 簜 蝪 踼 璗 鐋 荡 汤 烫 砀 铴 偒 圵 婸 摥 菪 薚 趤 雼 㼒 䑗 䦒
|
||||
태 太 泰 態 殆 怠 台 胎 兌 跆 颱 汰 苔 笞 邰 呆 駄 迨 蛻 駘 埭 娧 孡 抬 兑 紿 蜕 䭾 鮐 忕 呔 兊 忲
|
||||
태 太 泰 態 殆 怠 台 胎 兌 跆 颱 汰 苔 笞 邰 呆 駄 迨 蛻 駘 埭 娧 孡 抬 兑 紿 蜕 䭾 鮐 忕 呔 兊 忲 㤗 溙 㥭 态 绐 钛 骀 鲐 䌼 傣 冭 囼 夳 戻 捸 斄 曃 炱 炲 燤 粏 綐 緿 肽 舦 菭 軩 酞 鈦 颰 駾 㑀 㑷 㙂 㟋 㣍 㣖 㳲 䈚 䬈 珆 脫
|
||||
택 宅 擇 澤 択 择 沢 泽 伬 烢 礋 蠌 鸅 㡯 㭦 䕉 䕪 垞
|
||||
탱 撑 撐 牚 竀 樘 橖 橕 㯑
|
||||
터 攄 摅
|
||||
@@ -493,81 +493,81 @@
|
||||
톤 噸 噋 瓲 啍 畽 䵯
|
||||
톨 㐋
|
||||
통 通 洞 統 痛 慟 筒 桶 恫 侗 筩 樋 㷁 憅 熥 綂 恸 统 嗵 捅 痌 蓪 㣚 㪌 㳆
|
||||
퇴 退 椎 頹 堆 槌 腿 鎚 褪 焞 搥 隤 魋 頽 僓 蓷 痽 頺 俀 尵 䫋 颓 垖 塠 嵟 弚 桘 煺 磓 穨 脮 藬 蘈
|
||||
퇴 退 椎 頹 堆 槌 腿 鎚 褪 焞 搥 隤 魋 頽 僓 蓷 痽 頺 俀 尵 䫋 颓 垖 塠 嵟 弚 桘 煺 磓 穨 脮 藬 蘈 螁 蹆 蹪 陮 頧 骽 鴭 㕍 㞂 㞜 㢈 㥆 㨃 㱣 㷟 㾼 㿉 㿗 䀃 䇏 䊚 䏨 䜃 䨺 䭔
|
||||
투 投 鬪 透 套 鍮 妬 偸 渝 妒 骰 偷 闘 鬭 哣 敨 牏 秺 緰 蘣 鬦 鬬 㕻 㖣 㚐 㢏 㪗 䛠 䞬 䟝 䤅
|
||||
퉁 佟
|
||||
특 特 慝 忒 螣 蟘 貣 铽 忑 脦 蚮 鋱 鴏 㥂 㧹
|
||||
틈 闖 闯
|
||||
파 破 波 罷 播 派 頗 把 坡 巴 婆 跛 琶 芭 爬 擺 陂 杷 霸 叵 怕 葩 皤 帕 簸 灞 玻 笆 鄱 妑 岥 爸 耙
|
||||
파 破 波 罷 播 派 頗 把 坡 巴 婆 跛 琶 芭 爬 擺 陂 杷 霸 叵 怕 葩 皤 帕 簸 灞 玻 笆 鄱 妑 岥 爸 耙 菠 壩 罢 疤 帊 靶 䆉 嶓 垻 吧 欛 鈀 摆 坝 钯 钷 颇 啵 嘙 夿 尀 岜 弝 櫇 汖 潖 皅 碆 笸 筢 箥 粑 紦 羓 舥 蒎 蔢 蚆 蚾 袙 譒 豝 趴 跁 鉕 錃 鎃 駊 鲃 㗞 㜑 㝿 㞎 㨇 㩯 㭛 㸭 㿬 䃻 䎬 䎱 䔤 䝛 䥯 䯲 䰾 䶕
|
||||
판 判 板 版 販 阪 辦 坂 瓣 鈑 汴 辧 昄 舨 蝂 办 贩 钣 岅 瓪 畈 眅 粄 鵥 㤆
|
||||
팔 八 叭 捌 扒 朳 汃 仈 哵 玐 釟 㭭
|
||||
팟 巼
|
||||
팡 乓
|
||||
패 貝 敗 牌 悖 覇 佩 沛 稗 唄 狽 浿 霈 霸 孛 旆 珮 犻 誖 邶 棑 捭 㶚 斾 矲 郥 呗 狈 贝 败 钡 伂 姵
|
||||
팽 庄 彭 硼 膨 澎 烹 祊 砰 蟚 蟛 伻 閍 碰 嘭 嵭 憉 掽 梈 甏 皏 硑 磞 稝 軯 輣 錋 闏 騯 㑟 㤣 㧸 㱶
|
||||
패 貝 敗 牌 悖 覇 佩 沛 稗 唄 狽 浿 霈 霸 孛 旆 珮 犻 誖 邶 棑 捭 㶚 斾 矲 郥 呗 狈 贝 败 钡 伂 姵 昁 梖 牬 猈 珼 笩 篺 簰 粺 苝 茷 蛽 贁 鄁 鋇 馷 㔥 㗗 㛝 㤄 㧩 㫲 㳈 㸬 㸽 䊃 䖰 䟺 䢙 䩗 䩻 䰽 䱝
|
||||
팽 庄 彭 硼 膨 澎 烹 祊 砰 蟚 蟛 伻 閍 碰 嘭 嵭 憉 掽 梈 甏 皏 硑 磞 稝 軯 輣 錋 闏 騯 㑟 㤣 㧸 㱶 㼞 䄘 䑫 䙀 䥋
|
||||
퍅 愎
|
||||
편 便 篇 片 遍 偏 編 扁 鞭 騙 翩 徧 褊 艑 蝙 匾 諞 惼 緶 萹 煸 蹁 楩 楄 鯾 鯿 缏 编 谝 骗 鳊 囨 媥
|
||||
편 便 篇 片 遍 偏 編 扁 鞭 騙 翩 徧 褊 艑 蝙 匾 諞 惼 緶 萹 煸 蹁 楩 楄 鯾 鯿 缏 编 谝 骗 鳊 囨 媥 揙 牑 犏 猵 碥 箯 覑 貵 鍽 頨 騗 魸 鶣 㓲 㴜 㸤 㼐 㾫 䏒 䡢 䭏
|
||||
폄 貶 窆 砭 贬
|
||||
평 平 評 坪 萍 枰 苹 抨 怦 泙 蓱 鮃 玶 呯 䦕 评 鲆 匉 姘 岼 拼 胓 蚲 閛 駍 㛁 㺸 䍬 䓑
|
||||
폐 閉 廢 肺 弊 蔽 幣 陛 斃 吠 嬖 獘 敝 癈 狴 廃 杮 梐 㢢 幤 废 毙 箅 闭 櫠 獙 砩 蕟 蜌 鄨 鐾 閇 鼣
|
||||
포 布 抱 暴 捕 浦 胞 包 飽 砲 抛 鋪 逋 怖 鮑 泡 葡 褒 袍 蒲 圃 脯 哺 曝 匍 咆 疱 苞 佈 匏 庖 炮 晡
|
||||
폐 閉 廢 肺 弊 蔽 幣 陛 斃 吠 嬖 獘 敝 癈 狴 廃 杮 梐 㢢 幤 废 毙 箅 闭 櫠 獙 砩 蕟 蜌 鄨 鐾 閇 鼣 㙄 㡀 㯇 㾱 䉬 䕠 䠘 䯗
|
||||
포 布 抱 暴 捕 浦 胞 包 飽 砲 抛 鋪 逋 怖 鮑 泡 葡 褒 袍 蒲 圃 脯 哺 曝 匍 咆 疱 苞 佈 匏 庖 炮 晡 餔 舖 炰 誧 儤 鞄 鯆 曓 鉋 拋 酺 宲 礮 枹 襃 虣 圑 蒱 麅 笣 怉 脬 舗 跑 麃 㳍 刨 钸 铇 铺 饱 鲍 龅 佨 勹 咘 垉 埔 奅 孢 峬 庯 忁 悑 抪 柨 爮 狍 瓝 皰 礟 菢 萢 蚫 蜅 袌 裦 褜 謈 軳 鈽 鑤 闁 陠 靤 髱 鵏 麭 齙 㘵 㚴 㚿 㫧 㬥 㬧 㯡 㲒 䈬 䈻 䊇 䍖 䎂 䔕 䛌 䝵 䤖 䩝 䫽 䭋 䮒 䶌
|
||||
폭 暴 爆 幅 瀑 曝 輻 曓 䋹 䌿
|
||||
폿 喸
|
||||
표 表 標 票 漂 杓 豹 飄 剽 慓 瓢 俵 彪 驃 飇 縹 摽 殍 鑣 裱 僄 嫖 勡 嘌 熛 鏢 髟 鰾 飆 颷 醥 淲 藨
|
||||
표 表 標 票 漂 杓 豹 飄 剽 慓 瓢 俵 彪 驃 飇 縹 摽 殍 鑣 裱 僄 嫖 勡 嘌 熛 鏢 髟 鰾 飆 颷 醥 淲 藨 飈 飊 彯 爂 猋 穮 膘 蔈 薸 褾 謤 驫 魒 㼼 䅺 标 缥 镖 镳 飘 飙 飚 骉 骠 鳔 儦 墂 婊 幖 徱 旚 檦 滮 瀌 燢 犥 瘭 皫 瞟 磦 篻 翲 脿 臕 螵 諘 贆 錶 闝 顠 颩 飃 㘐 㟽 㠒 㧼 㬓 㯱 㯹 㲏 㵱 㶾 㹾 䁃 䁭 䏇 䔸 䕯 䙳 䞄 䮽 䱪 䴩 䶂
|
||||
푸 䬌
|
||||
품 品 稟 禀 榀
|
||||
풍 風 豊 楓 諷 馮 丰 葑 豐 瘋 酆 鄷 渢 偑 凮 灃 蘴 冯 枫 沣 疯 砜 讽 风 僼 凨 凬 堸 寷 栤 檒 沨 煈
|
||||
피 皮 彼 被 避 疲 披 跛 陂 辟 詖 鞁 屁 髲 柀 帔 骳 狓 鈹 佊 秛 㱟 诐 铍 鲏 䙓 怶 旇 氕 犤 紴 翍 耚
|
||||
풍 風 豊 楓 諷 馮 丰 葑 豐 瘋 酆 鄷 渢 偑 凮 灃 蘴 冯 枫 沣 疯 砜 讽 风 僼 凨 凬 堸 寷 栤 檒 沨 煈 猦 碸 霻 靊 颪 飌 麷 㐽 㒥 㠦 䏎 䵄
|
||||
피 皮 彼 被 避 疲 披 跛 陂 辟 詖 鞁 屁 髲 柀 帔 骳 狓 鈹 佊 秛 㱟 诐 铍 鲏 䙓 怶 旇 氕 犤 紴 翍 耚 藣 襬 貏 貱 銔 鮍 㓟 㗪 㢰 㨢 㯅 䏢 䏶
|
||||
픽 腷 煏
|
||||
필 必 筆 匹 畢 弼 泌 疋 苾 珌 馝 蹕 蓽 韠 篳 觱 佖 滭 駜 鵯 咇 罼 鞸 驆 弻 榓 㻶 熚 胇 袐 飶 䟆 哔
|
||||
필 必 筆 匹 畢 弼 泌 疋 苾 珌 馝 蹕 蓽 韠 篳 觱 佖 滭 駜 鵯 咇 罼 鞸 驆 弻 榓 㻶 熚 胇 袐 飶 䟆 哔 毕 滗 笔 筚 荜 虑 跸 铋 鹎 吡 嗶 妼 彃 怭 潷 縪 苉 襅 邲 鉍 鏎 饆 魓 鮅 鴄 鷝 㓖 㢶 㢸 㪤 㮿 㳼 㻫 䄶 䏘 䖩 䩛 䫾 䬛 䮡
|
||||
핍 乏 逼 偪 愊 姂 鴔
|
||||
하 下 夏 河 何 賀 荷 瑕 遐 霞 蝦 厦 廈 鰕 昰 嚇 罅 呀 鍜 煆 岈 懗 瘕 嘏 縀 睱 谺 騢 鏬 哧 抲 菏 㙈
|
||||
학 學 鶴 虐 壑 謔 瘧 确 涸 郝 狢 熇 鷽 皬 学 翯 觷 嗃 澩 矐 㕰 䳽 疟 谑 鸴 鹤 嗀 嚛 嚯 壆 峃 嶨 斈
|
||||
한 韓 漢 寒 限 閑 恨 旱 汗 翰 邯 罕 悍 澣 閒 瀚 桿 捍 閈 暵 鼾 犴 僩 嫺 熯 嫻 駻 鷳 扞 狠 僴 覵 瞷
|
||||
할 割 轄 蝎 黠 瞎 鶡 蠍 傄 劼 縖 舝 䫘 辖 乤 嗐 搳 硈 磍 螛 鎋 韔 鶷 㔛 㔠 㝬 㪴 㮫 㿣 䕣 䕸 䦖 䦪
|
||||
함 咸 陷 含 函 艦 喊 涵 銜 緘 檻 鹹 啣 諴 菡 頷 嗛 闞 憨 莟 轞 䧟 淊 凾 醎 顑 圅 㴠 䓿 衘 谽 馠 䘖
|
||||
합 合 陜 蛤 盒 閤 哈 闔 盍 溘 榼 郃 柙 匌 嗑 呷 詥 鴿 篕 㝓 峆 欱 烚 盇 鮯 䢔 陕 阖 颌 鸽 佮 峇 廅
|
||||
항 行 恒 降 項 抗 巷 港 航 沆 亢 缸 肛 杭 伉 姮 桁 嫦 恆 頏 炕 夯 缿 骯 吭 閌 衖 佭 斻 闀 肮 钪 闶
|
||||
해 海 害 解 亥 該 奚 偕 駭 骸 諧 楷 懈 咳 邂 孩 垓 瀣 蟹 醢 廨 薤 獬 瑎 欬 咍 嶰 痎 鮭 頦 觧 澥 晐
|
||||
하 下 夏 河 何 賀 荷 瑕 遐 霞 蝦 厦 廈 鰕 昰 嚇 罅 呀 鍜 煆 岈 懗 瘕 嘏 縀 睱 谺 騢 鏬 哧 抲 菏 㙈 虾 贺 丅 丷 哬 嗬 圷 夓 疜 碋 碬 芐 蕸 袔 諕 赮 鎼 閕 閜 鞐 颬 㗇 㗿 㙤 㰤 㰺 㵑 䠍 䪗 䫗
|
||||
학 學 鶴 虐 壑 謔 瘧 确 涸 郝 狢 熇 鷽 皬 学 翯 觷 嗃 澩 矐 㕰 䳽 疟 谑 鸴 鹤 嗀 嚛 嚯 壆 峃 嶨 斈 癋 硸 蠚 謞 貈 雤 靏 鴬 鶮 鸖 㕡 㰒 㶅 㿥 䅂 䖈 䖋 䤕 䨋 䮤
|
||||
한 韓 漢 寒 限 閑 恨 旱 汗 翰 邯 罕 悍 澣 閒 瀚 桿 捍 閈 暵 鼾 犴 僩 嫺 熯 嫻 駻 鷳 扞 狠 僴 覵 瞷 猂 邗 骭 撊 鷴 垾 憪 橌 㸁 䛞 䦘 闲 鹇 娴 汉 焊 闬 韩 仠 佷 傼 兯 哻 娨 嫨 屽 晘 橺 浫 澖 皔 蓒 蔊 蛝 螒 豻 貋 釬 銲 雗 鶾 鷼 麲 㒏 㛠 㡾 㢨 㪋 㯗 㲦 㵄 䁂 䂅 䈨 䉯 䍐 䍑 䎯 䏷 䓍 䓳 䕿 䗙 䥜 䦥 䮧 忓
|
||||
할 割 轄 蝎 黠 瞎 鶡 蠍 傄 劼 縖 舝 䫘 辖 乤 嗐 搳 硈 磍 螛 鎋 韔 鶷 㔛 㔠 㝬 㪴 㮫 㿣 䕣 䕸 䦖 䦪 䶤
|
||||
함 咸 陷 含 函 艦 喊 涵 銜 緘 檻 鹹 啣 諴 菡 頷 嗛 闞 憨 莟 轞 䧟 淊 凾 醎 顑 圅 㴠 䓿 衘 谽 馠 䘖 唅 浛 筨 琀 甉 魽 㨔 㺝 䤴 䶢 衔 槛 缄 舰 阚 馅 壏 娢 晗 梒 欦 澏 焓 糮 臽 蛿 蜬 蜭 譀 豃 豏 輱 鋡 錎 陥 顄 餡 鬫 鰔 㓧 㖤 㘅 㘎 㘚 㛾 㟏 㟔 㤷 㮭 㯺 㰹 㶰 㺖 㼨 㽉 䈄 䎏 䐄 䕔 䖔 䘶 䟰 䣻 䥁 䨡 䩂 䫲 䱤 䲗 䶃 䶟 䶠
|
||||
합 合 陜 蛤 盒 閤 哈 闔 盍 溘 榼 郃 柙 匌 嗑 呷 詥 鴿 篕 㝓 峆 欱 烚 盇 鮯 䢔 陕 阖 颌 鸽 佮 峇 廅 敮 熆 珨 秴 粭 翈 耠 頜 魻 㘡 㥺 㭘 㰰 䆟 䖎 䖖 䛅 䧻 䶎
|
||||
항 行 恒 降 項 抗 巷 港 航 沆 亢 缸 肛 杭 伉 姮 桁 嫦 恆 頏 炕 夯 缿 骯 吭 閌 衖 佭 斻 闀 肮 钪 闶 项 颃 塂 岲 栙 笐 罁 苀 蚢 貥 跭 迒 邟 酐 鈧 闂 魧 㔰 㟟 㤚 㰠 䀪 䁰 䘕 䜶 䟘 䢽 䣈 䲳
|
||||
해 海 害 解 亥 該 奚 偕 駭 骸 諧 楷 懈 咳 邂 孩 垓 瀣 蟹 醢 廨 薤 獬 瑎 欬 咍 嶰 痎 鮭 頦 觧 澥 晐 陔 荄 蠏 侅 駴 㤥 畡 賅 峐 檞 膎 韰 㧡 䪥 该 谐 赅 颏 骇 鲑 嗨 塰 妎 姟 慀 氦 絯 繲 胲 薢 觟 豥 賌 輆 郋 酼 餀 饚 龤 㒠 㗨 㙰 㚊 㜾 㞒 㰡 㰧 㰩 㱼 㾂 㾬 䀭 䇋 䉏 䚸 䠹 䠽 䡡 䦏 䯐 䱺 䲒 祄
|
||||
핵 核 劾 覈 翮 栶 势 䃒
|
||||
행 行 幸 杏 倖 荇 悻 婞 涬 筕 㼬 绗 鸻 哘 啈 垳 堼 洐 烆 絎 緈 胻 莕 裄 鴴 鵆 﨨 㓑 䁄 䂔 䓷 䛭 䯒
|
||||
향 向 鄕 香 享 響 饗 嚮 餉 珦 薌 鄊 晑 曏 郷 蠁 膷 鄉 銄 麘 䦳 乡 响 芗 飨 饷 亯 姠 楿 稥 萫 蚃 鱜
|
||||
행 行 幸 杏 倖 荇 悻 婞 涬 筕 㼬 绗 鸻 哘 啈 垳 堼 洐 烆 絎 緈 胻 莕 裄 鴴 鵆 﨨 㓑 䁄 䂔 䓷 䛭 䯒 䰢
|
||||
향 向 鄕 香 享 響 饗 嚮 餉 珦 薌 鄊 晑 曏 郷 蠁 膷 鄉 銄 麘 䦳 乡 响 芗 飨 饷 亯 姠 楿 稥 萫 蚃 鱜 黁 㕿 㗽 㴡 㿝 䅨 䊑 䖮 䦭
|
||||
허 許 虛 墟 噓 詡 歔 虚 栩 虗 㠊 魖 嘘 浒 许 憈 鄦 驉 魼 㞰 䔓
|
||||
헌 憲 獻 軒 櫶 掀 献 巚 幰 攇 巘 瀗 宪 轩 仚 佡 搟 㦥 䆭 䘆 䜢 䧮 䡣 昍
|
||||
헐 歇 蝎 蠍 滊
|
||||
험 險 驗 憸 嶮 玁 獫 硷 崄 猃 险 険 験 验 姭 忺 杴 榃 礆 譣 騐 㷿 㸝 㿌
|
||||
혁 革 赫 爀 奕 嚇 洫 鬩 弈 焱 衋 虩 侐 焃 殈 赩 㷜 吓 阋 伵 捇 煂 盢 瞁 覤 赥 趘 㤸 㦦 㬨 㮝 㷦 䓇
|
||||
현 見 現 賢 顯 玄 縣 懸 絃 弦 鉉 峴 炫 洵 眩 衒 絢 晛 俔 玹 泫 睍 舷 儇 嬛 昡 誢 琄 眴 繯 痃 筧 県
|
||||
혈 血 穴 孑 頁 絜 趐 泬 蝢 緳 䆷 䦧 页 吷 坹 奊 娎 岤 桖 烕 疦 茓 裇 㐖 䆝 䋉 䐼 䒸 䕵 䙽 䛎 䦑 䩤
|
||||
혁 革 赫 爀 奕 嚇 洫 鬩 弈 焱 衋 虩 侐 焃 殈 赩 㷜 吓 阋 伵 捇 煂 盢 瞁 覤 赥 趘 㤸 㦦 㬨 㮝 㷦 䓇 䚂 䚫 䦗 䱛 䵱
|
||||
현 見 現 賢 顯 玄 縣 懸 絃 弦 鉉 峴 炫 洵 眩 衒 絢 晛 俔 玹 泫 睍 舷 儇 嬛 昡 誢 琄 眴 繯 痃 筧 県 祆 翾 蜆 鋗 駽 顕 袨 灦 敻 礥 贒 垷 莧 㬎 哯 埍 胘 蚿 蠉 鋧 鑦 䧎 伣 县 岘 悬 显 现 绚 缳 苋 蚬 贤 铉 伭 呟 咞 妶 娊 娹 婱 弲 怰 惤 梋 矎 臔 臤 虤 訮 詪 詽 誸 譞 讂 贙 鞙 韅 㘋 㡉 㢺 㧋 㧦 㫫 㭹 㹡 㻹 䀏 䍗 䏹 䗾 䚯 䛹 䝨 䝮 䥪 䧋 䩙 䮄 䲻
|
||||
혈 血 穴 孑 頁 絜 趐 泬 蝢 緳 䆷 䦧 页 吷 坹 奊 娎 岤 桖 烕 疦 茓 裇 㐖 䆝 䋉 䐼 䒸 䕵 䙽 䛎 䦑 䩤 䩧 䫼
|
||||
혐 嫌 鼸 馦 㾾 㺌 㽐 䵌
|
||||
협 協 脅 狹 峽 陜 挾 俠 頰 夾 浹 脇 鋏 莢 叶 恊 愜 篋 悏 匧 埉 祫 峡 惬 䝱 筴 蛺 劦 冾 勰 脥 硤 陿
|
||||
형 兄 形 刑 亨 螢 衡 炯 馨 邢 瀅 型 荊 熒 瑩 泂 珩 逈 烱 桁 滎 鎣 灐 迥 詗 夐 陘 娙 荆 鉶 硎 浻 蘅
|
||||
혜 惠 慧 兮 彗 醯 鞋 蹊 蕙 暳 盻 徯 槥 傒 嘒 寭 謑 譿 恵 嵇 鼷 鞵 潓 嵆 橞 蟪 譓 豀 嚖 憓 㜎 䚷 䧥
|
||||
호 好 戶 號 湖 乎 呼 虎 胡 護 毫 浩 豪 互 扈 鎬 皓 糊 昊 濠 祜 晧 澔 壕 狐 瑚 弧 琥 壺 槁 蒿 濩 芦
|
||||
협 協 脅 狹 峽 陜 挾 俠 頰 夾 浹 脇 鋏 莢 叶 恊 愜 篋 悏 匧 埉 祫 峡 惬 䝱 筴 蛺 劦 冾 勰 脥 硤 陿 拹 狭 侠 协 夹 挟 浃 硖 箧 胁 荚 蛱 铗 铪 颊 饸 䇲 嗋 垥 愶 旪 梜 熁 燲 綊 脋 鉿 鞈 頬 餄 㙝 㛍 㢵 㤲 㥦 㥷 㮉 㶸 㼪 㽠 㾜 㿓 䁋 䏩 䏮 䕛 䬅 䶝
|
||||
형 兄 形 刑 亨 螢 衡 炯 馨 邢 瀅 型 荊 熒 瑩 泂 珩 逈 烱 桁 滎 鎣 灐 迥 詗 夐 陘 娙 荆 鉶 硎 浻 蘅 郉 脝 㢠 侀 涥 㺾 㼆 泶 滢 潆 荥 荧 萤 蓥 诇 铏 陉 哼 坓 嵤 悙 擤 瀠 藀 蛍 蛵 褮 賯 鋞 鶑 㐩 㓝 㚾 㣜 㭢 㯏 㼛 䅽 䚘 䢛 䣆 䤯 䤰 䳙
|
||||
혜 惠 慧 兮 彗 醯 鞋 蹊 蕙 暳 盻 徯 槥 傒 嘒 寭 謑 譿 恵 嵇 鼷 鞵 潓 嵆 橞 蟪 譓 豀 嚖 憓 㜎 䚷 䧥 僡 匸 嚡 忚 懳 榽 橀 櫘 璤 蒵 螇 豯 鏸 韢 騱 㚛 㥣 㨙 㩨 㬩 㰥 㰿 㿽 䎚 䒊 䙎 䛊 䜁 䤈 䫣 䲪
|
||||
호 好 戶 號 湖 乎 呼 虎 胡 護 毫 浩 豪 互 扈 鎬 皓 糊 昊 濠 祜 晧 澔 壕 狐 瑚 弧 琥 壺 槁 蒿 濩 芦 灝 瓠 縞 顥 岵 蝴 滸 頀 淏 葫 犒 怙 沍 箎 餬 皞 醐 嘷 滈 冱 嫮 滬 皜 儫 嫭 猢 聕 鬍 号 户 㦿 昈 皥 乕 䎁 蠔 鄗 唬 皡 壷 暭 虖 鄠 猲 嘑 楜 苸 䕶 䚽 帍 暤 曍 枑 滹 籇 萀 謼 譹 錿 䊀 壶 护 沪 灏 缟 轷 镐 颢 鹕 乊 傐 冴 哠 喖 嗥 噑 嚎 垀 夰 媩 峼 弖 恏 悎 戸 戽 摢 昦 枦 椃 槴 歑 泘 瀥 烀 煳
|
||||
혹 或 惑 酷 鵠 豰 掝 鍙 閄 頶 嗀 㦯 㷤 㺉 㽇 䞱 熇
|
||||
혼 混 婚 昏 魂 渾 煇 琿 溷 閽 睨 惛 焜 湣 圂 惽 睯 掍 鯶 鼲 䰟 挥 浑 珲 阍 馄 倱 堚 婫 忶 昬 棔 殙
|
||||
혼 混 婚 昏 魂 渾 煇 琿 溷 閽 睨 惛 焜 湣 圂 惽 睯 掍 鯶 鼲 䰟 挥 浑 珲 阍 馄 倱 堚 婫 忶 昬 棔 殙 涽 焝 睧 緷 繉 觨 餛 㑮 㖧 㛰 㮯 㱪 䅙 䊐 䎜 䐊 䚠 䛰 䡣 䧰 䮝 䴷 俒 䫟
|
||||
홀 忽 笏 惚 囫 絗 曶 芴 㧾 䬍 锪 乥 匢 匫 唿 啒 寣 鍃 㖴 㧮 㨡 㳷 㺀 䁫 䓤 䝆 䨚 䩐 䴯
|
||||
홍 紅 洪 弘 鴻 泓 訌 虹 哄 烘 汞 鬨 澒 晎 篊 鉷 洚 閧 葒 妅 唝 红 荭 讧 鸿 仜 叿 嗊 垬 娂 屸 渱 潂
|
||||
화 火 花 化 話 和 畵 華 貨 禍 禾 靴 樺 嬅 譁 樗 畫 夥 擭 嘩 驊 俰 婲 龢 画 澕 槬 墷 鞾 咊 鏵 伙 楇
|
||||
확 確 擴 穫 廓 濩 攫 碻 鑊 确 涸 擭 矍 礭 矡 䨥 镬 雘 矱 蠖 彍 戄 彉 攉 玃 臛 艧 靃 彠 扩 拡 鹱 劐
|
||||
환 患 歡 還 換 丸 環 喚 煥 桓 幻 宦 鰥 驩 渙 紈 奐 晥 懽 圜 寰 豢 鬟 擐 轘 瓛 睆 鍰 絙 鐶 繯 垸 皖
|
||||
홍 紅 洪 弘 鴻 泓 訌 虹 哄 烘 汞 鬨 澒 晎 篊 鉷 洚 閧 葒 妅 唝 红 荭 讧 鸿 仜 叿 嗊 垬 娂 屸 渱 潂 灴 焢 硔 粠 苰 葓 蕻 谼 谾 銾 霐 霟 魟 㖓 㙆 㬴 㶹 䀧 䂫 䃔 䆪 䉺 䜫 䞑 䧆 䨎 䪦 䫹 䲨
|
||||
화 火 花 化 話 和 畵 華 貨 禍 禾 靴 樺 嬅 譁 樗 畫 夥 擭 嘩 驊 俰 婲 龢 画 澕 槬 墷 鞾 咊 鏵 伙 楇 璍 訸 㠏 划 华 哗 桦 祸 话 货 钬 铧 骅 鳠 吙 啝 埖 夻 姀 崈 崋 惒 摦 旤 杹 枠 柇 椛 沎 灬 盉 硴 竵 糀 舙 芲 萂 蕐 誮 譮 邩 釫 鈥 鉌 錵 魤 鱯 鷨 㕦 㗾 㟆 㦊 㭉 㳸 㶡 䄀 䅿 䒩 䔢 䛡
|
||||
확 確 擴 穫 廓 濩 攫 碻 鑊 确 涸 擭 矍 礭 矡 䨥 镬 雘 矱 蠖 彍 戄 彉 攉 玃 臛 艧 靃 彠 扩 拡 鹱 劐 嬳 彏 彟 挄 曤 檴 爴 瓁 矆 篗 籆 籰 耯 臒 蒦 貜 霩 鸌 㠛 㦜 㨯 㬑 㬦 㸕 㺢 䁨 䂄 䈅 䉟
|
||||
환 患 歡 還 換 丸 環 喚 煥 桓 幻 宦 鰥 驩 渙 紈 奐 晥 懽 圜 寰 豢 鬟 擐 轘 瓛 睆 鍰 絙 鐶 繯 垸 皖 逭 癏 寏 瑍 䆠 讙 鱞 漶 㬊 汍 闤 澴 弮 芄 㬇 綄 䚪 䝔 唤 奂 换 欢 歓 涣 焕 纨 还 锾 镮 阛 鲩 鳏 喛 峘 愌 懁 攌 暅 梙 槵 烉 犱 獾 睅 瞣 笂 糫 羦 肒 荁 萈 萟 藧 貛 酄 釻 鋎 雈 鯇 鰀 鴅 鵍 鹮 㕕 㛟 㞍 㡲 㪱 㶎 㹕 㹖 㿪 䀓 䁵 䍺 䒛 䒯 䗭 䜨 䝠 䠉 䦡 䭴 䯘 䴉 䴋 䴟 洹
|
||||
활 活 闊 滑 猾 豁 佸 蛞 濶 䀨 敌 阔 奯 姡 眓 磆 秮 秳 萿 螖 趏 㓉 㕲 㵈 䄆 䄑 䦢 䱻 䴳
|
||||
황 黃 皇 況 荒 滉 慌 徨 晃 惶 遑 煌 凰 潢 璜 蝗 榥 隍 篁 愰 簧 恍 幌 湟 晄 怳 貺 肓 喤 鎤 熀 瑝 媓
|
||||
황 黃 皇 況 荒 滉 慌 徨 晃 惶 遑 煌 凰 潢 璜 蝗 榥 隍 篁 愰 簧 恍 幌 湟 晄 怳 貺 肓 喤 鎤 熀 瑝 媓 况 黄 䌙 皝 熿 偟 艎 謊 㡛 癀 穔 縨 墴 揘 衁 詤 䁜 谎 贶 鳇 兤 堭 塃 奛 宺 崲 巟 曂 朚 楻 櫎 炾 獚 皩 眖 葟 蟥 諻 趪 軦 韹 餭 騜 鰉 鱑 鷬 㠩 㠵 㡃 㡆 㣴 㤺 㨪 㬻 㵁 㶂 㾮 䀮 䄓 䅣 䊗 䊣 䍿 䐠 䐵 䑟 䞹 䪄 䮲 䳨
|
||||
홰 罫 噦 翽 哕 噧 澅 翙
|
||||
회 回 會 悔 懷 淮 恢 灰 廻 檜 膾 濊 晦 徊 誨 賄 繪 蛔 澮 匯 獪 茴 迴 燹 洄 詼 鱠 佪 繢 滙 闠 頮 沬
|
||||
회 回 會 悔 懷 淮 恢 灰 廻 檜 膾 濊 晦 徊 誨 賄 繪 蛔 澮 匯 獪 茴 迴 燹 洄 詼 鱠 佪 繢 滙 闠 頮 沬 盔 栃 湏 会 懐 囬 禬 㑹 薈 豗 囘 逥 瀤 恛 璯 蛕 螝 褢 鄶 刽 桧 浍 烩 狯 絵 绘 脍 荟 诙 诲 贿 郐 颒 鲙 㧟 劊 咴 嬒 屷 廽 拻 擓 敼 杤 櫰 洃 烣 烸 燴 痐 瞺 硘 耲 藱 蘹 蚘 蜖 褱 詯 諙 鐬 靧 鮰 㒑 㜳 㞀 㞧 㠢 㣛 㨤 㱱 㷄 㷇 㻅 㾯 䕇 䖶 䛛 䜋 䝅 䝇 䢈 䤧 䩈 䫭 䭝 䴜 䵳
|
||||
획 畵 獲 劃 硅 畫 擭 嚄 謢 撶 繣 㗲 获 婳 咟 嚿 嫿 湱 砉 窢 謋 讗 韄 騞 㓰 㖪 㦎 㩇 䐸 䦝 䪝 䬉 画
|
||||
횡 橫 薨 宖 鐄 黌 鈜 澋 横 鋐 紭 竑 黉 吰 嚝 峵 彋 揈 撔 谹 輷 鑅 㢬 㶇 䍔 䎕 䫺 䬖 䬝
|
||||
효 孝 效 曉 酵 爻 嚆 哮 梟 肴 淆 驍 斅 涍 囂 嘐 傚 殽 崤 熇 皛 虓 餚 効 暁 嘵 枵 鴞 歊 斆 憢 毊 嘋
|
||||
후 後 厚 侯 后 候 喉 吼 朽 逅 嗅 煦 帿 珝 詬 涸 垕 詡 酗 猴 堠 喣 餱 篌 佝 譃 吽 糇 齅 姁 睺 矦 郈
|
||||
훈 訓 薰 熏 勳 壎 暈 塤 勛 焄 燻 曛 纁 葷 獯 鑂 爋 勲 醺 坃 薫 矄 埙 勋 晕 荤 训 嚑 壦 瞓 臐 蔒 蘍
|
||||
효 孝 效 曉 酵 爻 嚆 哮 梟 肴 淆 驍 斅 涍 囂 嘐 傚 殽 崤 熇 皛 虓 餚 効 暁 嘵 枵 鴞 歊 斆 憢 毊 嘋 獢 筊 髇 哓 嚣 晓 枭 胶 骁 鸮 侾 俲 倄 呺 嚻 婋 宯 庨 撬 敩 洨 滧 灱 灲 猇 痚 皢 硣 穘 窙 笅 膮 藠 虈 訤 詨 誟 誵 郩 顤 髐 﨧 㕺 㚠 㤊 㩭 㫴 㭳 㮁 㵿 㹲 㺧 䂚 䉰 䋂 䒝 䓔 䕧 䖀 䥵 䫞 䬘 謼 烋
|
||||
후 後 厚 侯 后 候 喉 吼 朽 逅 嗅 煦 帿 珝 詬 涸 垕 詡 酗 猴 堠 喣 餱 篌 佝 譃 吽 糇 齅 姁 睺 矦 郈 鍭 珛 齁 鱟 㗋 䣱 诩 鲎 鲘 冔 呍 洉 犼 猤 疞 瘊 翭 翵 葔 螑 豞 鄇 銗 骺 鮜 鯸 㖃 㗜 㢿 㤧 㫗 㬋 㮢 㰭 㱙 㴟 㷞 㸸 㺅 㻈 㽳 䂉 䏏 䖉 䗔 䙈 䞀 䞧 䣴 䧁 䫛 䳧 芋
|
||||
훈 訓 薰 熏 勳 壎 暈 塤 勛 焄 燻 曛 纁 葷 獯 鑂 爋 勲 醺 坃 薫 矄 埙 勋 晕 荤 训 嚑 壦 瞓 臐 蔒 蘍 䌲 䗼 䙧 䠝 䵫 煇
|
||||
훌 欻 歘 烼 魆 㗵
|
||||
훙 薨 顭
|
||||
훤 喧 萱 暄 煊 諼 諠 烜 翧 晅 楦 藼 箮 䁔 咺 愋 蝖 貆 谖 吅 嚾 媗 昍 狟 睻 禤 萲 蕿 蘐 㓩 䚙 䚭 愃
|
||||
훨 䎀
|
||||
훼 毁 喙 卉 虫 虺 燬 芔 毀 毇 檓 譭 顪 餯 㑰 㩓 㷐 䂕 䃣 䛼
|
||||
휘 揮 輝 徽 諱 麾 彙 暉 煇 褘 撝 翬 彚 椲 禈 䘗 幑 楎 汇 晖 讳 辉 㧑 媈 泋 瀈 灳 翚 鰴 㩣 㫎 㭏 㹆
|
||||
휘 揮 輝 徽 諱 麾 彙 暉 煇 褘 撝 翬 彚 椲 禈 䘗 幑 楎 汇 晖 讳 辉 㧑 媈 泋 瀈 灳 翚 鰴 㩣 㫎 㭏 㹆 㺔 煒
|
||||
휭 遤
|
||||
휴 休 携 墮 烋 虧 畦 隳 亏 睢 鵂 咻 擕 髹 庥 鑴 貅 攜 巂 瓗 眭 茠 觿 雟 飍 鸺 倠 儶 婎 孈 烌 睳 蘳
|
||||
휴 休 携 墮 烋 虧 畦 隳 亏 睢 鵂 咻 擕 髹 庥 鑴 貅 攜 巂 瓗 眭 茠 觿 雟 飍 鸺 倠 儶 婎 孈 烌 睳 蘳 蠵 觹 觽 貕 酅 銝 隓 驨 髤 鮴 㔒 㥟 㩗 㩦 㱗 㳜 㵻 㹯 㽯 䏫 䝗 䮌 䰍
|
||||
휵 畜 慉 搐 槒 䛙
|
||||
휼 恤 譎 鷸 卹 潏 霱 遹 鐍 䘏 獝 䎉 谲 鹬 憰 烅 瞲 肷 賉 㤜 㳚 䢕 䬄
|
||||
흉 凶 胸 匈 兇 洶 胷 恟 汹 㐫 忷 訩 讻 哅 詾 㓙 㕳 㕼 䠗
|
||||
@@ -577,6 +577,6 @@
|
||||
흠 欽 欠 歆 廞 揿 钦 噷 嬜 撳 鑫 㐸 䜗
|
||||
흡 吸 洽 恰 扱 翕 潝 歙 噏 翖 熻 闟 㩉 㒆 㡊 㪧 㬛 㲸 㽏 䁯 䞩 䨐 䶋
|
||||
흥 興 兴 嬹 臖 馫 㒷 䕟
|
||||
희 希 喜 稀 戱 嬉 熙 噫 羲 禧 犧 姬 熹 憙 愾 僖 曦 凞 晞 憘 熺 囍 戲 嘻 餼 燹 欷 唏 暿 咥 悕 豨 爔
|
||||
희 希 喜 稀 戱 嬉 熙 噫 羲 禧 犧 姬 熹 憙 愾 僖 曦 凞 晞 憘 熺 囍 戲 嘻 餼 燹 欷 唏 暿 咥 悕 豨 爔 黖 戯 煕 餙 犠 俙 巇 屭 狶 㶊 饎 浠 嚱 橲 熈 睎 蟢 誒 㬢 呬 桸 琋 繥 霼 㱆 屃 戏 牺 诶 饩 屓 忥 怬 歖 烯 焁 焈 熂 燨 犔 瞦 礂 莃 譆 豷 隵 鯑 鱚 鵗 齂 㑶 㙿 㚀 㚦 㜯 㝆 㠻 㩬 㰕 㰨 㷗 㷩 㸍 㹷 㺣 䂀 䊠 䐅 䖒 䖷 䛥 䨳 䮎 姫
|
||||
히 屎 忾 㕧 䐖 䦙
|
||||
힐 詰 黠 頡 纈 犵 襭 肹 擷 肸 撷 缬 诘 颉 恄 欯 翓 㩪
|
||||
|
||||
66
map/hira.map
66
map/hira.map
@@ -42,10 +42,12 @@ so そ
|
||||
zo ぞ
|
||||
ta た
|
||||
da だ
|
||||
di ぢ
|
||||
ti ち
|
||||
chi ち
|
||||
cha ちゃ
|
||||
chu ちゅ
|
||||
che ちぇ
|
||||
tyu ちゅ
|
||||
cho ちょ
|
||||
ji じ
|
||||
@@ -83,6 +85,10 @@ pyu ぴゅ
|
||||
pyo ぴょ
|
||||
hu ふ
|
||||
fu ふ
|
||||
fa ふぁ
|
||||
fi ふぃ
|
||||
fe ふぇ
|
||||
fo ふぉ
|
||||
bu ぶ
|
||||
vu ぶ
|
||||
pu ぷ
|
||||
@@ -124,47 +130,23 @@ xi ぃ
|
||||
xu ぅ
|
||||
xe ぇ
|
||||
xo ぉ
|
||||
kka っか
|
||||
kki っき
|
||||
kkya っきゃ
|
||||
kkyu っきゅ
|
||||
kkyo っきょ
|
||||
kku っく
|
||||
kke っけ
|
||||
kko っこ
|
||||
ssa っさ
|
||||
ssi っし
|
||||
sshi っし
|
||||
ssha っしゃ
|
||||
sshu っしゅ
|
||||
sshe っしぇ
|
||||
ssho っしょ
|
||||
ssyo っしょ
|
||||
ssu っす
|
||||
sse っせ
|
||||
sso っそ
|
||||
tta った
|
||||
dda っだ
|
||||
tti っち
|
||||
cchi っち
|
||||
ccha っちゃ
|
||||
cchu っちゅ
|
||||
ttyu っちゅ
|
||||
ccho っちょ
|
||||
ttu っつ
|
||||
ttsu っつ
|
||||
ddu っづ
|
||||
tte って
|
||||
dde っで
|
||||
tto っと
|
||||
ddo っど
|
||||
ppa っぱ
|
||||
ppi っぴ
|
||||
ppya っぴゃ
|
||||
ppyu っぴゅ
|
||||
ppyo っぴょ
|
||||
ppu っぷ
|
||||
ppe っぺ
|
||||
ppo っぽ
|
||||
kk っ
|
||||
ss っ
|
||||
tt っ
|
||||
pp っ
|
||||
cc っ
|
||||
gg っ
|
||||
zz っ
|
||||
jj っ
|
||||
dd っ
|
||||
bb っ
|
||||
hh っ
|
||||
ff っ
|
||||
mm っ
|
||||
yy っ
|
||||
rr っ
|
||||
ww っ
|
||||
vv っ
|
||||
tch っ
|
||||
. 。
|
||||
, 、
|
||||
|
||||
11
map/kata.map
11
map/kata.map
@@ -128,6 +128,17 @@ pp ッ
|
||||
tt ッ
|
||||
tch ッ
|
||||
ss ッ
|
||||
gg ッ
|
||||
zz ッ
|
||||
jj ッ
|
||||
bb ッ
|
||||
hh ッ
|
||||
ff ッ
|
||||
mm ッ
|
||||
yy ッ
|
||||
rr ッ
|
||||
ww ッ
|
||||
vv ッ
|
||||
di ディ
|
||||
fa ファ
|
||||
fi フィ
|
||||
|
||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
||||
|
||||
|
||||
SOURCE = Path(__file__).with_name("hanja.src")
|
||||
MAXCANDIDATES = 32
|
||||
MAXCANDIDATES = 128
|
||||
|
||||
|
||||
def ishangul(s):
|
||||
|
||||
@@ -55,11 +55,15 @@ def vowel2():
|
||||
tab = [
|
||||
("oa", "oa", "a"), ("oe", "oe", "e"), ("ai", "ai", "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"),
|
||||
("aau", "âu", "â"), ("aay", "ây", "â"), ("eeu", "êu", "ê"),
|
||||
("ooi", "ôi", "ô"), ("owi", "ơi", "ơ"), ("uwi", "ưi", "ư"),
|
||||
("uwu", "ưu", "ư"), ("uwa", "ưa", "ư"), ("uee", "uê", "ê"),
|
||||
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
||||
("uow", "ươ", "ơ"), ("uaa", "uâ", "â"), ("oaw", "oă", "ă"),
|
||||
("uwa", "ưa", "a"), ("uwow", "ươ", "ơ"),
|
||||
("uwow", "ươ", "ơ"),
|
||||
]
|
||||
for i, o, v in tab:
|
||||
emit(i, o)
|
||||
@@ -68,21 +72,21 @@ def vowel2():
|
||||
emit("ie", "ie")
|
||||
emit("ye", "ye")
|
||||
emit("uo", "uo")
|
||||
emit("ua", "ua")
|
||||
|
||||
def vowel3():
|
||||
# input, output, vowel
|
||||
tab = [
|
||||
("ieeu", "iêu", "ê"), ("yeeu", "yêu", "ê"),
|
||||
("uooi", "uôi", "ô"), ("uowi", "ươi", "ơ"),
|
||||
("oai", "oai", "a"), ("oay", "oay", "a"),
|
||||
("uyee", "uyê", "ê"),
|
||||
("uowu", "ươu", "ơ"), ("uwowi", "ươi", "ơ"),
|
||||
("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:
|
||||
emit(i, o)
|
||||
for t in tone:
|
||||
emit(i+t, o.replace(v, addtone(v, t), 1))
|
||||
emit("uya", "uya")
|
||||
|
||||
def modvowels():
|
||||
for i, o in modvowel:
|
||||
@@ -132,10 +136,11 @@ def final():
|
||||
("aw", "ă", "ă"), ("aa", "â", "â"), ("ee", "ê", "ê"),
|
||||
("oo", "ô", "ô"), ("ow", "ơ", "ơ"), ("uw", "ư", "ư"),
|
||||
("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"),
|
||||
("ay", "ay", "a"), ("oa", "oa", "a"), ("oi", "oi", "o"),
|
||||
("ui", "ui", "u"), ("uy", "uy", "y"),
|
||||
("ay", "ay", "a"), ("oa", "oa", "a"), ("oe", "oe", "e"),
|
||||
("oi", "oi", "o"), ("ui", "ui", "u"), ("uy", "uy", "y"),
|
||||
]
|
||||
for i, o, v in tab:
|
||||
for c in coda:
|
||||
|
||||
768
map/telex.map
768
map/telex.map
@@ -142,6 +142,18 @@ ayx ãy
|
||||
Ayx Ã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
|
||||
eus éu
|
||||
@@ -154,6 +166,18 @@ eux ẽu
|
||||
Eux Ẽ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
|
||||
ius íu
|
||||
@@ -178,6 +202,18 @@ oix õi
|
||||
Oix Õ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
|
||||
uis úi
|
||||
@@ -202,6 +238,114 @@ uyx uỹ
|
||||
Uyx 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ê
|
||||
iees iế
|
||||
@@ -274,18 +418,6 @@ oawx oẵ
|
||||
Oawx Oẵ
|
||||
oawj oặ
|
||||
Oawj Oặ
|
||||
uwa ưa
|
||||
Uwa Ưa
|
||||
uwas ưá
|
||||
Uwas Ưá
|
||||
uwaf ưà
|
||||
Uwaf Ưà
|
||||
uwar ưả
|
||||
Uwar Ưả
|
||||
uwax ưã
|
||||
Uwax Ưã
|
||||
uwaj ưạ
|
||||
Uwaj Ưạ
|
||||
uwow ươ
|
||||
Uwow Ươ
|
||||
uwows ướ
|
||||
@@ -304,8 +436,6 @@ ye ye
|
||||
Ye Ye
|
||||
uo uo
|
||||
Uo Uo
|
||||
ua ua
|
||||
Ua Ua
|
||||
ieeu iêu
|
||||
Ieeu Iêu
|
||||
ieeus iếu
|
||||
@@ -354,6 +484,54 @@ uowix ưỡi
|
||||
Uowix Ưỡ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
|
||||
oais oái
|
||||
@@ -378,6 +556,42 @@ oayx oãy
|
||||
Oayx 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ê
|
||||
uyees uyế
|
||||
@@ -390,8 +604,6 @@ uyeex uyễ
|
||||
Uyeex Uyễ
|
||||
uyeej uyệ
|
||||
Uyeej Uyệ
|
||||
uya uya
|
||||
Uya Uya
|
||||
aw ă
|
||||
Aw Ă
|
||||
aa â
|
||||
@@ -2146,6 +2358,198 @@ uownhx ưỡnh
|
||||
Uownhx Ưỡ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
|
||||
uaacs uấc
|
||||
@@ -2242,6 +2646,102 @@ uaanhx uẫnh
|
||||
Uaanhx 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
|
||||
oawcs oắc
|
||||
@@ -2818,6 +3318,102 @@ oanhx oãnh
|
||||
Oanhx 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
|
||||
oics óic
|
||||
@@ -3153,10 +3749,18 @@ quay quay
|
||||
Quay Quay
|
||||
giay giay
|
||||
Giay Giay
|
||||
queo queo
|
||||
Queo Queo
|
||||
gieo gieo
|
||||
Gieo Gieo
|
||||
queu queu
|
||||
Queu Queu
|
||||
gieu gieu
|
||||
Gieu Gieu
|
||||
quia quia
|
||||
Quia Quia
|
||||
giia giia
|
||||
Giia Giia
|
||||
quiu quiu
|
||||
Quiu Quiu
|
||||
giiu giiu
|
||||
@@ -3165,10 +3769,40 @@ quoi quoi
|
||||
Quoi Quoi
|
||||
gioi gioi
|
||||
Gioi Gioi
|
||||
giua giua
|
||||
Giua Giua
|
||||
giui giui
|
||||
Giui Giui
|
||||
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ê
|
||||
giiee giiê
|
||||
@@ -3187,8 +3821,6 @@ quoaw quoă
|
||||
Quoaw Quoă
|
||||
gioaw gioă
|
||||
Gioaw Gioă
|
||||
giuwa giưa
|
||||
Giuwa Giưa
|
||||
giuwow giươ
|
||||
Giuwow Giươ
|
||||
quie quie
|
||||
@@ -3201,8 +3833,6 @@ giye giye
|
||||
Giye Giye
|
||||
giuo giuo
|
||||
Giuo Giuo
|
||||
giua giua
|
||||
Giua Giua
|
||||
quieeu quiêu
|
||||
Quieeu Quiêu
|
||||
giieeu giiêu
|
||||
@@ -3215,6 +3845,14 @@ giuooi giuôi
|
||||
Giuooi Giuô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
|
||||
gioai gioai
|
||||
@@ -3223,10 +3861,16 @@ quoay quoay
|
||||
Quoay Quoay
|
||||
gioay gioay
|
||||
Gioay Gioay
|
||||
giuyee giuyê
|
||||
Giuyee Giuyê
|
||||
quoeo quoeo
|
||||
Quoeo Quoeo
|
||||
gioeo gioeo
|
||||
Gioeo Gioeo
|
||||
giuya giuya
|
||||
Giuya Giuya
|
||||
giuyu giuyu
|
||||
Giuyu Giuyu
|
||||
giuyee giuyê
|
||||
Giuyee Giuyê
|
||||
quaw quă
|
||||
Quaw Quă
|
||||
giaw giă
|
||||
@@ -3719,6 +4363,38 @@ giuowng giương
|
||||
Giuowng Giương
|
||||
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
|
||||
giuaam giuâm
|
||||
@@ -3735,6 +4411,22 @@ giuaang giuâng
|
||||
Giuaang Giuâng
|
||||
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
|
||||
gioawc gioăc
|
||||
@@ -3927,6 +4619,38 @@ quoanh quoanh
|
||||
Quoanh Quoanh
|
||||
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
|
||||
gioic gioic
|
||||
|
||||
268
popup_layout.c
268
popup_layout.c
@@ -1,33 +1,269 @@
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
|
||||
int
|
||||
popupcells(Rune *r, int n)
|
||||
{
|
||||
int i, w;
|
||||
enum {
|
||||
Asciitofull = 0xFEE0,
|
||||
};
|
||||
|
||||
int popupscale = 1;
|
||||
|
||||
static void
|
||||
fill(u32int *buf, int n, u32int color)
|
||||
{
|
||||
int i;
|
||||
|
||||
w = 0;
|
||||
for(i = 0; i < n; i++)
|
||||
if(r[i] != 0xfe0e && r[i] != 0xfe0f)
|
||||
w++;
|
||||
return w;
|
||||
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
|
||||
popupposition(Caret *caret, int pointerx, int pointery, int screenw,
|
||||
int screenh, int w, int h, int *x, int *y)
|
||||
popuparea(Area *mon, int nmon, Area *work, int x, int y, Area *out)
|
||||
{
|
||||
vlong px, py, xmax, ymax;
|
||||
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, pad, total, width, y;
|
||||
|
||||
memset(p, 0, sizeof *p);
|
||||
p->prey = -1;
|
||||
p->sepy = -1;
|
||||
p->rowsy = -1;
|
||||
p->marky = -1;
|
||||
p->sely = -1;
|
||||
pad = PopupPad;
|
||||
nall = min(max(dc->nkouho, 0), Maxdisp);
|
||||
npre = dc->pre.n != 0;
|
||||
if((nall == 0 && !npre) || areaw <= 0 || areah <= 0)
|
||||
return;
|
||||
|
||||
/* Rows first; a page marker when they do not all fit; and in a
|
||||
* sliver of a work area the selected row alone, unpadded. */
|
||||
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 && markrow){
|
||||
markrow = 0;
|
||||
p->n = fitrows(areah, npre, markrow, nall);
|
||||
}
|
||||
if(p->n == 0 && nall != 0){
|
||||
npre = 0;
|
||||
pad = 0;
|
||||
p->n = max(min(nall, areah / Fontsz), 1);
|
||||
}
|
||||
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;
|
||||
|
||||
width = PopupBasew;
|
||||
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 = pad;
|
||||
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 + pad, 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;
|
||||
|
||||
if(img == nil || p->n < 0 || p->n > Maxdisp ||
|
||||
p->row0 < 0 || p->row0 + p->n > dc->nkouho ||
|
||||
p->w <= 0 || p->h <= 0 || p->h > Imgh)
|
||||
return;
|
||||
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;
|
||||
py = (vlong)caret->y + max(caret->h, 0);
|
||||
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((vlong)screenw - w, 0);
|
||||
ymax = max((vlong)screenh - h, 0);
|
||||
*x = max(0, min(px, xmax));
|
||||
*y = max(0, min(py, ymax));
|
||||
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));
|
||||
}
|
||||
|
||||
33
run.sh
33
run.sh
@@ -2,48 +2,23 @@
|
||||
|
||||
cd "$(dirname "$0")" || exit 1
|
||||
|
||||
if test "$#" -eq 0; then
|
||||
set --
|
||||
for font in \
|
||||
/usr/share/fonts/TTF/DejaVuSans.ttf \
|
||||
/usr/share/fonts/TTF/Jigmo.ttf \
|
||||
/usr/share/fonts/TTF/Jigmo2.ttf \
|
||||
/usr/share/fonts/noto/NotoSansCJK-Regular.ttc
|
||||
do
|
||||
test -f "$font" && set -- "$@" "$font"
|
||||
done
|
||||
if test "$#" -eq 0; then
|
||||
echo "run.sh: no default font found; pass a font file" >&2
|
||||
exit 1
|
||||
fi
|
||||
if test "$#" -ne 0; then
|
||||
echo "usage: run.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! test -x ./strans; then
|
||||
echo "run.sh: ./strans is not executable; run make first" >&2
|
||||
exit 1
|
||||
fi
|
||||
if test -n "${DISPLAY-}" && ! test -x xim/strans-xim; then
|
||||
echo "run.sh: xim/strans-xim 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-xim 2>/dev/null || :
|
||||
pkill -TERM -u "$uid" -x strans 2>/dev/null || :
|
||||
sleep 0.1
|
||||
pkill -KILL -u "$uid" -x strans-xim 2>/dev/null || :
|
||||
pkill -KILL -u "$uid" -x strans 2>/dev/null || :
|
||||
|
||||
./strans map "$@" </dev/null &
|
||||
strans_pid=$!
|
||||
if test -n "${DISPLAY-}"; then
|
||||
xim/strans-xim </dev/null &
|
||||
xim_pid=$!
|
||||
echo "run.sh: started daemon $strans_pid and XIM $xim_pid"
|
||||
else
|
||||
echo "run.sh: started daemon $strans_pid; DISPLAY is unset"
|
||||
fi
|
||||
./strans map </dev/null &
|
||||
|
||||
122
srv.c
122
srv.c
@@ -5,56 +5,108 @@
|
||||
#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 int
|
||||
srvreadkey(int fd, Keyreq *kr)
|
||||
/* Removes the socket only while it is still the one we announced. */
|
||||
static void
|
||||
srvunlink(void)
|
||||
{
|
||||
uchar req[Ipcreqsz];
|
||||
u32int ks, mod;
|
||||
int want;
|
||||
struct stat st;
|
||||
|
||||
if(ipcreadn(fd, req, sizeof req) < 0)
|
||||
return -1;
|
||||
ipcunpackreq(req, &want, &mod, &ks);
|
||||
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
|
||||
kr->ks = ks;
|
||||
kr->mod = mod;
|
||||
memset(&kr->caret, 0, sizeof kr->caret);
|
||||
return want;
|
||||
if(sockpath[0] != '\0' && lstat(sockpath, &st) == 0 &&
|
||||
st.st_dev == sockdev && st.st_ino == sockino)
|
||||
unlink(sockpath);
|
||||
sockpath[0] = '\0';
|
||||
}
|
||||
|
||||
static int
|
||||
srvnote(void *v, char *note)
|
||||
{
|
||||
USED(v);
|
||||
USED(note);
|
||||
srvunlink();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
srvreadreq(int fd, Keyreq *kr, int *want)
|
||||
{
|
||||
uchar req[Ipccaretsz];
|
||||
int valid;
|
||||
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;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* One Keyreq persists for the connection: the negotiated capability and
|
||||
* the last caret ride along with every request, and the socket closing
|
||||
* releases the engine.
|
||||
*/
|
||||
static void
|
||||
clientthread(void *arg)
|
||||
{
|
||||
Channel *reply;
|
||||
int fd;
|
||||
Keyreq kr;
|
||||
Keyres res;
|
||||
uchar out[Ipcmaxresp];
|
||||
uchar out[Ipcmaxresp], token;
|
||||
char commit[Maxutf], preedit[Maxutf];
|
||||
int n, ncommit, npreedit, want;
|
||||
uchar token;
|
||||
int fd, n, ncommit, npreedit, want;
|
||||
|
||||
fd = (int)(uintptr)arg;
|
||||
threadsetname("client %d", fd);
|
||||
reply = chancreate(sizeof(Keyres), 0);
|
||||
kr.reply = reply;
|
||||
memset(&kr, 0, sizeof kr);
|
||||
kr.reply = chancreate(sizeof(Keyres), 0);
|
||||
kr.owner = &fd;
|
||||
while((want = srvreadkey(fd, &kr)) >= 0){
|
||||
kr.cap = Cclientpreedit;
|
||||
while(srvreadreq(fd, &kr, &want) >= 0){
|
||||
if(kr.op != Keycaret)
|
||||
kr.cap = want ? Cclientpreedit : 0;
|
||||
chansend(keyc, &kr);
|
||||
chanrecv(reply, &res);
|
||||
chanrecv(kr.reply, &res);
|
||||
if(kr.op == Keycaret)
|
||||
continue;
|
||||
ncommit = stoutf(&res.commit, commit, sizeof commit);
|
||||
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
|
||||
n = ipcpackresp(out, sizeof out, res.eaten,
|
||||
/* A capability reply is always eaten: it marks the extension. */
|
||||
n = ipcpackresp(out, sizeof out, kr.op == Keycap || res.eaten,
|
||||
commit, ncommit, preedit, npreedit, want);
|
||||
if(n < 0 || ipcsend(fd, out, n) < 0)
|
||||
break;
|
||||
}
|
||||
kr.op = Keyrelease;
|
||||
kr.ks = 0;
|
||||
kr.mod = 0;
|
||||
chansend(keyc, &kr);
|
||||
chanrecv(reply, &res);
|
||||
chanfree(reply);
|
||||
chanrecv(kr.reply, &res);
|
||||
chanfree(kr.reply);
|
||||
close(fd);
|
||||
chanrecv(clientc, &token);
|
||||
}
|
||||
@@ -62,18 +114,23 @@ clientthread(void *arg)
|
||||
void
|
||||
srvinit(void)
|
||||
{
|
||||
char *addr, path[sizeof(((struct sockaddr_un*)0)->sun_path)];
|
||||
struct stat st;
|
||||
char *addr;
|
||||
|
||||
if(ipcpath(path, sizeof path) < 0)
|
||||
if(ipcpath(sockpath, sizeof sockpath) < 0)
|
||||
die("IPC path is too long");
|
||||
addr = smprint("unix!%s", path);
|
||||
addr = smprint("unix!%s", sockpath);
|
||||
if(addr == nil)
|
||||
die("out of memory");
|
||||
if(announce(addr, adir) < 0)
|
||||
die("IPC endpoint is already in use: %r");
|
||||
free(addr);
|
||||
if(chmod(path, 0600) < 0)
|
||||
die("can't protect IPC endpoint: %s", path);
|
||||
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
|
||||
@@ -94,9 +151,6 @@ srvthread(void*)
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
if(proccreate(clientthread, (void*)(uintptr)fd, 8192) < 0){
|
||||
chanrecv(clientc, &token);
|
||||
close(fd);
|
||||
}
|
||||
proccreate(clientthread, (void*)(uintptr)fd, 8192);
|
||||
}
|
||||
}
|
||||
|
||||
26
str.c
26
str.c
@@ -1,24 +1,27 @@
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
|
||||
void
|
||||
/* Fills s from n bytes of UTF-8; whole, valid, and at most Maxrunes. */
|
||||
int
|
||||
sinit(Str *s, char *src, int n)
|
||||
{
|
||||
Str tmp = {0};
|
||||
Rune r;
|
||||
int len;
|
||||
|
||||
s->n = 0;
|
||||
if(n < 0 || (n > 0 && src == nil))
|
||||
return;
|
||||
while(n > 0 && s->n < Maxrunes){
|
||||
if(!fullrune(src, n))
|
||||
break;
|
||||
len = chartorune(&s->r[s->n], src);
|
||||
if(len > n)
|
||||
break;
|
||||
s->n++;
|
||||
while(n > 0){
|
||||
if(tmp.n >= Maxrunes || !fullrune(src, n))
|
||||
return 0;
|
||||
len = chartorune(&r, src);
|
||||
if((r == Runeerror && len == 1) || (r >= 0xd800 && r <= 0xdfff))
|
||||
return 0;
|
||||
tmp.r[tmp.n++] = r;
|
||||
src += len;
|
||||
n -= len;
|
||||
}
|
||||
*s = tmp;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -66,14 +69,13 @@ scmp(Str *a, Str *b)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* UTF-8 of s into buf[sz], NUL-terminated, whole runes only. */
|
||||
int
|
||||
stoutf(Str *s, char *buf, int sz)
|
||||
{
|
||||
char tmp[UTFmax];
|
||||
int i, n, len;
|
||||
|
||||
if(sz <= 0)
|
||||
return 0;
|
||||
n = 0;
|
||||
for(i = 0; i < s->n; i++){
|
||||
len = runetochar(tmp, &s->r[i]);
|
||||
|
||||
151
tests/Makefile
151
tests/Makefile
@@ -1,71 +1,144 @@
|
||||
CC = 9c
|
||||
LD = 9l
|
||||
HOSTCC = cc
|
||||
CFLAGS = -std=c99 -Wall -Wextra -O2 -g -I.. -I../cutest
|
||||
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
|
||||
FT_LIBS = $(shell pkg-config --libs freetype2)
|
||||
DBUS_CFLAGS = $(shell pkg-config --cflags dbus-1)
|
||||
DBUS_LIBS = $(shell pkg-config --libs dbus-1)
|
||||
IBUS_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
|
||||
IBUS_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
|
||||
LIBS = -lthread -lbio $(FT_LIBS) $(IBUS_LIBS)
|
||||
PKG_CONFIG ?= pkg-config
|
||||
CFLAGS ?= -O2 -g
|
||||
UNIT_CPPFLAGS = -I.. -I../cutest
|
||||
UNIT_CFLAGS = -std=c99 -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-xkb xkbcommon-x11)
|
||||
XIM_LIBS = $(shell $(PKG_CONFIG) --libs xcb-imdkit xcb-xkb xkbcommon-x11)
|
||||
UNIT_LDLIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS)
|
||||
|
||||
PROG = unit_test
|
||||
LIVE = ibus_live_test ipc_live_test daemon_collision_test daemon_failure_test \
|
||||
daemon_restart_test
|
||||
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
||||
STRESS = stress_test
|
||||
LIVESRC = live.c live.h
|
||||
LIVEBUSSRC = livebus.c livebus.h
|
||||
SMOKE = ibus_live_test ibus_client_smoke gtk_live_test xim_live_test \
|
||||
ipc_live_test
|
||||
FAULT = daemon_collision_test daemon_failure_test daemon_restart_test
|
||||
LIVE = $(SMOKE) $(FAULT)
|
||||
TESTSRC = test_util.c str_test.c trie_test.c \
|
||||
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
||||
popup_test.c font_test.c ibus_test.c server_test.c
|
||||
popup_test.c font_test.c ibus_test.c server_test.c keymap_test.c \
|
||||
xim_adapter_test.c
|
||||
TESTOBJ = $(TESTSRC:.c=.o)
|
||||
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
|
||||
PARENTSRC = str.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
|
||||
font.c
|
||||
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
||||
OBJS = $(TESTOBJ) $(PARENTOBJ)
|
||||
XIMOBJ = unit_xim_keymap.o
|
||||
COMMONOBJ = $(TESTOBJ) $(PARENTOBJ) $(XIMOBJ)
|
||||
OBJS = unit_test.o stress_test.o $(COMMONOBJ)
|
||||
|
||||
all: $(PROG) $(LIVE)
|
||||
all: $(PROG) $(STRESS) $(LIVE)
|
||||
|
||||
check test: $(PROG) $(LIVE)
|
||||
./$(PROG) $(TESTARGS)
|
||||
./ibus_live_test ../strans ../map
|
||||
check test: $(PROG)
|
||||
./$(PROG) $(UNITARGS)
|
||||
|
||||
check-live: $(SMOKE) ../strans ../gtk/im-strans.so
|
||||
./ibus_live_test ../strans ../map ./ibus_client_smoke
|
||||
./gtk_live_test ../gtk/im-strans.so
|
||||
./xim_live_test ../strans ../map
|
||||
./ipc_live_test ../strans ../map
|
||||
|
||||
check-stress: $(STRESS) ibus_live_test ipc_live_test $(FAULT) ../strans
|
||||
./$(STRESS)
|
||||
./ibus_live_test --capacity ../strans ../map
|
||||
./ipc_live_test --capacity ../strans ../map
|
||||
./daemon_collision_test ../strans ../map
|
||||
./daemon_failure_test ../strans
|
||||
./daemon_restart_test ../strans ../map
|
||||
|
||||
$(PROG): $(OBJS)
|
||||
$(LD) -o $@ $(OBJS) $(LIBS)
|
||||
$(PROG): unit_test.o $(COMMONOBJ)
|
||||
$(LD) $(LDFLAGS) -o $@ unit_test.o $(COMMONOBJ) $(UNIT_LDLIBS) \
|
||||
$(LDLIBS)
|
||||
|
||||
ibus_live_test: ibus_live_test.c
|
||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g $(DBUS_CFLAGS) -o $@ $< $(DBUS_LIBS)
|
||||
$(STRESS): stress_test.o $(COMMONOBJ)
|
||||
$(LD) $(LDFLAGS) -o $@ stress_test.o $(COMMONOBJ) $(UNIT_LDLIBS) \
|
||||
$(LDLIBS)
|
||||
|
||||
ipc_live_test: ipc_live_test.c ../ipc.c ../ipc.h
|
||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. -o $@ ipc_live_test.c ../ipc.c
|
||||
stress_test.o: unit_test.c
|
||||
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -DSTRESS \
|
||||
-c -o $@ $<
|
||||
|
||||
daemon_collision_test: daemon_collision_test.c ../ipc.c ../ipc.h
|
||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(DBUS_CFLAGS) -o $@ \
|
||||
daemon_collision_test.c ../ipc.c $(DBUS_LIBS)
|
||||
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)
|
||||
|
||||
daemon_failure_test: daemon_failure_test.c
|
||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -o $@ $<
|
||||
ibus_client_smoke: ibus_client_smoke.c
|
||||
$(HOSTCC) $(CPPFLAGS) $(IBUS_CLIENT_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
||||
$(LDFLAGS) -o $@ $< $(IBUS_CLIENT_LIBS) $(LDLIBS)
|
||||
|
||||
daemon_restart_test: daemon_restart_test.c ../ipc.c ../ipc.h
|
||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(DBUS_CFLAGS) -o $@ \
|
||||
daemon_restart_test.c ../ipc.c $(DBUS_LIBS)
|
||||
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)
|
||||
|
||||
$(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h ../cutest/cutest.h
|
||||
../gtk/im-strans.so: ../gtk/main.c ../ipc.c ../ipc.h
|
||||
$(MAKE) -C ../gtk
|
||||
|
||||
../strans:
|
||||
$(MAKE) -C .. strans
|
||||
|
||||
xim_live_test: xim_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
||||
$(HOSTCC) $(CPPFLAGS) -I.. $(X11_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
||||
$(LDFLAGS) -o $@ xim_live_test.c live.c ../ipc.c $(X11_LIBS) \
|
||||
$(LDLIBS)
|
||||
|
||||
ipc_live_test: ipc_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
||||
$(HOSTCC) $(CPPFLAGS) -I.. $(HOST_CFLAGS) $(CFLAGS) $(LDFLAGS) \
|
||||
-o $@ ipc_live_test.c live.c ../ipc.c $(LDLIBS)
|
||||
|
||||
daemon_collision_test: daemon_collision_test.c $(LIVESRC) $(LIVEBUSSRC) \
|
||||
../ipc.c ../ipc.h
|
||||
$(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
||||
$(LDFLAGS) -o $@ daemon_collision_test.c live.c livebus.c \
|
||||
../ipc.c $(DBUS_LIBS) $(LDLIBS)
|
||||
|
||||
daemon_failure_test: daemon_failure_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
||||
$(HOSTCC) $(CPPFLAGS) -I.. $(HOST_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ \
|
||||
daemon_failure_test.c live.c ../ipc.c $(LDLIBS)
|
||||
|
||||
daemon_restart_test: daemon_restart_test.c $(LIVESRC) $(LIVEBUSSRC) \
|
||||
../ipc.c ../ipc.h
|
||||
$(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
||||
$(LDFLAGS) -o $@ daemon_restart_test.c live.c livebus.c ../ipc.c \
|
||||
$(DBUS_LIBS) $(LDLIBS)
|
||||
|
||||
unit_test.o stress_test.o $(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h \
|
||||
../cutest/cutest.h
|
||||
engine_test.o: ../strans.c
|
||||
ibus_test.o: CFLAGS += $(IBUS_CFLAGS)
|
||||
ibus_test.o: UNIT_CPPFLAGS += $(IBUS_CFLAGS)
|
||||
ibus_test.o: ../ibus.c
|
||||
keymap_test.o xim_adapter_test.o: UNIT_CPPFLAGS += $(XIM_CFLAGS)
|
||||
xim_adapter_test.o: ../xim/xim.c
|
||||
server_test.o: ../srv.c
|
||||
unit_font.o: CFLAGS += $(FT_CFLAGS)
|
||||
unit_font.o: UNIT_CPPFLAGS += $(TEXT_CFLAGS)
|
||||
|
||||
unit_xim_keymap.o: ../xim/keymap.c
|
||||
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(XIM_CFLAGS) $(UNIT_CFLAGS) \
|
||||
$(CFLAGS) -c -o $@ $<
|
||||
|
||||
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(PROG) $(LIVE)
|
||||
rm -f $(OBJS) $(PROG) $(STRESS) $(LIVE)
|
||||
|
||||
.PHONY: all check test clean
|
||||
.PHONY: all check check-live check-stress test clean
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,635 +1,138 @@
|
||||
#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/inotify.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "live.h"
|
||||
|
||||
enum
|
||||
{
|
||||
Failtimeout = 8000,
|
||||
Failtimeout = 4000,
|
||||
};
|
||||
|
||||
typedef struct Test Test;
|
||||
|
||||
struct Test
|
||||
{
|
||||
pid_t child;
|
||||
int errfd;
|
||||
int notifyfd;
|
||||
int runtimewd;
|
||||
int buswd;
|
||||
int endpointclean;
|
||||
char root[256];
|
||||
char runtime[320];
|
||||
char config[320];
|
||||
char ibus[384];
|
||||
char bus[448];
|
||||
char home[320];
|
||||
char socket[384];
|
||||
char badmap[320];
|
||||
char missingfont[384];
|
||||
char expected[768];
|
||||
char err[4096];
|
||||
size_t nerr;
|
||||
};
|
||||
|
||||
static uint32_t endpointmask = IN_CREATE|IN_MOVED_TO|IN_MOVED_FROM|IN_ATTRIB|
|
||||
IN_DELETE|IN_MODIFY|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF;
|
||||
|
||||
static int
|
||||
fail(char *fmt, ...)
|
||||
waitfailed(Daemon *d, char *badmap)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
fprintf(stderr, "daemon_failure_test: ");
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static int
|
||||
leftms(int64_t deadline)
|
||||
{
|
||||
int64_t n;
|
||||
|
||||
n = nowms();
|
||||
if(n < 0)
|
||||
return -1;
|
||||
n = deadline - n;
|
||||
if(n <= 0)
|
||||
return 0;
|
||||
if(n > 0x7fffffff)
|
||||
return 0x7fffffff;
|
||||
return n;
|
||||
}
|
||||
|
||||
static int
|
||||
makedir(char *path)
|
||||
{
|
||||
if(mkdir(path, 0700) == 0)
|
||||
return 1;
|
||||
return fail("mkdir %s: %s", path, strerror(errno));
|
||||
}
|
||||
|
||||
static void
|
||||
readerrors(Test *t)
|
||||
{
|
||||
ssize_t n;
|
||||
|
||||
if(t->errfd < 0)
|
||||
return;
|
||||
while(t->nerr + 1 < sizeof t->err){
|
||||
n = read(t->errfd, t->err + t->nerr,
|
||||
sizeof t->err - t->nerr - 1);
|
||||
if(n > 0){
|
||||
t->nerr += n;
|
||||
continue;
|
||||
}
|
||||
if(n < 0 && errno == EINTR)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
t->err[t->nerr] = '\0';
|
||||
}
|
||||
|
||||
static void
|
||||
showerrors(Test *t)
|
||||
{
|
||||
readerrors(t);
|
||||
if(t->nerr != 0)
|
||||
fprintf(stderr, "daemon_failure_test: daemon stderr:\n%s", t->err);
|
||||
}
|
||||
|
||||
static int
|
||||
setup(Test *t)
|
||||
{
|
||||
memset(t, 0, sizeof *t);
|
||||
t->child = -1;
|
||||
t->errfd = -1;
|
||||
t->notifyfd = -1;
|
||||
t->runtimewd = -1;
|
||||
t->buswd = -1;
|
||||
t->endpointclean = 1;
|
||||
if(nowms() < 0)
|
||||
return fail("read monotonic clock: %s", strerror(errno));
|
||||
snprintf(t->root, sizeof t->root, "/tmp/strans-failure.XXXXXX");
|
||||
if(mkdtemp(t->root) == NULL){
|
||||
t->root[0] = '\0';
|
||||
return fail("mkdtemp: %s", strerror(errno));
|
||||
}
|
||||
if(snprintf(t->runtime, sizeof t->runtime, "%s/runtime", t->root)
|
||||
>= (int)sizeof t->runtime ||
|
||||
snprintf(t->config, sizeof t->config, "%s/config", t->root)
|
||||
>= (int)sizeof t->config ||
|
||||
snprintf(t->ibus, sizeof t->ibus, "%s/ibus", t->config)
|
||||
>= (int)sizeof t->ibus ||
|
||||
snprintf(t->bus, sizeof t->bus, "%s/bus", t->ibus)
|
||||
>= (int)sizeof t->bus ||
|
||||
snprintf(t->home, sizeof t->home, "%s/home", t->root)
|
||||
>= (int)sizeof t->home ||
|
||||
snprintf(t->socket, sizeof t->socket, "%s/strans.sock", t->runtime)
|
||||
>= (int)sizeof t->socket ||
|
||||
snprintf(t->badmap, sizeof t->badmap, "%s/missing-map", t->root)
|
||||
>= (int)sizeof t->badmap ||
|
||||
snprintf(t->missingfont, sizeof t->missingfont,
|
||||
"%s/missing-font.ttf", t->root) >= (int)sizeof t->missingfont ||
|
||||
snprintf(t->expected, sizeof t->expected,
|
||||
"strans: can't open: %s/hira.map\n", t->badmap)
|
||||
>= (int)sizeof t->expected)
|
||||
return fail("temporary path is too long");
|
||||
if(!makedir(t->runtime) || !makedir(t->config) || !makedir(t->ibus) ||
|
||||
!makedir(t->bus) || !makedir(t->home))
|
||||
return 0;
|
||||
t->notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
|
||||
if(t->notifyfd < 0)
|
||||
return fail("inotify_init1: %s", strerror(errno));
|
||||
t->runtimewd = inotify_add_watch(t->notifyfd, t->runtime, endpointmask);
|
||||
if(t->runtimewd < 0)
|
||||
return fail("watch runtime directory: %s", strerror(errno));
|
||||
t->buswd = inotify_add_watch(t->notifyfd, t->bus, endpointmask);
|
||||
if(t->buswd < 0)
|
||||
return fail("watch IBus directory: %s", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
startchild(Test *t, char *program)
|
||||
{
|
||||
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(dup2(errpipe[1], STDERR_FILENO) < 0)
|
||||
_exit(126);
|
||||
close(errpipe[1]);
|
||||
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", t->runtime, 1) < 0 ||
|
||||
setenv("XDG_CONFIG_HOME", t->config, 1) < 0 ||
|
||||
setenv("HOME", t->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
||||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
||||
unsetenv("IBUS_ADDRESS") < 0){
|
||||
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
|
||||
strerror(errno));
|
||||
_exit(126);
|
||||
}
|
||||
execl(program, program, t->badmap, t->missingfont, (char*)0);
|
||||
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
||||
_exit(127);
|
||||
}
|
||||
close(errpipe[1]);
|
||||
t->child = pid;
|
||||
t->errfd = errpipe[0];
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
drainnotify(Test *t)
|
||||
{
|
||||
char buf[4096];
|
||||
char *where;
|
||||
struct inotify_event *ev;
|
||||
ssize_t n;
|
||||
size_t off;
|
||||
int ok;
|
||||
|
||||
ok = 1;
|
||||
for(;;){
|
||||
n = read(t->notifyfd, buf, sizeof buf);
|
||||
if(n < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(n < 0 && errno == EAGAIN)
|
||||
return ok;
|
||||
if(n < 0)
|
||||
return fail("read endpoint watches: %s", strerror(errno));
|
||||
if(n == 0)
|
||||
return ok;
|
||||
for(off = 0; off + sizeof *ev <= (size_t)n;
|
||||
off += sizeof *ev + ev->len){
|
||||
ev = (struct inotify_event*)(buf + off);
|
||||
if(off + sizeof *ev + ev->len > (size_t)n)
|
||||
return fail("truncated inotify event");
|
||||
if(ev->mask & IN_Q_OVERFLOW){
|
||||
t->endpointclean = 0;
|
||||
fail("endpoint watch queue overflowed");
|
||||
ok = 0;
|
||||
continue;
|
||||
}
|
||||
if(ev->mask & (IN_IGNORED|IN_UNMOUNT)){
|
||||
t->endpointclean = 0;
|
||||
fail("endpoint watch became invalid: %#x", ev->mask);
|
||||
ok = 0;
|
||||
continue;
|
||||
}
|
||||
if(ev->wd != t->runtimewd && ev->wd != t->buswd)
|
||||
continue;
|
||||
if(!(ev->mask & endpointmask))
|
||||
continue;
|
||||
where = ev->wd == t->runtimewd ? "runtime" : "IBus";
|
||||
t->endpointclean = 0;
|
||||
fail("unexpected %s endpoint event %#x for %s", where,
|
||||
ev->mask, ev->len != 0 ? ev->name : "directory");
|
||||
ok = 0;
|
||||
}
|
||||
if(off != (size_t)n)
|
||||
return fail("truncated inotify event buffer");
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
killowned(Test *t, int *status)
|
||||
{
|
||||
pid_t pid;
|
||||
int n, ok;
|
||||
|
||||
if(t->child <= 0)
|
||||
return 1;
|
||||
pid = t->child;
|
||||
ok = 1;
|
||||
if(kill(pid, SIGKILL) < 0 && errno != ESRCH){
|
||||
fail("kill -9 %ld: %s", (long)pid, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
do
|
||||
n = waitpid(pid, status, 0);
|
||||
while(n < 0 && errno == EINTR);
|
||||
if(n != pid){
|
||||
fail("reap %ld after SIGKILL: %s", (long)pid,
|
||||
n < 0 ? strerror(errno) : "wrong child");
|
||||
ok = 0;
|
||||
}else
|
||||
t->child = -1;
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
waitfailed(Test *t)
|
||||
{
|
||||
struct pollfd pfd[2];
|
||||
pid_t pid;
|
||||
int n, ok, status, timeout;
|
||||
struct pollfd pfd;
|
||||
int n, status;
|
||||
int64_t deadline;
|
||||
|
||||
pid = t->child;
|
||||
ok = 1;
|
||||
deadline = nowms();
|
||||
if(deadline < 0){
|
||||
fail("read monotonic clock before waiting for daemon: %s",
|
||||
strerror(errno));
|
||||
killowned(t, &status);
|
||||
readerrors(t);
|
||||
drainnotify(t);
|
||||
return 0;
|
||||
}
|
||||
deadline += Failtimeout;
|
||||
status = 0;
|
||||
deadline = nowms() + Failtimeout;
|
||||
for(;;){
|
||||
do
|
||||
n = waitpid(pid, &status, WNOHANG);
|
||||
while(n < 0 && errno == EINTR);
|
||||
if(n == pid){
|
||||
t->child = -1;
|
||||
readerrors(t);
|
||||
if(!drainnotify(t))
|
||||
ok = 0;
|
||||
n = waitpid(d->pid, &status, WNOHANG);
|
||||
if(n == d->pid){
|
||||
d->pid = -1;
|
||||
readerrors(d);
|
||||
break;
|
||||
}
|
||||
if(n < 0){
|
||||
fail("waitpid failed daemon %ld: %s", (long)pid,
|
||||
strerror(errno));
|
||||
if(errno == ECHILD){
|
||||
t->child = -1;
|
||||
readerrors(t);
|
||||
drainnotify(t);
|
||||
}else{
|
||||
killowned(t, &status);
|
||||
readerrors(t);
|
||||
drainnotify(t);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
timeout = leftms(deadline);
|
||||
if(timeout < 0){
|
||||
fail("read monotonic clock while waiting for daemon: %s",
|
||||
strerror(errno));
|
||||
killowned(t, &status);
|
||||
readerrors(t);
|
||||
drainnotify(t);
|
||||
return 0;
|
||||
}
|
||||
if(timeout == 0){
|
||||
fail("daemon %ld did not exit after map initialization failure",
|
||||
(long)pid);
|
||||
killowned(t, &status);
|
||||
return 0;
|
||||
}
|
||||
pfd[0].fd = t->notifyfd;
|
||||
pfd[0].events = POLLIN;
|
||||
pfd[0].revents = 0;
|
||||
pfd[1].fd = t->errfd;
|
||||
pfd[1].events = POLLIN|POLLHUP;
|
||||
pfd[1].revents = 0;
|
||||
n = poll(pfd, 2, timeout);
|
||||
if(n < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(n < 0){
|
||||
fail("poll failed daemon: %s", strerror(errno));
|
||||
killowned(t, &status);
|
||||
readerrors(t);
|
||||
drainnotify(t);
|
||||
return 0;
|
||||
if(errno == ECHILD)
|
||||
d->pid = -1;
|
||||
return fail("waitpid: %s", strerror(errno));
|
||||
}
|
||||
if(pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL)){
|
||||
fail("endpoint watch became unusable: %#x", pfd[0].revents);
|
||||
killowned(t, &status);
|
||||
readerrors(t);
|
||||
drainnotify(t);
|
||||
return 0;
|
||||
}
|
||||
if(pfd[1].revents & (POLLERR|POLLNVAL)){
|
||||
fail("daemon stderr pipe became unusable: %#x",
|
||||
pfd[1].revents);
|
||||
killowned(t, &status);
|
||||
readerrors(t);
|
||||
drainnotify(t);
|
||||
return 0;
|
||||
}
|
||||
if((pfd[0].revents & POLLIN) && !drainnotify(t))
|
||||
ok = 0;
|
||||
if(pfd[1].revents & (POLLIN|POLLHUP))
|
||||
readerrors(t);
|
||||
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) != 1)
|
||||
if(!WIFEXITED(status) || WEXITSTATUS(status) == 0)
|
||||
return fail("daemon map failure had wait status %#x", status);
|
||||
if(strcmp(t->err, t->expected) != 0)
|
||||
return fail("daemon did not report the expected map initialization failure");
|
||||
return ok && t->endpointclean;
|
||||
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
|
||||
dirempty(char *path, char *which)
|
||||
emptydir(char *path)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent *de;
|
||||
int count, ok;
|
||||
int empty;
|
||||
|
||||
dir = opendir(path);
|
||||
if(dir == NULL)
|
||||
return fail("open %s directory %s: %s", which, path, strerror(errno));
|
||||
count = 0;
|
||||
ok = 1;
|
||||
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)
|
||||
continue;
|
||||
count++;
|
||||
fail("unexpected %s directory entry %s", which, de->d_name);
|
||||
ok = 0;
|
||||
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 directory %s: %s", which, path, strerror(errno));
|
||||
ok = 0;
|
||||
fail("read %s: %s", path, strerror(errno));
|
||||
empty = 0;
|
||||
}
|
||||
if(closedir(dir) < 0){
|
||||
fail("close %s directory %s: %s", which, path, strerror(errno));
|
||||
ok = 0;
|
||||
fail("close %s: %s", path, strerror(errno));
|
||||
empty = 0;
|
||||
}
|
||||
if(count != 0)
|
||||
ok = 0;
|
||||
return ok;
|
||||
return empty;
|
||||
}
|
||||
|
||||
static int
|
||||
checkendpoints(Test *t)
|
||||
checkendpoints(Live *l)
|
||||
{
|
||||
struct stat st;
|
||||
int ok;
|
||||
|
||||
ok = 1;
|
||||
errno = 0;
|
||||
if(lstat(t->socket, &st) == 0){
|
||||
fail("IPC endpoint exists after failed startup: %s", t->socket);
|
||||
ok = 0;
|
||||
}else if(errno != ENOENT){
|
||||
fail("lstat failed IPC endpoint: %s", strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(!dirempty(t->runtime, "runtime"))
|
||||
ok = 0;
|
||||
if(!dirempty(t->bus, "IBus"))
|
||||
ok = 0;
|
||||
return ok && t->endpointclean;
|
||||
}
|
||||
|
||||
static int clearfd(int);
|
||||
|
||||
static int
|
||||
removeentry(int dirfd, char *name)
|
||||
{
|
||||
struct stat st;
|
||||
int fd, ok;
|
||||
|
||||
if(fstatat(dirfd, name, &st, AT_SYMLINK_NOFOLLOW) < 0)
|
||||
return fail("stat cleanup entry %s: %s", name, strerror(errno));
|
||||
if(!S_ISDIR(st.st_mode)){
|
||||
if(unlinkat(dirfd, name, 0) == 0 || errno == ENOENT)
|
||||
return 1;
|
||||
return fail("remove cleanup entry %s: %s", name, strerror(errno));
|
||||
}
|
||||
fd = openat(dirfd, name, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
|
||||
if(fd < 0)
|
||||
return fail("open cleanup directory %s: %s", name, strerror(errno));
|
||||
ok = clearfd(fd);
|
||||
if(close(fd) < 0){
|
||||
fail("close cleanup directory %s: %s", name, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(unlinkat(dirfd, name, AT_REMOVEDIR) < 0 && errno != ENOENT){
|
||||
fail("remove cleanup directory %s: %s", name, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
clearfd(int fd)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent *de;
|
||||
int copy, ok;
|
||||
|
||||
copy = dup(fd);
|
||||
if(copy < 0)
|
||||
return fail("duplicate cleanup directory: %s", strerror(errno));
|
||||
dir = fdopendir(copy);
|
||||
if(dir == NULL){
|
||||
close(copy);
|
||||
return fail("open cleanup directory stream: %s", strerror(errno));
|
||||
}
|
||||
ok = 1;
|
||||
errno = 0;
|
||||
while((de = readdir(dir)) != NULL){
|
||||
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
||||
continue;
|
||||
if(!removeentry(fd, de->d_name))
|
||||
ok = 0;
|
||||
errno = 0;
|
||||
}
|
||||
if(errno != 0){
|
||||
fail("read cleanup directory: %s", strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(closedir(dir) < 0){
|
||||
fail("close cleanup directory stream: %s", strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
removeroot(Test *t)
|
||||
{
|
||||
int fd, ok;
|
||||
|
||||
if(t->root[0] == '\0')
|
||||
return 1;
|
||||
fd = open(t->root, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
|
||||
if(fd < 0)
|
||||
return errno == ENOENT || fail("open cleanup root %s: %s", t->root,
|
||||
strerror(errno));
|
||||
ok = clearfd(fd);
|
||||
if(close(fd) < 0){
|
||||
fail("close cleanup root %s: %s", t->root, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(rmdir(t->root) < 0 && errno != ENOENT){
|
||||
fail("rmdir %s: %s", t->root, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
cleanup(Test *t)
|
||||
{
|
||||
int n, ok, status;
|
||||
|
||||
ok = 1;
|
||||
if(t->child > 0){
|
||||
do
|
||||
n = waitpid(t->child, &status, WNOHANG);
|
||||
while(n < 0 && errno == EINTR);
|
||||
if(n == t->child)
|
||||
t->child = -1;
|
||||
else if(n == 0){
|
||||
fail("failed-start daemon still running during cleanup");
|
||||
ok = 0;
|
||||
if(!killowned(t, &status))
|
||||
ok = 0;
|
||||
}else{
|
||||
fail("check failed-start daemon during cleanup: %s",
|
||||
strerror(errno));
|
||||
ok = 0;
|
||||
if(errno == ECHILD)
|
||||
t->child = -1;
|
||||
else if(!killowned(t, &status))
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
readerrors(t);
|
||||
if(t->errfd >= 0){
|
||||
if(close(t->errfd) < 0){
|
||||
fail("close daemon stderr: %s", strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
t->errfd = -1;
|
||||
}
|
||||
if(t->notifyfd >= 0){
|
||||
if(t->runtimewd >= 0 &&
|
||||
inotify_rm_watch(t->notifyfd, t->runtimewd) < 0 && errno != EINVAL){
|
||||
fail("remove runtime watch: %s", strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(t->buswd >= 0 &&
|
||||
inotify_rm_watch(t->notifyfd, t->buswd) < 0 && errno != EINVAL){
|
||||
fail("remove IBus watch: %s", strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(close(t->notifyfd) < 0){
|
||||
fail("close endpoint watches: %s", strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
t->notifyfd = -1;
|
||||
}
|
||||
if(!removeroot(t))
|
||||
ok = 0;
|
||||
return ok;
|
||||
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)
|
||||
{
|
||||
Test test;
|
||||
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;
|
||||
}
|
||||
ok = setup(&test);
|
||||
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 = startchild(&test, argv[1]);
|
||||
if(ok){
|
||||
ok = waitfailed(&test);
|
||||
if(test.child <= 0 && !checkendpoints(&test))
|
||||
ok = 0;
|
||||
}
|
||||
if(!cleanup(&test))
|
||||
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(&test);
|
||||
showerrors(&daemon);
|
||||
return 1;
|
||||
}
|
||||
printf("failed daemon startup published no endpoints: ok\n");
|
||||
printf("failed daemon startup leaves no endpoints: ok\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,35 +1,28 @@
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
#include "test.h"
|
||||
|
||||
void
|
||||
dictionary_candidates(struct ct *t)
|
||||
{
|
||||
char many[512], item[8];
|
||||
char many[8*(Maxkouho+1)], item[8];
|
||||
char *p;
|
||||
Dictreq req;
|
||||
Dictres res;
|
||||
Hmap *saved;
|
||||
Str kouho[Maxkouho], key;
|
||||
Trie *saved;
|
||||
Lang *lang;
|
||||
Str key;
|
||||
int i;
|
||||
int i, n;
|
||||
|
||||
lang = getlang(LangJP);
|
||||
saved = lang->dict;
|
||||
lang->dict = hmapalloc(1);
|
||||
key = mkstr("かな");
|
||||
hmapset(&lang->dict, &key, " 候補1 かな 候補2 ",
|
||||
lang->dict = trienew();
|
||||
trieput(lang->dict, "かな", strlen("かな"), " 候補1 かな 候補2 ",
|
||||
strlen(" 候補1 かな 候補2 "));
|
||||
memset(&req, 0, sizeof req);
|
||||
req.key = key;
|
||||
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");
|
||||
key = mkstr("かな");
|
||||
n = dictlookup(lang, &key, kouho, Maxkouho);
|
||||
if(CT_EQ_INT(t, 2, n)){
|
||||
checkstr(t, "candidate 1", "候補1", &kouho[0]);
|
||||
checkstr(t, "candidate 2", "候補2", &kouho[1]);
|
||||
}
|
||||
p = many;
|
||||
for(i = 0; i < Maxkouho+1; i++){
|
||||
snprint(item, sizeof item, "c%02d", i);
|
||||
@@ -39,78 +32,52 @@ dictionary_candidates(struct ct *t)
|
||||
p += strlen(item);
|
||||
}
|
||||
*p = '\0';
|
||||
hmapset(&lang->dict, &key, many, strlen(many));
|
||||
req.key = key;
|
||||
req.pre = mkstr("preedit-two");
|
||||
dictlookup(&req, &res);
|
||||
if(!CT_EQ_INT(t, Maxkouho, res.nkouho))
|
||||
goto cleanup;
|
||||
CT_EQ_INT(t, LangJP, res.lang);
|
||||
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
|
||||
checkstr(t, "first capped candidate", "c00", &res.kouho[0]);
|
||||
checkstr(t, "last capped candidate", "c31", &res.kouho[31]);
|
||||
cleanup:
|
||||
hmapfree(lang->dict);
|
||||
trieput(lang->dict, "key", 3, many, strlen(many));
|
||||
key = mkstr("key");
|
||||
n = dictlookup(lang, &key, kouho, Maxkouho);
|
||||
if(CT_EQ_INT(t, Maxkouho, n)){
|
||||
checkstr(t, "first capped candidate", "c00", &kouho[0]);
|
||||
snprint(item, sizeof item, "c%02d", Maxkouho-1);
|
||||
checkstr(t, "last capped candidate", item, &kouho[Maxkouho-1]);
|
||||
}
|
||||
CT_EQ_INT(t, 3, dictlookup(lang, &key, kouho, 3));
|
||||
trieclose(lang->dict);
|
||||
lang->dict = saved;
|
||||
}
|
||||
|
||||
void
|
||||
dictionary_misses_clear_result(struct ct *t)
|
||||
dictionary_misses(struct ct *t)
|
||||
{
|
||||
static const struct {
|
||||
char *name;
|
||||
char *key;
|
||||
char *pre;
|
||||
} cases[] = {
|
||||
{ "empty key", "", "empty-preedit" },
|
||||
{ "missing key", "missing", "missing-preedit" },
|
||||
};
|
||||
Dictreq req;
|
||||
Dictres res;
|
||||
Hmap *saved;
|
||||
Str kouho[Maxkouho], key;
|
||||
Trie *saved;
|
||||
Lang *lang;
|
||||
int i;
|
||||
|
||||
lang = getlang(LangJP);
|
||||
saved = lang->dict;
|
||||
lang->dict = hmapalloc(1);
|
||||
for(i = 0; i < nelem(cases); i++){
|
||||
memset(&res, 0xa5, sizeof res);
|
||||
req.key = mkstr(cases[i].key);
|
||||
req.pre = mkstr(cases[i].pre);
|
||||
req.lang = LangJP;
|
||||
dictlookup(&req, &res);
|
||||
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 = trienew();
|
||||
key = mkstr("");
|
||||
CT_EQ_INT(t, 0, dictlookup(lang, &key, kouho, Maxkouho));
|
||||
key = mkstr("missing");
|
||||
CT_EQ_INT(t, 0, dictlookup(lang, &key, kouho, Maxkouho));
|
||||
CT_EQ_INT(t, 0, dictlookup(getlang(LangKO), &key, kouho, Maxkouho));
|
||||
trieclose(lang->dict);
|
||||
lang->dict = saved;
|
||||
}
|
||||
|
||||
void
|
||||
dictionary_emoji_identity(struct ct *t)
|
||||
{
|
||||
Dictreq req;
|
||||
Dictres res;
|
||||
Hmap *saved;
|
||||
Str kouho[Maxkouho], key;
|
||||
Trie *saved;
|
||||
Lang *lang;
|
||||
Str key;
|
||||
|
||||
lang = getlang(LangEMOJI);
|
||||
saved = lang->dict;
|
||||
lang->dict = hmapalloc(2);
|
||||
lang->dict = trienew();
|
||||
trieput(lang->dict, "é", strlen("é"), "é", strlen("é"));
|
||||
key = mkstr("é");
|
||||
hmapset(&lang->dict, &key, "é", strlen("é"));
|
||||
memset(&req, 0, sizeof req);
|
||||
req.key = key;
|
||||
req.pre = key;
|
||||
req.lang = LangEMOJI;
|
||||
dictlookup(&req, &res);
|
||||
if(CT_EQ_INT(t, 1, res.nkouho))
|
||||
checkstr(t, "emoji identity candidate", "é", &res.kouho[0]);
|
||||
hmapfree(lang->dict);
|
||||
if(CT_EQ_INT(t, 1, dictlookup(lang, &key, kouho, Maxkouho)))
|
||||
checkstr(t, "emoji identity candidate", "é", &kouho[0]);
|
||||
trieclose(lang->dict);
|
||||
lang->dict = saved;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,18 @@
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
#include "test.h"
|
||||
|
||||
enum
|
||||
{
|
||||
Guard = 8,
|
||||
Testw = 3 * Fontsz,
|
||||
Testh = 3 * Fontsz,
|
||||
Testn = Testw * Testh,
|
||||
Missingn = Fontsz * Fontsz,
|
||||
Rgbmask = 0xffffff,
|
||||
};
|
||||
|
||||
#define Testw (6 * Fontsz)
|
||||
#define Testh (3 * Fontsz)
|
||||
#define Testn (Testw * Testh)
|
||||
|
||||
static u32int guardcolor = 0x5a5a5a5a;
|
||||
static char *testfonts[] = {
|
||||
"/usr/share/fonts/TTF/DejaVuSans.ttf",
|
||||
"/usr/share/fonts/TTF/Jigmo.ttf",
|
||||
"/usr/share/fonts/TTF/Jigmo2.ttf",
|
||||
};
|
||||
static char *missingfonts[] = {
|
||||
"/strans-test-no-such-font",
|
||||
};
|
||||
|
||||
static void
|
||||
fillpixels(u32int *p, int n, u32int color)
|
||||
@@ -29,38 +24,84 @@ fillpixels(u32int *p, int n, u32int color)
|
||||
}
|
||||
|
||||
static int
|
||||
hasink(u32int *p, int n)
|
||||
hasink(u32int *p, int n, u32int bg)
|
||||
{
|
||||
int i;
|
||||
|
||||
bg &= Rgbmask;
|
||||
for(i = 0; i < n; i++)
|
||||
if(p[i] != Colbg)
|
||||
if((p[i] & Rgbmask) != bg)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
renders(Rune r)
|
||||
hascolors(u32int *p, int n, u32int bg)
|
||||
{
|
||||
u32int buf[Fontsz * Fontsz];
|
||||
u32int c, first;
|
||||
int b, found, g, i, r;
|
||||
|
||||
fillpixels(buf, nelem(buf), Colbg);
|
||||
putfont(buf, Fontsz, Fontsz, 0, 0, r);
|
||||
return hasink(buf, nelem(buf));
|
||||
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, int *minx, int *miny, int *maxx, int *maxy)
|
||||
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] == Colbg)
|
||||
if((p[y * Testw + x] & Rgbmask) == bg)
|
||||
continue;
|
||||
*minx = min(*minx, x);
|
||||
*miny = min(*miny, y);
|
||||
@@ -72,86 +113,150 @@ inkbounds(u32int *p, int *minx, int *miny, int *maxx, int *maxy)
|
||||
}
|
||||
|
||||
static void
|
||||
checkclip(struct ct *t, u32int *ref, int dx, int dy)
|
||||
checktext(struct ct *t, u32int *buf, char *utf)
|
||||
{
|
||||
u32int *buf, *mem, *want;
|
||||
int i, sx, sy, x, y;
|
||||
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;
|
||||
|
||||
mem = emalloc((Testn + 2 * Guard) * sizeof mem[0]);
|
||||
want = emalloc(Testn * sizeof want[0]);
|
||||
fillpixels(mem, Testn + 2 * Guard, guardcolor);
|
||||
buf = mem + Guard;
|
||||
fillpixels(buf, Testn, Colbg);
|
||||
fillpixels(want, Testn, Colbg);
|
||||
for(y = 0; y < Testh; y++){
|
||||
for(x = 0; x < Testw; x++){
|
||||
sx = x - dx;
|
||||
sy = y - dy;
|
||||
if(sx >= 0 && sx < Testw && sy >= 0 && sy < Testh)
|
||||
want[y * Testw + x] = ref[sy * Testw + sx];
|
||||
}
|
||||
}
|
||||
putfont(buf, Testw, Testh, Fontsz + dx, Fontsz + dy, 'A');
|
||||
CT_CHECK(t, hasink(buf, Testn));
|
||||
CT_EQ_MEM(t, want, buf, Testn * sizeof buf[0]);
|
||||
for(i = 0; i < Guard; i++){
|
||||
CT_EQ_UINT(t, guardcolor, mem[i]);
|
||||
CT_EQ_UINT(t, guardcolor, mem[Guard + Testn + i]);
|
||||
}
|
||||
free(want);
|
||||
free(mem);
|
||||
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)
|
||||
{
|
||||
u32int *buf, *missing, *ref;
|
||||
int i, minx, miny, maxx, maxy;
|
||||
static char *plain[] = { "A", "가", "あ", "漢", "☆", "𠀋" };
|
||||
u32int *buf, *mem;
|
||||
Str a, heart, longrow, missing;
|
||||
int i, maxx, maxy, minx, miny, natural, w, x, y;
|
||||
|
||||
missing = emalloc((Missingn + 2 * Guard) * sizeof missing[0]);
|
||||
fillpixels(missing, Missingn + 2 * Guard, guardcolor);
|
||||
buf = missing + Guard;
|
||||
fillpixels(buf, Missingn, Colbg);
|
||||
CT_CHECK(t, !fontinit(missingfonts, nelem(missingfonts)));
|
||||
putfont(buf, Fontsz, Fontsz, 0, 0, 'A');
|
||||
for(i = 0; i < Missingn; i++)
|
||||
CT_EQ_UINT(t, Colbg, buf[i]);
|
||||
for(i = 0; i < Guard; i++){
|
||||
CT_EQ_UINT(t, guardcolor, missing[i]);
|
||||
CT_EQ_UINT(t, guardcolor, missing[Guard + Missingn + i]);
|
||||
}
|
||||
|
||||
if(!CT_CHECK(t, fontinit(testfonts, nelem(testfonts)))){
|
||||
free(missing);
|
||||
mem = emalloc((Testn + 2 * Guard) * sizeof mem[0]);
|
||||
fillpixels(mem, Testn + 2 * Guard, guardcolor);
|
||||
buf = mem + Guard;
|
||||
fillpixels(buf, Testn, Colbg);
|
||||
a = mkstr("A");
|
||||
if(!CT_CHECK(t, textinit())){
|
||||
free(mem);
|
||||
return;
|
||||
}
|
||||
CT_CHECK(t, renders('A'));
|
||||
CT_CHECK(t, renders(0xac00));
|
||||
CT_CHECK(t, renders(0x4e00));
|
||||
CT_CHECK(t, renders(0x1f600));
|
||||
CT_CHECK(t, renders(0x2000b));
|
||||
fillpixels(missing, Missingn + 2 * Guard, guardcolor);
|
||||
buf = missing + Guard;
|
||||
fillpixels(buf, Missingn, Colbg);
|
||||
putfont(buf, Fontsz, Fontsz, 0, 0, 0x10ffff);
|
||||
for(i = 0; i < Missingn; i++)
|
||||
CT_EQ_UINT(t, Colbg, buf[i]);
|
||||
for(i = 0; i < Guard; i++){
|
||||
CT_EQ_UINT(t, guardcolor, missing[i]);
|
||||
CT_EQ_UINT(t, guardcolor, missing[Guard + Missingn + i]);
|
||||
}
|
||||
free(missing);
|
||||
for(i = 0; i < nelem(plain); i++)
|
||||
checktext(t, buf, plain[i]);
|
||||
|
||||
ref = emalloc(Testn * sizeof ref[0]);
|
||||
fillpixels(ref, Testn, Colbg);
|
||||
putfont(ref, Testw, Testh, Fontsz, Fontsz, 'A');
|
||||
if(!CT_CHECK(t, inkbounds(ref, &minx, &miny, &maxx, &maxy))){
|
||||
free(ref);
|
||||
return;
|
||||
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);
|
||||
}
|
||||
checkclip(t, ref, -minx - 1, 0);
|
||||
checkclip(t, ref, Testw - maxx, 0);
|
||||
checkclip(t, ref, 0, -miny - 1);
|
||||
checkclip(t, ref, 0, Testh - maxy);
|
||||
free(ref);
|
||||
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();
|
||||
textclose();
|
||||
CT_EQ_INT(t, 0, textwidth(&a));
|
||||
}
|
||||
|
||||
940
tests/gtk_live_test.c
Normal file
940
tests/gtk_live_test.c
Normal file
@@ -0,0 +1,940 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.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"
|
||||
|
||||
enum
|
||||
{
|
||||
Maxevent = 256,
|
||||
Timeout = 4000,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
Ecap,
|
||||
Ecaret,
|
||||
Ekey,
|
||||
Ereset,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
Rnormal,
|
||||
Rcommitpre,
|
||||
Rbadcommit,
|
||||
Rbadpreedit,
|
||||
};
|
||||
|
||||
typedef struct Event Event;
|
||||
typedef struct Server Server;
|
||||
typedef struct Siglog Siglog;
|
||||
|
||||
struct Event
|
||||
{
|
||||
int type;
|
||||
int want;
|
||||
int valid;
|
||||
uint32_t key;
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
int32_t h;
|
||||
};
|
||||
|
||||
struct Server
|
||||
{
|
||||
pthread_t thread;
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t changed;
|
||||
int listenfd;
|
||||
int wake[2];
|
||||
int stop;
|
||||
int old;
|
||||
int eaten;
|
||||
int stall;
|
||||
int response;
|
||||
int nclose;
|
||||
char pre[Ipcfieldmax+1];
|
||||
Event ev[Maxevent];
|
||||
int nev;
|
||||
};
|
||||
|
||||
struct Siglog
|
||||
{
|
||||
char event[128];
|
||||
int n;
|
||||
char pre[Ipcfieldmax+1];
|
||||
char shown[Ipcfieldmax+1];
|
||||
char commit[Ipcfieldmax+1];
|
||||
int prechanged;
|
||||
};
|
||||
|
||||
static void
|
||||
record(Server *s, Event *e)
|
||||
{
|
||||
pthread_mutex_lock(&s->lock);
|
||||
if(s->nev < Maxevent)
|
||||
s->ev[s->nev++] = *e;
|
||||
pthread_cond_broadcast(&s->changed);
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
closed(Server *s)
|
||||
{
|
||||
pthread_mutex_lock(&s->lock);
|
||||
s->nclose++;
|
||||
pthread_cond_broadcast(&s->changed);
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
}
|
||||
|
||||
static int
|
||||
sendresponse(Server *s, int fd, int eaten, int want, int key)
|
||||
{
|
||||
unsigned char buf[Ipcmaxresp];
|
||||
const char *commit;
|
||||
char pre[Ipcfieldmax+1];
|
||||
int n, response, stall;
|
||||
size_t ncommit, npreedit;
|
||||
|
||||
pthread_mutex_lock(&s->lock);
|
||||
memcpy(pre, s->pre, sizeof pre);
|
||||
response = s->response;
|
||||
stall = s->stall;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
if(stall)
|
||||
return 1;
|
||||
commit = "";
|
||||
ncommit = 0;
|
||||
npreedit = strlen(pre);
|
||||
if(key && response == Rcommitpre){
|
||||
commit = pre;
|
||||
ncommit = npreedit;
|
||||
}else if(key && response == Rbadcommit){
|
||||
commit = "\xc3(";
|
||||
ncommit = 2;
|
||||
}else if(key && response == Rbadpreedit){
|
||||
pre[0] = 'x';
|
||||
pre[1] = 0;
|
||||
pre[2] = 'y';
|
||||
npreedit = 3;
|
||||
}
|
||||
n = ipcpackresp(buf, sizeof buf, eaten, commit, ncommit,
|
||||
pre, npreedit, want);
|
||||
return n >= 0 && ipcsend(fd, buf, n) == 0;
|
||||
}
|
||||
|
||||
static int
|
||||
request(Server *s, int fd)
|
||||
{
|
||||
unsigned char buf[Ipccaretsz];
|
||||
uint32_t mod, key;
|
||||
Event e;
|
||||
int old, type, want;
|
||||
|
||||
if(ipcreadn(fd, buf, Ipcreqsz) < 0)
|
||||
return 0;
|
||||
type = ipcreqtype(buf);
|
||||
memset(&e, 0, sizeof e);
|
||||
switch(type){
|
||||
case Ipccap:
|
||||
e.type = Ecap;
|
||||
ipcunpackreq(buf, &e.want, &mod, &key);
|
||||
record(s, &e);
|
||||
pthread_mutex_lock(&s->lock);
|
||||
old = s->old;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return sendresponse(s, fd, !old, e.want, 0);
|
||||
case Ipccaret:
|
||||
if(ipcreadn(fd, buf + Ipcreqsz, Ipccaretsz - Ipcreqsz) < 0 ||
|
||||
ipcunpackcaret(buf, &e.valid, &e.x, &e.y, &e.h) < 0)
|
||||
return 0;
|
||||
e.type = Ecaret;
|
||||
record(s, &e);
|
||||
return 1;
|
||||
case Ipckey:
|
||||
ipcunpackreq(buf, &want, &mod, &key);
|
||||
e.want = want;
|
||||
e.key = key;
|
||||
e.type = ipcreqreset(buf) ? Ereset : Ekey;
|
||||
record(s, &e);
|
||||
pthread_mutex_lock(&s->lock);
|
||||
old = s->eaten;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return sendresponse(s, fd, e.type == Ekey && old, want,
|
||||
e.type == Ekey);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void*
|
||||
serverthread(void *v)
|
||||
{
|
||||
Server *s;
|
||||
struct pollfd pfd[3];
|
||||
int fd, n;
|
||||
char c;
|
||||
|
||||
s = v;
|
||||
fd = -1;
|
||||
for(;;){
|
||||
pfd[0].fd = s->wake[0];
|
||||
pfd[0].events = POLLIN;
|
||||
pfd[1].fd = s->listenfd;
|
||||
pfd[1].events = fd < 0 ? POLLIN : 0;
|
||||
pfd[2].fd = fd;
|
||||
pfd[2].events = fd >= 0 ? POLLIN : 0;
|
||||
n = poll(pfd, 3, -1);
|
||||
if(n < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(n < 0 || (pfd[0].revents & POLLIN))
|
||||
break;
|
||||
if(pfd[1].revents & POLLIN){
|
||||
fd = accept(s->listenfd, NULL, NULL);
|
||||
continue;
|
||||
}
|
||||
if(fd >= 0 && pfd[2].revents != 0){
|
||||
if((pfd[2].revents & POLLIN) && request(s, fd))
|
||||
continue;
|
||||
close(fd);
|
||||
fd = -1;
|
||||
closed(s);
|
||||
}
|
||||
}
|
||||
if(fd >= 0){
|
||||
close(fd);
|
||||
closed(s);
|
||||
}
|
||||
while(read(s->wake[0], &c, 1) < 0 && errno == EINTR)
|
||||
;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
startserver(Server *s, char *path)
|
||||
{
|
||||
struct sockaddr_un addr;
|
||||
int cond;
|
||||
|
||||
memset(s, 0, sizeof *s);
|
||||
s->eaten = 1;
|
||||
s->listenfd = -1;
|
||||
s->wake[0] = s->wake[1] = -1;
|
||||
if(pthread_mutex_init(&s->lock, NULL) != 0)
|
||||
return fail("initialize server lock");
|
||||
cond = pthread_cond_init(&s->changed, NULL) == 0;
|
||||
if(!cond){
|
||||
fail("initialize server condition");
|
||||
goto bad;
|
||||
}
|
||||
if(pipe(s->wake) < 0){
|
||||
fail("pipe: %s", strerror(errno));
|
||||
goto bad;
|
||||
}
|
||||
s->listenfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if(s->listenfd < 0){
|
||||
fail("socket: %s", strerror(errno));
|
||||
goto bad;
|
||||
}
|
||||
memset(&addr, 0, sizeof addr);
|
||||
addr.sun_family = AF_UNIX;
|
||||
if(strlen(path) >= sizeof addr.sun_path){
|
||||
fail("socket path is too long");
|
||||
goto bad;
|
||||
}
|
||||
strcpy(addr.sun_path, path);
|
||||
if(bind(s->listenfd, (struct sockaddr*)&addr, sizeof addr) < 0 ||
|
||||
listen(s->listenfd, 4) < 0){
|
||||
fail("listen %s: %s", path, strerror(errno));
|
||||
goto bad;
|
||||
}
|
||||
if(pthread_create(&s->thread, NULL, serverthread, s) != 0){
|
||||
fail("create server thread");
|
||||
goto bad;
|
||||
}
|
||||
return 1;
|
||||
bad:
|
||||
if(s->listenfd >= 0)
|
||||
close(s->listenfd);
|
||||
if(s->wake[0] >= 0)
|
||||
close(s->wake[0]);
|
||||
if(s->wake[1] >= 0)
|
||||
close(s->wake[1]);
|
||||
if(cond)
|
||||
pthread_cond_destroy(&s->changed);
|
||||
pthread_mutex_destroy(&s->lock);
|
||||
s->listenfd = s->wake[0] = s->wake[1] = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
stopserver(Server *s)
|
||||
{
|
||||
char c;
|
||||
|
||||
if(s->wake[1] >= 0){
|
||||
c = 0;
|
||||
while(write(s->wake[1], &c, 1) < 0 && errno == EINTR)
|
||||
;
|
||||
pthread_join(s->thread, NULL);
|
||||
}
|
||||
if(s->listenfd >= 0)
|
||||
close(s->listenfd);
|
||||
if(s->wake[0] >= 0)
|
||||
close(s->wake[0]);
|
||||
if(s->wake[1] >= 0)
|
||||
close(s->wake[1]);
|
||||
pthread_cond_destroy(&s->changed);
|
||||
pthread_mutex_destroy(&s->lock);
|
||||
s->listenfd = s->wake[0] = s->wake[1] = -1;
|
||||
}
|
||||
|
||||
static void
|
||||
setpre(Server *s, char *pre)
|
||||
{
|
||||
pthread_mutex_lock(&s->lock);
|
||||
snprintf(s->pre, sizeof s->pre, "%s", pre);
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
setold(Server *s, int old)
|
||||
{
|
||||
pthread_mutex_lock(&s->lock);
|
||||
s->old = old;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
setreply(Server *s, int eaten, int response, int stall)
|
||||
{
|
||||
pthread_mutex_lock(&s->lock);
|
||||
s->eaten = eaten;
|
||||
s->response = response;
|
||||
s->stall = stall;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
}
|
||||
|
||||
static int
|
||||
eventcount(Server *s)
|
||||
{
|
||||
int n;
|
||||
|
||||
pthread_mutex_lock(&s->lock);
|
||||
n = s->nev;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return n;
|
||||
}
|
||||
|
||||
static int
|
||||
closecount(Server *s)
|
||||
{
|
||||
int n;
|
||||
|
||||
pthread_mutex_lock(&s->lock);
|
||||
n = s->nclose;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return n;
|
||||
}
|
||||
|
||||
static int
|
||||
waitcount(Server *s, int n)
|
||||
{
|
||||
struct timespec ts;
|
||||
int r;
|
||||
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += Timeout / 1000;
|
||||
ts.tv_nsec += (Timeout % 1000) * 1000000;
|
||||
if(ts.tv_nsec >= 1000000000){
|
||||
ts.tv_sec++;
|
||||
ts.tv_nsec -= 1000000000;
|
||||
}
|
||||
pthread_mutex_lock(&s->lock);
|
||||
while(s->nev < n){
|
||||
r = pthread_cond_timedwait(&s->changed, &s->lock, &ts);
|
||||
if(r == ETIMEDOUT)
|
||||
break;
|
||||
}
|
||||
r = s->nev >= n;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int
|
||||
waitclose(Server *s, int n)
|
||||
{
|
||||
struct timespec ts;
|
||||
int r;
|
||||
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += Timeout / 1000;
|
||||
pthread_mutex_lock(&s->lock);
|
||||
while(s->nclose < n){
|
||||
r = pthread_cond_timedwait(&s->changed, &s->lock, &ts);
|
||||
if(r == ETIMEDOUT)
|
||||
break;
|
||||
}
|
||||
r = s->nclose >= n;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
static Event
|
||||
getevent(Server *s, int n)
|
||||
{
|
||||
Event e;
|
||||
|
||||
memset(&e, 0, sizeof e);
|
||||
pthread_mutex_lock(&s->lock);
|
||||
if(n >= 0 && n < s->nev)
|
||||
e = s->ev[n];
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return e;
|
||||
}
|
||||
|
||||
static int
|
||||
runquery(char *module, char *cache)
|
||||
{
|
||||
int fd, status;
|
||||
pid_t pid;
|
||||
|
||||
fd = open(cache, O_WRONLY|O_CREAT|O_TRUNC, 0600);
|
||||
if(fd < 0)
|
||||
return fail("open module cache: %s", strerror(errno));
|
||||
pid = fork();
|
||||
if(pid < 0){
|
||||
close(fd);
|
||||
return fail("fork gtk-query-immodules: %s", strerror(errno));
|
||||
}
|
||||
if(pid == 0){
|
||||
dup2(fd, STDOUT_FILENO);
|
||||
close(fd);
|
||||
execlp("gtk-query-immodules-3.0", "gtk-query-immodules-3.0",
|
||||
module, (char*)NULL);
|
||||
_exit(127);
|
||||
}
|
||||
close(fd);
|
||||
if(waitpid(pid, &status, 0) != pid || !WIFEXITED(status) ||
|
||||
WEXITSTATUS(status) != 0)
|
||||
return fail("gtk-query-immodules-3.0 failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
startxvfb(pid_t *pidp, char *display, size_t ndisplay)
|
||||
{
|
||||
struct pollfd pfd;
|
||||
char fdarg[32], line[32];
|
||||
int p[2], n, status;
|
||||
pid_t pid;
|
||||
|
||||
if(pipe(p) < 0)
|
||||
return fail("Xvfb display pipe: %s", strerror(errno));
|
||||
pid = fork();
|
||||
if(pid < 0){
|
||||
close(p[0]);
|
||||
close(p[1]);
|
||||
return fail("fork Xvfb: %s", strerror(errno));
|
||||
}
|
||||
if(pid == 0){
|
||||
close(p[0]);
|
||||
snprintf(fdarg, sizeof fdarg, "%d", p[1]);
|
||||
execlp("Xvfb", "Xvfb", "-displayfd", fdarg, "-screen", "0",
|
||||
"1024x768x24", "-nolisten", "tcp", "-noreset", (char*)NULL);
|
||||
_exit(127);
|
||||
}
|
||||
close(p[1]);
|
||||
pfd.fd = p[0];
|
||||
pfd.events = POLLIN;
|
||||
if(poll(&pfd, 1, Timeout) <= 0){
|
||||
kill(pid, SIGTERM);
|
||||
waitpid(pid, NULL, 0);
|
||||
close(p[0]);
|
||||
return fail("Xvfb did not publish a display");
|
||||
}
|
||||
n = read(p[0], line, sizeof line - 1);
|
||||
close(p[0]);
|
||||
if(n <= 0){
|
||||
waitpid(pid, &status, 0);
|
||||
return fail("read Xvfb display number");
|
||||
}
|
||||
line[n] = '\0';
|
||||
line[strcspn(line, "\r\n")] = '\0';
|
||||
if(snprintf(display, ndisplay, ":%s", line) >= (int)ndisplay){
|
||||
kill(pid, SIGTERM);
|
||||
waitpid(pid, NULL, 0);
|
||||
return fail("Xvfb display number is too long");
|
||||
}
|
||||
*pidp = pid;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
stopxvfb(pid_t *pidp)
|
||||
{
|
||||
int status;
|
||||
int64_t deadline;
|
||||
|
||||
if(*pidp <= 0)
|
||||
return;
|
||||
kill(*pidp, SIGTERM);
|
||||
deadline = nowms() + Timeout;
|
||||
while(waitpid(*pidp, &status, WNOHANG) == 0 && leftms(deadline) > 0)
|
||||
g_usleep(10000);
|
||||
if(waitpid(*pidp, &status, WNOHANG) == 0){
|
||||
kill(*pidp, SIGKILL);
|
||||
waitpid(*pidp, &status, 0);
|
||||
}
|
||||
*pidp = -1;
|
||||
}
|
||||
|
||||
static void
|
||||
pump(void)
|
||||
{
|
||||
while(gtk_events_pending())
|
||||
gtk_main_iteration();
|
||||
}
|
||||
|
||||
static void
|
||||
logsig(Siglog *l, char c)
|
||||
{
|
||||
if(l->n + 1 < (int)sizeof l->event){
|
||||
l->event[l->n++] = c;
|
||||
l->event[l->n] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
prestart(GtkIMContext *ctx, Siglog *l)
|
||||
{
|
||||
(void)ctx;
|
||||
logsig(l, 'S');
|
||||
}
|
||||
|
||||
static void
|
||||
prechange(GtkIMContext *ctx, Siglog *l)
|
||||
{
|
||||
gchar *s;
|
||||
|
||||
gtk_im_context_get_preedit_string(ctx, &s, NULL, NULL);
|
||||
if(strcmp(l->pre, s) != 0)
|
||||
l->prechanged = 1;
|
||||
snprintf(l->pre, sizeof l->pre, "%s", s);
|
||||
g_free(s);
|
||||
logsig(l, 'C');
|
||||
}
|
||||
|
||||
static void
|
||||
preend(GtkIMContext *ctx, Siglog *l)
|
||||
{
|
||||
(void)ctx;
|
||||
logsig(l, 'E');
|
||||
}
|
||||
|
||||
static void
|
||||
commit(GtkIMContext *ctx, gchar *s, Siglog *l)
|
||||
{
|
||||
(void)ctx;
|
||||
snprintf(l->commit, sizeof l->commit, "%s", s);
|
||||
logsig(l, 'K');
|
||||
}
|
||||
|
||||
static void
|
||||
clearevent(Siglog *l)
|
||||
{
|
||||
l->event[0] = '\0';
|
||||
l->n = 0;
|
||||
l->commit[0] = '\0';
|
||||
l->prechanged = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
applyclient(Siglog *l)
|
||||
{
|
||||
if(l->commit[0] != '\0')
|
||||
l->shown[0] = '\0';
|
||||
if(l->prechanged)
|
||||
snprintf(l->shown, sizeof l->shown, "%s", l->pre);
|
||||
}
|
||||
|
||||
static void
|
||||
clearlog(Siglog *l)
|
||||
{
|
||||
memset(l, 0, sizeof *l);
|
||||
}
|
||||
|
||||
static GtkIMContext*
|
||||
newcontext(Siglog *l)
|
||||
{
|
||||
GtkIMContext *ctx;
|
||||
|
||||
ctx = gtk_im_multicontext_new();
|
||||
gtk_im_multicontext_set_context_id(GTK_IM_MULTICONTEXT(ctx), "strans");
|
||||
g_signal_connect(ctx, "preedit-start", G_CALLBACK(prestart), l);
|
||||
g_signal_connect(ctx, "preedit-changed", G_CALLBACK(prechange), l);
|
||||
g_signal_connect(ctx, "preedit-end", G_CALLBACK(preend), l);
|
||||
g_signal_connect(ctx, "commit", G_CALLBACK(commit), l);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static int
|
||||
keyevent(GtkIMContext *ctx, GdkWindow *win, GdkEventType type,
|
||||
guint keyval, guint state)
|
||||
{
|
||||
GdkEvent *ev;
|
||||
int r;
|
||||
|
||||
ev = gdk_event_new(type);
|
||||
ev->key.window = g_object_ref(win);
|
||||
ev->key.send_event = TRUE;
|
||||
ev->key.time = GDK_CURRENT_TIME;
|
||||
ev->key.keyval = keyval;
|
||||
ev->key.state = state;
|
||||
r = gtk_im_context_filter_keypress(ctx, &ev->key);
|
||||
gdk_event_free(ev);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int
|
||||
keystate(GtkIMContext *ctx, GdkWindow *win, guint keyval, guint state)
|
||||
{
|
||||
return keyevent(ctx, win, GDK_KEY_PRESS, keyval, state);
|
||||
}
|
||||
|
||||
static int
|
||||
key(GtkIMContext *ctx, GdkWindow *win, guint keyval)
|
||||
{
|
||||
return keystate(ctx, win, keyval, 0);
|
||||
}
|
||||
|
||||
static GtkWidget*
|
||||
newwindow(int x, int y)
|
||||
{
|
||||
GtkWidget *w;
|
||||
|
||||
w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_default_size(GTK_WINDOW(w), 240, 120);
|
||||
gtk_window_move(GTK_WINDOW(w), x, y);
|
||||
gtk_widget_show(w);
|
||||
pump();
|
||||
return w;
|
||||
}
|
||||
|
||||
#define Check(c, ...) do{ if(!(c)){ fail(__VA_ARGS__); goto out; } }while(0)
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
Server srv;
|
||||
Siglog log;
|
||||
GtkIMContext *ctx, *oldctx, *badctx;
|
||||
GtkWidget *top;
|
||||
GdkWindow *win;
|
||||
GdkRectangle rect;
|
||||
Event e;
|
||||
static const GtkInputPurpose purposes[] = {
|
||||
GTK_INPUT_PURPOSE_PASSWORD,
|
||||
GTK_INPUT_PURPOSE_PIN,
|
||||
};
|
||||
static const int badresponses[] = { Rbadcommit, Rbadpreedit };
|
||||
char root[] = "/tmp/strans-gtk.XXXXXX";
|
||||
char runtime[320] = "", socket[384] = "", cache[384] = "";
|
||||
char display[32];
|
||||
char *module;
|
||||
pid_t xvfb;
|
||||
int first, i, ox, oy, scale, closes, ok, rootmade, serverup;
|
||||
int64_t stalledms, started;
|
||||
|
||||
testname = "gtk_live_test";
|
||||
ctx = oldctx = badctx = NULL;
|
||||
top = NULL;
|
||||
xvfb = -1;
|
||||
rootmade = 0;
|
||||
serverup = 0;
|
||||
ok = 0;
|
||||
if(argc != 2){
|
||||
fail("usage: gtk_live_test /path/to/im-strans.so");
|
||||
return 1;
|
||||
}
|
||||
module = realpath(argv[1], NULL);
|
||||
if(module == NULL){
|
||||
fail("realpath %s: %s", argv[1], strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
Check(mkdtemp(root) != NULL, "mkdtemp: %s", strerror(errno));
|
||||
rootmade = 1;
|
||||
Check(snprintf(runtime, sizeof runtime, "%s/runtime", root) <
|
||||
(int)sizeof runtime, "runtime path is too long");
|
||||
Check(snprintf(socket, sizeof socket, "%s/strans.sock", runtime) <
|
||||
(int)sizeof socket, "socket path is too long");
|
||||
Check(snprintf(cache, sizeof cache, "%s/immodules.cache", root) <
|
||||
(int)sizeof cache, "cache path is too long");
|
||||
Check(mkdir(runtime, 0700) == 0, "mkdir runtime: %s", strerror(errno));
|
||||
Check(startxvfb(&xvfb, display, sizeof display), "start private Xvfb");
|
||||
Check(setenv("DISPLAY", display, 1) == 0 &&
|
||||
setenv("GDK_BACKEND", "x11", 1) == 0 &&
|
||||
setenv("GDK_SCALE", "2", 1) == 0 &&
|
||||
setenv("XDG_RUNTIME_DIR", runtime, 1) == 0,
|
||||
"set private environment: %s", strerror(errno));
|
||||
Check(runquery(module, cache), "generate GTK module cache");
|
||||
Check(setenv("GTK_IM_MODULE_FILE", cache, 1) == 0 &&
|
||||
setenv("GTK_IM_MODULE", "strans", 1) == 0,
|
||||
"set GTK module environment: %s", strerror(errno));
|
||||
Check(startserver(&srv, socket), "start fake daemon");
|
||||
serverup = 1;
|
||||
gtk_init(&argc, &argv);
|
||||
|
||||
clearlog(&log);
|
||||
ctx = newcontext(&log);
|
||||
top = newwindow(73, 41);
|
||||
win = gtk_widget_get_window(top);
|
||||
gtk_im_context_set_client_window(ctx, win);
|
||||
rect.x = 11;
|
||||
rect.y = 17;
|
||||
rect.width = 3;
|
||||
rect.height = 19;
|
||||
gtk_im_context_set_cursor_location(ctx, &rect);
|
||||
Check(eventcount(&srv) == 0, "cursor reporting opened the socket");
|
||||
setpre(&srv, "");
|
||||
Check(key(ctx, win, GDK_KEY_a), "initial key was not eaten");
|
||||
Check(waitcount(&srv, 3), "initial protocol frames timed out");
|
||||
e = getevent(&srv, 0);
|
||||
Check(e.type == Ecap && e.want == 1, "default preedit capability missing");
|
||||
e = getevent(&srv, 1);
|
||||
Check(e.type == Ecaret && e.valid, "cached caret was not negotiated");
|
||||
gdk_window_get_origin(win, &ox, &oy);
|
||||
Check(ox != 0 && oy != 0, "test window did not get a nonzero root origin");
|
||||
scale = gdk_window_get_scale_factor(win);
|
||||
Check(scale == 2, "private Xvfb did not honor GDK_SCALE=2");
|
||||
Check(e.x == (ox + rect.x) * scale &&
|
||||
e.y == (oy + rect.y) * scale && e.h == rect.height * scale,
|
||||
"caret conversion got %d,%d,%d want %d,%d,%d",
|
||||
e.x, e.y, e.h, (ox + rect.x) * scale,
|
||||
(oy + rect.y) * scale, rect.height * scale);
|
||||
e = getevent(&srv, 2);
|
||||
Check(e.type == Ekey && e.want == 1, "initial key framing changed");
|
||||
Check(log.n == 0, "initial empty preedit emitted %s", log.event);
|
||||
setpre(&srv, "ㅋ");
|
||||
clearlog(&log);
|
||||
Check(key(ctx, win, GDK_KEY_z), "first preedit key was not eaten");
|
||||
applyclient(&log);
|
||||
Check(strcmp(log.event, "SC") == 0 && strcmp(log.pre, "ㅋ") == 0 &&
|
||||
strcmp(log.shown, "ㅋ") == 0,
|
||||
"first preedit signals were %s (preedit %s, shown %s)",
|
||||
log.event, log.pre, log.shown);
|
||||
clearevent(&log);
|
||||
setreply(&srv, 1, Rcommitpre, 0);
|
||||
Check(key(ctx, win, GDK_KEY_z), "repeated preedit key was not eaten");
|
||||
applyclient(&log);
|
||||
Check(strcmp(log.event, "CEKSC") == 0 &&
|
||||
strcmp(log.commit, "ㅋ") == 0 && strcmp(log.pre, "ㅋ") == 0 &&
|
||||
strcmp(log.shown, "ㅋ") == 0,
|
||||
"repeated preedit signals were %s (commit %s, preedit %s, shown %s)",
|
||||
log.event, log.commit, log.pre, log.shown);
|
||||
Check(g_utf8_strlen(log.commit, -1) + g_utf8_strlen(log.shown, -1) == 2,
|
||||
"two physical keys produced commit %s and shown preedit %s",
|
||||
log.commit, log.shown);
|
||||
|
||||
/* Unconsumed input uses GtkIMContextSimple, including its stateful forms. */
|
||||
setpre(&srv, "");
|
||||
setreply(&srv, 0, Rnormal, 0);
|
||||
clearlog(&log);
|
||||
Check(key(ctx, win, GDK_KEY_Multi_key) &&
|
||||
key(ctx, win, GDK_KEY_apostrophe) && key(ctx, win, GDK_KEY_e),
|
||||
"Compose sequence was not consumed");
|
||||
Check(strcmp(log.commit, "é") == 0, "Compose committed %s", log.commit);
|
||||
clearlog(&log);
|
||||
Check(key(ctx, win, GDK_KEY_dead_acute) && key(ctx, win, GDK_KEY_e),
|
||||
"dead-key sequence was not consumed");
|
||||
Check(strcmp(log.commit, "é") == 0, "dead key committed %s", log.commit);
|
||||
clearlog(&log);
|
||||
Check(keystate(ctx, win, GDK_KEY_U, GDK_CONTROL_MASK|GDK_SHIFT_MASK),
|
||||
"Unicode prefix was not consumed");
|
||||
(void)keyevent(ctx, win, GDK_KEY_RELEASE, GDK_KEY_U,
|
||||
GDK_CONTROL_MASK|GDK_SHIFT_MASK);
|
||||
(void)keyevent(ctx, win, GDK_KEY_RELEASE, GDK_KEY_Shift_L,
|
||||
GDK_CONTROL_MASK);
|
||||
(void)keyevent(ctx, win, GDK_KEY_RELEASE, GDK_KEY_Control_L, 0);
|
||||
Check(key(ctx, win, GDK_KEY_0), "Unicode digit 1 was not consumed");
|
||||
Check(key(ctx, win, GDK_KEY_0), "Unicode digit 2 was not consumed");
|
||||
Check(key(ctx, win, GDK_KEY_e), "Unicode digit 3 was not consumed");
|
||||
Check(key(ctx, win, GDK_KEY_9), "Unicode digit 4 was not consumed");
|
||||
(void)key(ctx, win, GDK_KEY_Return);
|
||||
Check(strcmp(log.commit, "é") == 0, "Unicode entry committed %s", log.commit);
|
||||
setreply(&srv, 1, Rnormal, 0);
|
||||
|
||||
/* PASSWORD and PIN transitions reset an existing composition. */
|
||||
rect.height = 12;
|
||||
gtk_im_context_set_cursor_location(ctx, &rect);
|
||||
for(i = 0; i < (int)(sizeof purposes / sizeof purposes[0]); i++){
|
||||
g_object_set(ctx, "input-purpose", GTK_INPUT_PURPOSE_FREE_FORM, NULL);
|
||||
setpre(&srv, i == 0 ? "password" : "pin");
|
||||
clearlog(&log);
|
||||
Check(key(ctx, win, GDK_KEY_c + i), "private setup key was not eaten");
|
||||
clearlog(&log);
|
||||
first = eventcount(&srv);
|
||||
g_object_set(ctx, "input-purpose", purposes[i], NULL);
|
||||
Check(waitcount(&srv, first + 1), "private reset timed out");
|
||||
Check(getevent(&srv, first).type == Ereset &&
|
||||
strcmp(log.event, "CE") == 0,
|
||||
"private transition did not reset and clear");
|
||||
clearlog(&log);
|
||||
first = eventcount(&srv);
|
||||
Check(key(ctx, win, GDK_KEY_x), "private fallback did not commit");
|
||||
Check(eventcount(&srv) == first && strcmp(log.commit, "x") == 0,
|
||||
"private key reached daemon or did not commit");
|
||||
}
|
||||
|
||||
g_object_set(ctx, "input-purpose", GTK_INPUT_PURPOSE_FREE_FORM, NULL);
|
||||
setpre(&srv, "reset");
|
||||
clearlog(&log);
|
||||
Check(key(ctx, win, GDK_KEY_e), "reset setup key was not eaten");
|
||||
clearlog(&log);
|
||||
first = eventcount(&srv);
|
||||
gtk_im_context_reset(ctx);
|
||||
Check(waitcount(&srv, first + 1), "explicit reset timed out");
|
||||
Check(getevent(&srv, first).type == Ereset && strcmp(log.event, "CE") == 0,
|
||||
"explicit reset did not clear preedit");
|
||||
setpre(&srv, "focus");
|
||||
clearlog(&log);
|
||||
Check(key(ctx, win, GDK_KEY_f), "focus setup key was not eaten");
|
||||
clearlog(&log);
|
||||
first = eventcount(&srv);
|
||||
closes = closecount(&srv);
|
||||
gtk_im_context_focus_out(ctx);
|
||||
Check(waitclose(&srv, closes + 1), "focus-out did not close connection");
|
||||
Check(eventcount(&srv) == first + 1 &&
|
||||
getevent(&srv, first).type == Ereset,
|
||||
"focus-out did not hand the pending text back before closing");
|
||||
Check(strcmp(log.event, "CE") == 0, "focus-out clear order was %s", log.event);
|
||||
|
||||
/* Dispose is repeatable, closes the connection, and emits no preedit. */
|
||||
gtk_im_context_set_client_window(ctx, win);
|
||||
setpre(&srv, "final");
|
||||
clearlog(&log);
|
||||
Check(key(ctx, win, GDK_KEY_g), "dispose setup key was not eaten");
|
||||
clearlog(&log);
|
||||
closes = closecount(&srv);
|
||||
g_object_run_dispose(G_OBJECT(ctx));
|
||||
g_object_run_dispose(G_OBJECT(ctx));
|
||||
g_object_unref(ctx);
|
||||
ctx = NULL;
|
||||
Check(waitclose(&srv, closes + 1), "dispose did not close connection");
|
||||
Check(log.n == 0, "dispose emitted %s", log.event);
|
||||
|
||||
/* Malformed commit and preedit fields close the protocol connection. */
|
||||
for(i = 0; i < (int)(sizeof badresponses / sizeof badresponses[0]); i++){
|
||||
setreply(&srv, 1, badresponses[i], 0);
|
||||
clearlog(&log);
|
||||
badctx = newcontext(&log);
|
||||
gtk_im_context_set_client_window(badctx, win);
|
||||
closes = closecount(&srv);
|
||||
Check(key(badctx, win, GDK_KEY_m + i),
|
||||
"malformed response did not fall back");
|
||||
Check(waitclose(&srv, closes + 1),
|
||||
"malformed response did not close connection");
|
||||
Check(log.commit[0] == 'm' + i && log.commit[1] == '\0',
|
||||
"malformed response fallback committed %s", log.commit);
|
||||
g_object_unref(badctx);
|
||||
badctx = NULL;
|
||||
}
|
||||
|
||||
/* A peer which accepts a connection but never replies cannot freeze GTK. */
|
||||
setreply(&srv, 1, Rnormal, 1);
|
||||
clearlog(&log);
|
||||
badctx = newcontext(&log);
|
||||
closes = closecount(&srv);
|
||||
started = nowms();
|
||||
Check(key(badctx, win, GDK_KEY_t), "stalled peer did not fall back");
|
||||
stalledms = nowms() - started;
|
||||
Check(stalledms < 4*Ipcwaitms, "stalled peer blocked GTK");
|
||||
Check(waitclose(&srv, closes + 1), "stalled connection was not closed");
|
||||
Check(strcmp(log.commit, "t") == 0, "stalled fallback committed %s",
|
||||
log.commit);
|
||||
g_object_unref(badctx);
|
||||
badctx = NULL;
|
||||
setreply(&srv, 1, Rnormal, 0);
|
||||
|
||||
/* An old daemon still honors set_use_preedit(FALSE). */
|
||||
setold(&srv, 1);
|
||||
clearlog(&log);
|
||||
oldctx = newcontext(&log);
|
||||
gtk_im_context_set_client_window(oldctx, win);
|
||||
rect.x = 2;
|
||||
rect.y = 3;
|
||||
rect.height = 10;
|
||||
gtk_im_context_set_cursor_location(oldctx, &rect);
|
||||
gtk_im_context_set_use_preedit(oldctx, FALSE);
|
||||
setpre(&srv, "legacy");
|
||||
first = eventcount(&srv);
|
||||
Check(key(oldctx, win, GDK_KEY_h), "old-daemon key was not eaten");
|
||||
Check(waitcount(&srv, first + 2), "old-daemon exchange timed out");
|
||||
e = getevent(&srv, first);
|
||||
Check(e.type == Ecap && e.want == 0, "old-daemon probe framing is wrong");
|
||||
e = getevent(&srv, first + 1);
|
||||
Check(e.type == Ekey && e.want == 0, "old daemon forced inline preedit");
|
||||
for(i = first; i < eventcount(&srv); i++)
|
||||
Check(getevent(&srv, i).type != Ecaret,
|
||||
"caret extension sent to old daemon");
|
||||
Check(log.n == 0 && log.pre[0] == '\0',
|
||||
"old daemon emitted inline preedit %s (%s)", log.event, log.pre);
|
||||
closes = closecount(&srv);
|
||||
g_object_unref(oldctx);
|
||||
oldctx = NULL;
|
||||
Check(waitclose(&srv, closes + 1), "old-daemon context did not close");
|
||||
|
||||
/* A failed first connection uses GtkIMContextSimple. */
|
||||
stopserver(&srv);
|
||||
serverup = 0;
|
||||
unlink(socket);
|
||||
clearlog(&log);
|
||||
badctx = newcontext(&log);
|
||||
Check(key(badctx, win, GDK_KEY_z), "connection failure did not fall back");
|
||||
Check(strcmp(log.commit, "z") == 0 && log.pre[0] == '\0',
|
||||
"connection failure left stale preedit");
|
||||
|
||||
ok = 1;
|
||||
out:
|
||||
if(badctx != NULL)
|
||||
g_object_unref(badctx);
|
||||
if(oldctx != NULL)
|
||||
g_object_unref(oldctx);
|
||||
if(ctx != NULL)
|
||||
g_object_unref(ctx);
|
||||
if(top != NULL)
|
||||
gtk_widget_destroy(top);
|
||||
pump();
|
||||
if(serverup)
|
||||
stopserver(&srv);
|
||||
if(socket[0] != '\0')
|
||||
unlink(socket);
|
||||
if(cache[0] != '\0')
|
||||
unlink(cache);
|
||||
if(runtime[0] != '\0')
|
||||
rmdir(runtime);
|
||||
if(rootmade)
|
||||
rmdir(root);
|
||||
stopxvfb(&xvfb);
|
||||
free(module);
|
||||
if(ok)
|
||||
printf("gtk_live_test: ok (scale %d, stalled peer %lld ms)\n",
|
||||
scale, (long long)stalledms);
|
||||
return !ok;
|
||||
}
|
||||
@@ -1,112 +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);
|
||||
}
|
||||
|
||||
void
|
||||
hmap_binary_keys_and_invalid_lengths(struct ct *t)
|
||||
{
|
||||
Hmap *h;
|
||||
Hnode *n;
|
||||
Str key, other;
|
||||
|
||||
h = hmapalloc(1);
|
||||
CT_CHECK(t, h != nil);
|
||||
key.n = 3;
|
||||
key.r[0] = 'a';
|
||||
key.r[1] = 0;
|
||||
key.r[2] = 'b';
|
||||
other = key;
|
||||
other.r[2] = 'c';
|
||||
hmapset(&h, &key, "one", 3);
|
||||
hmapset(&h, &other, "two", 3);
|
||||
n = hmapget(h, &key);
|
||||
if(CT_CHECK(t, n != nil)){
|
||||
CT_EQ_INT(t, 3, n->klen);
|
||||
CT_EQ_MEM(t, "one", n->val, n->vlen);
|
||||
}
|
||||
n = hmapget(h, &other);
|
||||
if(CT_CHECK(t, n != nil))
|
||||
CT_EQ_MEM(t, "two", n->val, n->vlen);
|
||||
hmapset(&h, &key, "bad", -1);
|
||||
hmapset(&h, &key, nil, 1);
|
||||
n = hmapget(h, &key);
|
||||
if(CT_CHECK(t, n != nil))
|
||||
CT_EQ_MEM(t, "one", n->val, n->vlen);
|
||||
CT_EQ_PTR(t, nil, hmapalloc(0));
|
||||
hmapfree(h);
|
||||
}
|
||||
293
tests/ibus_client_smoke.c
Normal file
293
tests/ibus_client_smoke.c
Normal file
@@ -0,0 +1,293 @@
|
||||
#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;
|
||||
char revent[16];
|
||||
int rn;
|
||||
int repeat;
|
||||
int rfirst;
|
||||
int rcommit;
|
||||
int rdone;
|
||||
int flushed;
|
||||
};
|
||||
|
||||
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->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(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 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;
|
||||
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 = 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);
|
||||
ok = ok && ibus_input_context_process_key_event(a, 's', 0,
|
||||
IBUS_CONTROL_MASK);
|
||||
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);
|
||||
if(log.legacy == 0 || log.modern != 0 || log.commit != 3 ||
|
||||
log.invalid || !log.done || !log.atext || !log.aclear ||
|
||||
log.clearctx != a || !log.rdone)
|
||||
ok = 0;
|
||||
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 repeat=%s flushed=%d\n",
|
||||
log.legacy, log.modern, log.commit, log.invalid,
|
||||
log.sawpreedit, log.sawcommit, log.atext, log.aclear,
|
||||
log.revent, log.flushed);
|
||||
return 1;
|
||||
}
|
||||
printf("official libibus client preedit, commit, owner clear, and focus-out hand-back: ok\n");
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,8 @@
|
||||
#include "../ibus.c"
|
||||
#include "../cutest/cutest.h"
|
||||
#include "ibus.c"
|
||||
#include "test.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
void testengineinit(int);
|
||||
void testenginehandle(Keyreq*);
|
||||
void* testengineowner(void);
|
||||
void testenginepreedit(Str*);
|
||||
void testenginecaret(Caret*);
|
||||
|
||||
enum
|
||||
{
|
||||
@@ -18,20 +13,17 @@ typedef struct Ibusfix Ibusfix;
|
||||
struct Ibusfix
|
||||
{
|
||||
Channel *oldreply;
|
||||
Channel *trace;
|
||||
Channel *stop;
|
||||
Channel *done;
|
||||
Pump pump;
|
||||
DBusConnection *c1;
|
||||
DBusConnection *c2;
|
||||
int pumpactive;
|
||||
};
|
||||
|
||||
void
|
||||
ibus_machine_id_fallback(struct ct *t)
|
||||
{
|
||||
char root[] = "/tmp/strans-machine-id.XXXXXX";
|
||||
char primary[128], fallback[128], got[64];
|
||||
char *path[2];
|
||||
char primary[128], fallback[128], override[128], got[512];
|
||||
char *path[2], *old, *saved;
|
||||
FILE *fp;
|
||||
int madeprimary, madefallback;
|
||||
|
||||
@@ -61,6 +53,24 @@ ibus_machine_id_fallback(struct ct *t)
|
||||
path[1] = fallback;
|
||||
machineidfiles(got, sizeof got, path, nelem(path));
|
||||
CT_EQ_STR(t, "fallback-machine-id", 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);
|
||||
@@ -85,6 +95,8 @@ ibus_startup_requires_ownership(struct ct *t)
|
||||
saved = old == nil ? nil : strdup(old);
|
||||
if(old != nil && !CT_CHECK(t, saved != nil))
|
||||
return;
|
||||
/* An IBus desktop names a live address file; the path must be ours. */
|
||||
unsetenv("IBUS_ADDRESS_FILE");
|
||||
if(!CT_CHECK(t, mkdtemp(root) != nil))
|
||||
goto cleanup;
|
||||
made = 1;
|
||||
@@ -146,32 +158,6 @@ cleanup:
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
enginepump(void *arg)
|
||||
{
|
||||
Ibusfix *f;
|
||||
Keyreq req;
|
||||
uchar token;
|
||||
Alt alts[] = {
|
||||
{keyc, &req, CHANRCV, nil},
|
||||
{nil, &token, CHANRCV, nil},
|
||||
{nil, nil, CHANEND, nil},
|
||||
};
|
||||
|
||||
f = arg;
|
||||
alts[1].c = f->stop;
|
||||
for(;;)
|
||||
switch(alt(alts)){
|
||||
case 0:
|
||||
chansend(f->trace, &req);
|
||||
testenginehandle(&req);
|
||||
break;
|
||||
case 1:
|
||||
chansend(f->done, &token);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
ibusbegin(struct ct *t, Ibusfix *f)
|
||||
{
|
||||
@@ -181,41 +167,32 @@ ibusbegin(struct ct *t, Ibusfix *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);
|
||||
f->trace = chancreate(sizeof(Keyreq), Maxcontexts);
|
||||
f->stop = chancreate(sizeof(uchar), 0);
|
||||
f->done = chancreate(sizeof(uchar), 0);
|
||||
f->pumpactive = threadcreate(enginepump, f, 8192) >= 0;
|
||||
pumpstart(&f->pump, Maxcontexts);
|
||||
f->c1 = malloc(1);
|
||||
f->c2 = malloc(1);
|
||||
return CT_CHECK(t, f->pumpactive && f->c1 != nil && f->c2 != nil);
|
||||
return CT_CHECK(t, f->c1 != nil && f->c2 != nil);
|
||||
}
|
||||
|
||||
static void
|
||||
ibusend(Ibusfix *f)
|
||||
{
|
||||
Drawcmd dc;
|
||||
Keyreq req;
|
||||
uchar token;
|
||||
int i;
|
||||
|
||||
if(f->pumpactive){
|
||||
for(i = 0; i < nelem(contexts); i++)
|
||||
if(contexts[i].conn != nil)
|
||||
dropcontext(&contexts[i]);
|
||||
while(channbrecv(f->trace, &req) > 0)
|
||||
;
|
||||
token = 0;
|
||||
chansend(f->stop, &token);
|
||||
chanrecv(f->done, &token);
|
||||
}
|
||||
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)
|
||||
;
|
||||
@@ -223,9 +200,6 @@ ibusend(Ibusfix *f)
|
||||
free(f->c2);
|
||||
chanfree(replyc);
|
||||
replyc = f->oldreply;
|
||||
chanfree(f->trace);
|
||||
chanfree(f->stop);
|
||||
chanfree(f->done);
|
||||
}
|
||||
|
||||
static Keyreq
|
||||
@@ -234,10 +208,12 @@ nexttrace(struct ct *t, Ibusfix *f, int op, Ictx *ctx)
|
||||
Keyreq req;
|
||||
|
||||
memset(&req, 0, sizeof req);
|
||||
if(!CT_CHECK(t, channbrecv(f->trace, &req) > 0))
|
||||
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, ctx->cap & 1 ? Cclientpreedit : 0, req.cap);
|
||||
CT_EQ_PTR(t, replyc, req.reply);
|
||||
return req;
|
||||
}
|
||||
@@ -247,27 +223,7 @@ notrace(struct ct *t, Ibusfix *f)
|
||||
{
|
||||
Keyreq req;
|
||||
|
||||
CT_CHECK(t, channbrecv(f->trace, &req) <= 0);
|
||||
}
|
||||
|
||||
static void
|
||||
checkpreedit(struct ct *t, char *want, Keyres *res)
|
||||
{
|
||||
char got[Maxutf];
|
||||
|
||||
stoutf(&res->preedit, got, sizeof got);
|
||||
CT_EQ_STR(t, want, got);
|
||||
}
|
||||
|
||||
static void
|
||||
checkenginepreedit(struct ct *t, char *want)
|
||||
{
|
||||
char got[Maxutf];
|
||||
Str preedit;
|
||||
|
||||
testenginepreedit(&preedit);
|
||||
stoutf(&preedit, got, sizeof got);
|
||||
CT_EQ_STR(t, want, got);
|
||||
CT_CHECK(t, channbrecv(f->pump.trace, &req) <= 0);
|
||||
}
|
||||
|
||||
static Keyres
|
||||
@@ -281,6 +237,108 @@ contextkey(struct ct *t, Ibusfix *f, Ictx *ctx, u32int sym, u32int state)
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
ibus_capability_policy(struct ct *t)
|
||||
{
|
||||
Ibusfix f;
|
||||
Ictx *a, *b;
|
||||
Keyres res;
|
||||
|
||||
if(!ibusbegin(t, &f))
|
||||
goto cleanup;
|
||||
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 = Cclientpreedit;
|
||||
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 = Cclientpreedit;
|
||||
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 },
|
||||
{ Ibuspurposepassword, 1 },
|
||||
{ Ibuspurposepin, 1 },
|
||||
{ 37, 0 },
|
||||
};
|
||||
Ibusfix f;
|
||||
Ictx *ctx;
|
||||
Keyres res;
|
||||
int i;
|
||||
|
||||
if(!ibusbegin(t, &f))
|
||||
goto cleanup;
|
||||
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 = Cclientpreedit;
|
||||
ctx->purpose = 0;
|
||||
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
||||
res = contextkey(t, &f, ctx, 'k', 0);
|
||||
checkstr(t, "preedit", "k", &res.preedit);
|
||||
ctx->purpose = Ibuspurposepassword;
|
||||
memset(&res, 0, sizeof res);
|
||||
CT_CHECK(t, !processkey(ctx, 'x', 0, &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)
|
||||
{
|
||||
@@ -306,7 +364,7 @@ ibus_context_lifecycle(struct ct *t)
|
||||
notrace(t, &f);
|
||||
CT_EQ_PTR(t, nil, testengineowner());
|
||||
|
||||
setcursor(a, 10, 20, 14);
|
||||
setcursor(a, -10, -20, 14);
|
||||
notrace(t, &f);
|
||||
CT_CHECK(t, a->caret.valid);
|
||||
a->focused = 1;
|
||||
@@ -321,23 +379,23 @@ ibus_context_lifecycle(struct ct *t)
|
||||
req = nexttrace(t, &f, Keypress, a);
|
||||
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, -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);
|
||||
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);
|
||||
checkpreedit(t, "か", &res);
|
||||
checkstr(t, "preedit", "か", &res.preedit);
|
||||
|
||||
b->focused = 1;
|
||||
res = contextkey(t, &f, b, 'n', 0);
|
||||
checkpreedit(t, "ん", &res);
|
||||
checkstr(t, "preedit", "ん", &res.preedit);
|
||||
CT_EQ_PTR(t, b, testengineowner());
|
||||
|
||||
setcursor(b, 50, 60, 12);
|
||||
@@ -351,7 +409,7 @@ ibus_context_lifecycle(struct ct *t)
|
||||
memset(&res, 0, sizeof res);
|
||||
sendrequest(a, Keyreset, 0, 0, &res);
|
||||
nexttrace(t, &f, Keyreset, a);
|
||||
checkpreedit(t, "", &res);
|
||||
checkstr(t, "preedit", "", &res.preedit);
|
||||
checkenginepreedit(t, "ん");
|
||||
CT_EQ_PTR(t, b, testengineowner());
|
||||
|
||||
@@ -367,11 +425,7 @@ ibus_context_lifecycle(struct ct *t)
|
||||
CT_CHECK(t, !a->caret.valid);
|
||||
checkenginepreedit(t, "ん");
|
||||
CT_EQ_PTR(t, b, testengineowner());
|
||||
releasecontext(a);
|
||||
notrace(t, &f);
|
||||
dropcontext(a);
|
||||
notrace(t, &f);
|
||||
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/one"));
|
||||
checkdrop(t, &f, a, "/context/one");
|
||||
|
||||
reused = newcontext(f.c1, "/context/stale-destroy");
|
||||
CT_EQ_PTR(t, a, reused);
|
||||
@@ -419,17 +473,17 @@ ibus_active_release_lifecycle(struct ct *t)
|
||||
ctx->focused = 1;
|
||||
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
||||
res = contextkey(t, &f, ctx, 'n', 0);
|
||||
checkpreedit(t, "ん", &res);
|
||||
checkstr(t, "preedit", "ん", &res.preedit);
|
||||
|
||||
memset(&res, 0, sizeof res);
|
||||
sendrequest(ctx, Keyreset, 0, 0, &res);
|
||||
nexttrace(t, &f, Keyreset, ctx);
|
||||
checkpreedit(t, "", &res);
|
||||
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);
|
||||
checkpreedit(t, "k", &res);
|
||||
checkstr(t, "preedit", "k", &res.preedit);
|
||||
CT_EQ_PTR(t, ctx, testengineowner());
|
||||
|
||||
releasecontext(ctx);
|
||||
@@ -437,11 +491,7 @@ ibus_active_release_lifecycle(struct ct *t)
|
||||
CT_CHECK(t, !ctx->focused);
|
||||
checkenginepreedit(t, "");
|
||||
CT_EQ_PTR(t, nil, testengineowner());
|
||||
releasecontext(ctx);
|
||||
notrace(t, &f);
|
||||
dropcontext(ctx);
|
||||
notrace(t, &f);
|
||||
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/reset"));
|
||||
checkdrop(t, &f, ctx, "/context/reset");
|
||||
|
||||
reused = newcontext(f.c1, "/context/destroy");
|
||||
CT_EQ_PTR(t, ctx, reused);
|
||||
|
||||
@@ -1,52 +1,25 @@
|
||||
#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/inotify.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 "ipc.h"
|
||||
#include "live.h"
|
||||
|
||||
enum
|
||||
{
|
||||
Maxclients = 64,
|
||||
Calltimeout = 4000,
|
||||
Starttimeout = 8000,
|
||||
Stoptimeout = 3000,
|
||||
Cappreedit = 1,
|
||||
};
|
||||
|
||||
typedef struct Daemon Daemon;
|
||||
typedef struct Response Response;
|
||||
|
||||
struct Daemon
|
||||
{
|
||||
pid_t pid;
|
||||
int errfd;
|
||||
char root[256];
|
||||
char runtime[320];
|
||||
char config[320];
|
||||
char ibus[384];
|
||||
char bus[448];
|
||||
char home[320];
|
||||
char socket[384];
|
||||
char err[4096];
|
||||
size_t nerr;
|
||||
};
|
||||
|
||||
struct Response
|
||||
{
|
||||
int eaten;
|
||||
@@ -54,485 +27,6 @@ struct Response
|
||||
char preedit[Ipcfieldmax+1];
|
||||
};
|
||||
|
||||
static int
|
||||
fail(char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
fprintf(stderr, "ipc_live_test: ");
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 int
|
||||
leftms(int64_t deadline)
|
||||
{
|
||||
int64_t n;
|
||||
|
||||
n = deadline - nowms();
|
||||
if(n <= 0)
|
||||
return 0;
|
||||
if(n > 0x7fffffff)
|
||||
return 0x7fffffff;
|
||||
return n;
|
||||
}
|
||||
|
||||
static int
|
||||
makedir(char *path)
|
||||
{
|
||||
if(mkdir(path, 0700) == 0)
|
||||
return 1;
|
||||
return fail("mkdir %s: %s", path, strerror(errno));
|
||||
}
|
||||
|
||||
static 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';
|
||||
}
|
||||
|
||||
static void
|
||||
showerrors(Daemon *d)
|
||||
{
|
||||
readerrors(d);
|
||||
if(d->nerr != 0)
|
||||
fprintf(stderr, "ipc_live_test: daemon stderr:\n%s", d->err);
|
||||
}
|
||||
|
||||
static int
|
||||
waitsocket(Daemon *d, int notifyfd)
|
||||
{
|
||||
struct pollfd pfd[2];
|
||||
struct stat st;
|
||||
char buf[4096];
|
||||
int n, status, timeout;
|
||||
int64_t deadline;
|
||||
|
||||
deadline = nowms() + Starttimeout;
|
||||
for(;;){
|
||||
if(lstat(d->socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
||||
(st.st_mode & 0777) == 0600)
|
||||
return 1;
|
||||
if(waitpid(d->pid, &status, WNOHANG) == d->pid){
|
||||
d->pid = -1;
|
||||
readerrors(d);
|
||||
return fail("daemon exited before creating IPC socket");
|
||||
}
|
||||
timeout = leftms(deadline);
|
||||
if(timeout == 0)
|
||||
return fail("timed out waiting for protected IPC socket %s",
|
||||
d->socket);
|
||||
pfd[0].fd = notifyfd;
|
||||
pfd[0].events = POLLIN;
|
||||
pfd[0].revents = 0;
|
||||
pfd[1].fd = d->errfd;
|
||||
pfd[1].events = POLLIN;
|
||||
pfd[1].revents = 0;
|
||||
n = poll(pfd, 2, timeout);
|
||||
if(n < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(n < 0)
|
||||
return fail("poll for IPC socket: %s", strerror(errno));
|
||||
if(n == 0)
|
||||
continue;
|
||||
if(pfd[1].revents != 0)
|
||||
readerrors(d);
|
||||
if(pfd[0].revents & POLLIN)
|
||||
while(read(notifyfd, buf, sizeof buf) < 0 && errno == EINTR)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
startdaemon(Daemon *d, char *program, char *mapdir)
|
||||
{
|
||||
char missing[384];
|
||||
int fd, notifyfd, watch, errpipe[2];
|
||||
long maxfd;
|
||||
pid_t pid;
|
||||
|
||||
memset(d, 0, sizeof *d);
|
||||
d->pid = -1;
|
||||
d->errfd = -1;
|
||||
snprintf(d->root, sizeof d->root, "/tmp/strans-ipc.XXXXXX");
|
||||
if(mkdtemp(d->root) == NULL)
|
||||
return fail("mkdtemp: %s", strerror(errno));
|
||||
if(snprintf(d->runtime, sizeof d->runtime, "%s/runtime", d->root)
|
||||
>= (int)sizeof d->runtime ||
|
||||
snprintf(d->config, sizeof d->config, "%s/config", d->root)
|
||||
>= (int)sizeof d->config ||
|
||||
snprintf(d->ibus, sizeof d->ibus, "%s/ibus", d->config)
|
||||
>= (int)sizeof d->ibus ||
|
||||
snprintf(d->bus, sizeof d->bus, "%s/bus", d->ibus)
|
||||
>= (int)sizeof d->bus ||
|
||||
snprintf(d->home, sizeof d->home, "%s/home", d->root)
|
||||
>= (int)sizeof d->home ||
|
||||
snprintf(d->socket, sizeof d->socket, "%s/strans.sock", d->runtime)
|
||||
>= (int)sizeof d->socket ||
|
||||
snprintf(missing, sizeof missing, "%s/no-such-font.ttf", d->root)
|
||||
>= (int)sizeof missing)
|
||||
return fail("temporary path is too long");
|
||||
if(!makedir(d->runtime) || !makedir(d->config) || !makedir(d->ibus) ||
|
||||
!makedir(d->bus) || !makedir(d->home))
|
||||
return 0;
|
||||
notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
|
||||
if(notifyfd < 0)
|
||||
return fail("inotify_init1: %s", strerror(errno));
|
||||
watch = inotify_add_watch(notifyfd, d->runtime,
|
||||
IN_CREATE|IN_MOVED_TO|IN_ATTRIB);
|
||||
if(watch < 0){
|
||||
close(notifyfd);
|
||||
return fail("inotify_add_watch: %s", strerror(errno));
|
||||
}
|
||||
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0){
|
||||
close(notifyfd);
|
||||
return fail("pipe2: %s", strerror(errno));
|
||||
}
|
||||
pid = fork();
|
||||
if(pid < 0){
|
||||
close(errpipe[0]);
|
||||
close(errpipe[1]);
|
||||
close(notifyfd);
|
||||
return fail("fork: %s", strerror(errno));
|
||||
}
|
||||
if(pid == 0){
|
||||
close(errpipe[0]);
|
||||
close(notifyfd);
|
||||
if(dup2(errpipe[1], STDOUT_FILENO) < 0 ||
|
||||
dup2(errpipe[1], STDERR_FILENO) < 0)
|
||||
_exit(126);
|
||||
close(errpipe[1]);
|
||||
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", d->runtime, 1) < 0 ||
|
||||
setenv("XDG_CONFIG_HOME", d->config, 1) < 0 ||
|
||||
setenv("HOME", d->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
||||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
||||
unsetenv("IBUS_ADDRESS") < 0){
|
||||
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
|
||||
strerror(errno));
|
||||
_exit(126);
|
||||
}
|
||||
execl(program, program, mapdir, missing, (char*)0);
|
||||
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
||||
_exit(127);
|
||||
}
|
||||
close(errpipe[1]);
|
||||
d->pid = pid;
|
||||
d->errfd = errpipe[0];
|
||||
if(!waitsocket(d, notifyfd)){
|
||||
inotify_rm_watch(notifyfd, watch);
|
||||
close(notifyfd);
|
||||
return 0;
|
||||
}
|
||||
inotify_rm_watch(notifyfd, watch);
|
||||
close(notifyfd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
clearbus(Daemon *d)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent *de;
|
||||
char path[576];
|
||||
int ok;
|
||||
|
||||
dir = opendir(d->bus);
|
||||
if(dir == NULL)
|
||||
return errno == ENOENT || fail("open cleanup directory %s: %s",
|
||||
d->bus, 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(path, sizeof path, "%s/%s", d->bus, de->d_name)
|
||||
>= (int)sizeof path){
|
||||
fail("cleanup path is too long: %s", de->d_name);
|
||||
ok = 0;
|
||||
continue;
|
||||
}
|
||||
if(unlink(path) < 0 && errno != ENOENT){
|
||||
fail("remove IBus address file %s: %s", de->d_name,
|
||||
strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
errno = 0;
|
||||
}
|
||||
if(errno != 0){
|
||||
fail("read cleanup directory %s: %s", d->bus, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
closedir(dir);
|
||||
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));
|
||||
}
|
||||
|
||||
static int
|
||||
killdaemon(Daemon *d, int *status)
|
||||
{
|
||||
int n, ok;
|
||||
|
||||
ok = 1;
|
||||
if(kill(d->pid, SIGKILL) < 0 && errno != ESRCH){
|
||||
fail("kill -9 %ld: %s", (long)d->pid, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
do
|
||||
n = waitpid(d->pid, status, 0);
|
||||
while(n < 0 && errno == EINTR);
|
||||
if(n != d->pid){
|
||||
fail("reap %ld after SIGKILL: %s", (long)d->pid,
|
||||
n < 0 ? strerror(errno) : "wrong child");
|
||||
ok = 0;
|
||||
}else
|
||||
d->pid = -1;
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
stopdaemon(Daemon *d)
|
||||
{
|
||||
struct pollfd pfd;
|
||||
int ok, status, n, reaped;
|
||||
int64_t deadline;
|
||||
|
||||
ok = 1;
|
||||
if(d->pid > 0){
|
||||
do
|
||||
n = waitpid(d->pid, &status, WNOHANG);
|
||||
while(n < 0 && errno == EINTR);
|
||||
if(n == d->pid){
|
||||
fail("daemon exited before test termination with wait status %#x",
|
||||
status);
|
||||
d->pid = -1;
|
||||
ok = 0;
|
||||
}else if(n < 0){
|
||||
fail("check daemon %ld before termination: %s",
|
||||
(long)d->pid, strerror(errno));
|
||||
if(errno == ECHILD)
|
||||
d->pid = -1;
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
if(d->pid > 0){
|
||||
if(kill(d->pid, SIGTERM) < 0){
|
||||
fail("kill %ld: %s", (long)d->pid, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
reaped = 0;
|
||||
deadline = nowms() + Stoptimeout;
|
||||
for(;;){
|
||||
n = waitpid(d->pid, &status, WNOHANG);
|
||||
if(n == d->pid){
|
||||
reaped = 1;
|
||||
d->pid = -1;
|
||||
break;
|
||||
}
|
||||
if(n < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(n < 0){
|
||||
fail("waitpid %ld: %s", (long)d->pid, strerror(errno));
|
||||
ok = 0;
|
||||
if(errno == ECHILD)
|
||||
d->pid = -1;
|
||||
break;
|
||||
}
|
||||
n = leftms(deadline);
|
||||
if(n == 0){
|
||||
fail("daemon %ld did not stop after SIGTERM", (long)d->pid);
|
||||
ok = 0;
|
||||
if(!killdaemon(d, &status))
|
||||
ok = 0;
|
||||
break;
|
||||
}
|
||||
pfd.fd = d->errfd;
|
||||
pfd.events = POLLIN|POLLHUP;
|
||||
pfd.revents = 0;
|
||||
if(poll(&pfd, 1, n) < 0 && errno != EINTR){
|
||||
fail("poll daemon %ld: %s", (long)d->pid, strerror(errno));
|
||||
ok = 0;
|
||||
if(!killdaemon(d, &status))
|
||||
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))){
|
||||
fail("daemon exited with unexpected wait status %#x", status);
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
readerrors(d);
|
||||
if(d->errfd >= 0){
|
||||
close(d->errfd);
|
||||
d->errfd = -1;
|
||||
}
|
||||
if(d->socket[0] != '\0' && unlink(d->socket) < 0 && errno != ENOENT){
|
||||
fail("remove IPC socket %s: %s", d->socket, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(!clearbus(d)) ok = 0;
|
||||
if(!rmdirknown(d->bus)) ok = 0;
|
||||
if(!rmdirknown(d->ibus)) ok = 0;
|
||||
if(!rmdirknown(d->config)) ok = 0;
|
||||
if(!rmdirknown(d->runtime)) ok = 0;
|
||||
if(!rmdirknown(d->home)) ok = 0;
|
||||
if(!rmdirknown(d->root)) ok = 0;
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
connectuntil(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;
|
||||
}
|
||||
pfd.fd = fd;
|
||||
pfd.events = POLLOUT;
|
||||
pfd.revents = 0;
|
||||
for(;;){
|
||||
n = leftms(deadline);
|
||||
if(n == 0){
|
||||
close(fd);
|
||||
errno = ETIMEDOUT;
|
||||
return -1;
|
||||
}
|
||||
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. */
|
||||
static 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){
|
||||
if(errno == EINTR)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
static size_t
|
||||
getlen(unsigned char p[Ipclensz])
|
||||
{
|
||||
@@ -616,6 +110,15 @@ sendreset(int fd, int 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
|
||||
expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
||||
char *where)
|
||||
@@ -632,11 +135,12 @@ expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
||||
}
|
||||
|
||||
static int
|
||||
requestreset(int fd, int want, int eaten, char *preedit, char *where)
|
||||
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, "", preedit, where);
|
||||
return expectresponse(fd, want, eaten, commit, preedit, where);
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -654,7 +158,7 @@ tryclient(char *path, int64_t deadline, int *client)
|
||||
Response res;
|
||||
int fd, rv;
|
||||
|
||||
fd = connectuntil(path, deadline);
|
||||
fd = connectsocket(path, deadline);
|
||||
if(fd < 0)
|
||||
return errno == ETIMEDOUT ? -1 : 0;
|
||||
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
||||
@@ -701,7 +205,7 @@ overflowrejected(char *path)
|
||||
int64_t deadline;
|
||||
|
||||
deadline = nowms() + Calltimeout;
|
||||
fd = connectuntil(path, deadline);
|
||||
fd = connectsocket(path, deadline);
|
||||
if(fd < 0)
|
||||
return fail("overflow connect: %s", strerror(errno));
|
||||
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
||||
@@ -742,7 +246,25 @@ overflowrejected(char *path)
|
||||
}
|
||||
|
||||
static int
|
||||
runlistener(Daemon *d)
|
||||
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");
|
||||
close(client);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
runcapacity(char *path)
|
||||
{
|
||||
int client[Maxclients];
|
||||
int i, ok;
|
||||
@@ -750,25 +272,16 @@ runlistener(Daemon *d)
|
||||
for(i = 0; i < Maxclients; i++)
|
||||
client[i] = -1;
|
||||
ok = 0;
|
||||
client[0] = connectuntil(d->socket, nowms() + Calltimeout);
|
||||
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") ||
|
||||
!requestkey(client[0], 1, 0, 'k', 1, "", "k", "GTK preedit") ||
|
||||
!requestreset(client[0], 1, 1, "", "active reset"))
|
||||
goto out;
|
||||
if(!sendkey(client[0], 0, 0, 'k') ||
|
||||
!sendkey(client[0], 1, 0, Kmodfirst)){
|
||||
fail("send pipelined XIM/GTK requests: %s", strerror(errno));
|
||||
goto out;
|
||||
}
|
||||
if(!expectresponse(client[0], 0, 1, "", "", "XIM framing") ||
|
||||
!expectresponse(client[0], 1, 0, "", "k", "GTK after XIM framing"))
|
||||
if(!requestkey(client[0], 1, Mctrl, 'n', 1, "", "",
|
||||
"select Japanese"))
|
||||
goto out;
|
||||
for(i = 1; i < Maxclients; i++){
|
||||
client[i] = connectuntil(d->socket, nowms() + Calltimeout);
|
||||
client[i] = connectsocket(path, nowms() + Calltimeout);
|
||||
if(client[i] < 0){
|
||||
fail("connect capacity client %d: %s", i, strerror(errno));
|
||||
goto out;
|
||||
@@ -777,15 +290,15 @@ runlistener(Daemon *d)
|
||||
"capacity modifier"))
|
||||
goto out;
|
||||
}
|
||||
if(!overflowrejected(d->socket))
|
||||
if(!overflowrejected(path))
|
||||
goto out;
|
||||
close(client[Maxclients-1]);
|
||||
client[Maxclients-1] = -1;
|
||||
if(!waitslot(d->socket, &client[Maxclients-1], "inactive slot recovery"))
|
||||
if(!waitslot(path, &client[Maxclients-1], "inactive slot recovery"))
|
||||
goto out;
|
||||
close(client[0]);
|
||||
client[0] = -1;
|
||||
if(!waitslot(d->socket, &client[0], "active slot recovery") ||
|
||||
if(!waitslot(path, &client[0], "active slot recovery") ||
|
||||
!requestkey(client[0], 1, 0, 'a', 1, "", "あ",
|
||||
"post-disconnect composition"))
|
||||
goto out;
|
||||
@@ -801,21 +314,35 @@ int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
Daemon daemon;
|
||||
int ok;
|
||||
Live live;
|
||||
int capacity, ok;
|
||||
|
||||
if(argc != 3){
|
||||
fprintf(stderr, "usage: ipc_live_test strans mapdir\n");
|
||||
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;
|
||||
}
|
||||
ok = startdaemon(&daemon, argv[1], argv[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 = runlistener(&daemon);
|
||||
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 live listener: ok\n");
|
||||
printf("ipc %s: ok\n", capacity ? "connection capacity" : "live smoke");
|
||||
return 0;
|
||||
}
|
||||
|
||||
286
tests/ipc_test.c
286
tests/ipc_test.c
@@ -1,10 +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"
|
||||
|
||||
#undef accept
|
||||
#undef listen
|
||||
#undef send
|
||||
|
||||
static int64_t
|
||||
nowms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
||||
return 0;
|
||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
static void ipcclientdeadlines(struct ct*);
|
||||
static void ipcconnectcloexec(struct ct*);
|
||||
|
||||
void
|
||||
ipc_masks_modifiers(struct ct *t)
|
||||
{
|
||||
@@ -25,6 +48,103 @@ ipc_masks_modifiers(struct ct *t)
|
||||
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, 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, 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|Cclientpreedit, 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, Cclientpreedit);
|
||||
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
|
||||
@@ -45,7 +165,6 @@ ipc_runtime_path(struct ct *t)
|
||||
snprint(fallback, sizeof fallback, "/tmp/strans.%d", getuid());
|
||||
CT_EQ_STR(t, fallback, buf);
|
||||
CT_EQ_INT(t, -1, ipcpath(buf, 4));
|
||||
CT_EQ_INT(t, -1, ipcpath(nil, sizeof buf));
|
||||
if(saved != nil){
|
||||
setenv("XDG_RUNTIME_DIR", saved, 1);
|
||||
free(saved);
|
||||
@@ -83,22 +202,18 @@ ipc_response_pack_boundaries(struct ct *t)
|
||||
CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1,
|
||||
(char*)field, sizeof field, (char*)field, sizeof field, 1));
|
||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
||||
nil, 1, nil, 0, 0));
|
||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
||||
nil, 0, nil, 1, 1));
|
||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
||||
(char*)field, Ipcfieldmax+1, nil, 0, 0));
|
||||
(char*)field, Ipcfieldmax+1, "", 0, 0));
|
||||
}
|
||||
|
||||
void
|
||||
ipc_response_empty_and_preedit(struct ct *t)
|
||||
{
|
||||
uchar first[Ipcmaxresp], second[Ipcmaxresp];
|
||||
char commit[16], preedit[16];
|
||||
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
||||
Ipcresp resp;
|
||||
int fd[2], nfirst, nsecond;
|
||||
|
||||
nfirst = ipcpackresp(first, sizeof first, 0, nil, 0, nil, 0, 0);
|
||||
nfirst = ipcpackresp(first, sizeof first, 0, "", 0, "", 0, 0);
|
||||
nsecond = ipcpackresp(second, sizeof second, 1,
|
||||
"go", 2, "kana", 4, 1);
|
||||
if(!CT_CHECK(t, nfirst > 0 && nsecond > 0))
|
||||
@@ -109,18 +224,16 @@ ipc_response_empty_and_preedit(struct ct *t)
|
||||
}
|
||||
if(CT_EQ_INT(t, 0, ipcsend(fd[0], first, nfirst)) &&
|
||||
CT_EQ_INT(t, 0, ipcsend(fd[0], second, nsecond)) &&
|
||||
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, sizeof commit,
|
||||
nil, 0, &resp))){
|
||||
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, preedit, &resp))){
|
||||
CT_EQ_INT(t, 0, resp.eaten);
|
||||
CT_EQ_SIZE(t, 0, resp.ncommit);
|
||||
CT_EQ_SIZE(t, 0, resp.npreedit);
|
||||
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, sizeof commit,
|
||||
preedit, sizeof preedit, &resp))){
|
||||
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.ncommit);
|
||||
CT_EQ_SIZE(t, 4, resp.npreedit);
|
||||
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);
|
||||
}
|
||||
@@ -132,14 +245,14 @@ void
|
||||
ipc_response_max_and_drain(struct ct *t)
|
||||
{
|
||||
uchar first[Ipcmaxresp], second[Ipcmaxresp], field[Ipcfieldmax];
|
||||
char commit[8], preedit[8];
|
||||
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,
|
||||
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
||||
nsecond = ipcpackresp(second, sizeof second, 0, "ok", 2, nil, 0, 0);
|
||||
nsecond = ipcpackresp(second, sizeof second, 0, "ok", 2, "", 0, 0);
|
||||
if(!CT_CHECK(t, nfirst == Ipcmaxresp && nsecond > 0))
|
||||
return;
|
||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
||||
@@ -148,15 +261,15 @@ ipc_response_max_and_drain(struct ct *t)
|
||||
}
|
||||
if(CT_EQ_INT(t, 0, ipcsend(fd[0], first, nfirst)) &&
|
||||
CT_EQ_INT(t, 0, ipcsend(fd[0], second, nsecond)) &&
|
||||
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, sizeof commit,
|
||||
preedit, sizeof preedit, &resp))){
|
||||
CT_EQ_SIZE(t, Ipcfieldmax, resp.ncommit);
|
||||
CT_EQ_SIZE(t, Ipcfieldmax, resp.npreedit);
|
||||
CT_EQ_STR(t, "xxxxxxx", commit);
|
||||
CT_EQ_STR(t, "xxxxxxx", preedit);
|
||||
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, sizeof commit,
|
||||
nil, 0, &resp)))
|
||||
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]);
|
||||
@@ -166,7 +279,7 @@ void
|
||||
ipc_response_fragmented_and_truncated(struct ct *t)
|
||||
{
|
||||
uchar frame[Ipcmaxresp];
|
||||
char commit[8], preedit[8];
|
||||
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
||||
Ipcresp resp;
|
||||
int fd[2], i, n;
|
||||
|
||||
@@ -182,8 +295,8 @@ ipc_response_fragmented_and_truncated(struct ct *t)
|
||||
CT_ERRORF(t, "fragment send failed");
|
||||
break;
|
||||
}
|
||||
if(i == n && CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1,
|
||||
commit, sizeof commit, preedit, sizeof preedit, &resp))){
|
||||
if(i == n && CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit,
|
||||
&resp))){
|
||||
CT_EQ_STR(t, "abc", commit);
|
||||
CT_EQ_STR(t, "xy", preedit);
|
||||
}
|
||||
@@ -196,8 +309,19 @@ ipc_response_fragmented_and_truncated(struct ct *t)
|
||||
}
|
||||
CT_EQ_INT(t, 0, ipcsend(fd[0], frame, n-1));
|
||||
shutdown(fd[0], SHUT_WR);
|
||||
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, sizeof commit,
|
||||
preedit, sizeof preedit, &resp));
|
||||
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]);
|
||||
}
|
||||
@@ -214,4 +338,104 @@ ipc_broken_peer_send(struct ct *t)
|
||||
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");
|
||||
}
|
||||
|
||||
73
tests/keymap_test.c
Normal file
73
tests/keymap_test.c
Normal file
@@ -0,0 +1,73 @@
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
#include "test.h"
|
||||
#include <xcb-imdkit/encoding.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||
|
||||
uint32_t keymaplookup(struct xkb_state*, uint8_t, uint16_t);
|
||||
|
||||
enum
|
||||
{
|
||||
ShiftMask = 1<<0,
|
||||
LockMask = 1<<1,
|
||||
Mod2Mask = 1<<4,
|
||||
Mod5Mask = 1<<7,
|
||||
Group1 = 1<<13,
|
||||
};
|
||||
|
||||
void
|
||||
xim_keymap_lookup(struct ct *t)
|
||||
{
|
||||
struct xkb_context *context;
|
||||
struct xkb_keymap *keymap;
|
||||
struct xkb_rule_names names;
|
||||
struct xkb_state *state;
|
||||
|
||||
memset(&names, 0, sizeof names);
|
||||
names.layout = "de,ru";
|
||||
context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||
keymap = xkb_keymap_new_from_names(context, &names,
|
||||
XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||
if(!CT_CHECK(t, keymap != nil)){
|
||||
xkb_context_unref(context);
|
||||
return;
|
||||
}
|
||||
state = xkb_state_new(keymap);
|
||||
CT_EQ_UINT(t, XKB_KEY_a, keymaplookup(state, 38, 0));
|
||||
CT_EQ_UINT(t, XKB_KEY_A, keymaplookup(state, 38, ShiftMask));
|
||||
CT_EQ_UINT(t, XKB_KEY_A, keymaplookup(state, 38, LockMask));
|
||||
CT_EQ_UINT(t, XKB_KEY_a, keymaplookup(state, 38, LockMask|ShiftMask));
|
||||
CT_EQ_UINT(t, XKB_KEY_EuroSign, keymaplookup(state, 26, Mod5Mask));
|
||||
CT_EQ_UINT(t, XKB_KEY_Cyrillic_u, keymaplookup(state, 26, Group1));
|
||||
CT_EQ_UINT(t, XKB_KEY_KP_End, keymaplookup(state, 87, 0));
|
||||
CT_EQ_UINT(t, XKB_KEY_KP_1, keymaplookup(state, 87, Mod2Mask));
|
||||
xkb_state_unref(state);
|
||||
xkb_keymap_unref(keymap);
|
||||
xkb_context_unref(context);
|
||||
}
|
||||
|
||||
/* imdkit carries any UTF-8 text through COMPOUND_TEXT unchanged. */
|
||||
void
|
||||
xim_compound_text(struct ct *t)
|
||||
{
|
||||
static char *text[] = { "A😀한", "한글", "𠀋", "👩💻" };
|
||||
char *ct, *utf8;
|
||||
size_t clen, len, ulen;
|
||||
int i;
|
||||
|
||||
xcb_compound_text_init();
|
||||
for(i = 0; i < nelem(text); i++){
|
||||
len = strlen(text[i]);
|
||||
ct = xcb_utf8_to_compound_text(text[i], len, &clen);
|
||||
if(!CT_CHECK(t, ct != nil))
|
||||
continue;
|
||||
utf8 = xcb_compound_text_to_utf8(ct, clen, &ulen);
|
||||
if(CT_CHECK(t, utf8 != nil)){
|
||||
CT_EQ_SIZE(t, len, ulen);
|
||||
CT_EQ_MEM(t, text[i], utf8, len);
|
||||
}
|
||||
free(utf8);
|
||||
free(ct);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,14 @@
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
#include "test.h"
|
||||
|
||||
static Str
|
||||
kotrans(char *keys)
|
||||
{
|
||||
Emit e;
|
||||
Im state;
|
||||
Str out;
|
||||
char *p;
|
||||
Str out, raw;
|
||||
|
||||
memset(&state, 0, sizeof state);
|
||||
state.l = getlang(LangKO);
|
||||
sclear(&out);
|
||||
for(p = keys; *p != '\0'; p++){
|
||||
e = transko(&state, (uchar)*p);
|
||||
sappend(&out, &e.s);
|
||||
state.pre = e.next;
|
||||
if(!e.eat)
|
||||
sputr(&out, (uchar)*p);
|
||||
}
|
||||
sappend(&out, &state.pre);
|
||||
raw = mkstr(keys);
|
||||
transstr(getlang(LangKO), &raw, &out);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
695
tests/live.c
Normal file
695
tests/live.c
Normal file
@@ -0,0 +1,695 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
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 || 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)
|
||||
{
|
||||
unsigned char empty[] = {0, 0, 0, 0, 0};
|
||||
|
||||
return ipcrequest(fd, 0, Kmodfirst, empty, sizeof empty, where);
|
||||
}
|
||||
73
tests/live.h
Normal file
73
tests/live.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#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];
|
||||
};
|
||||
|
||||
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 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*);
|
||||
@@ -128,12 +128,12 @@ class MkhanjaTest(unittest.TestCase):
|
||||
self.assertIn("한\t", result.stdout)
|
||||
|
||||
def test_generator_keeps_runtime_candidate_limit(self):
|
||||
rows = "".join(f"{chr(0x4E00 + n)}\t한\n" for n in range(33))
|
||||
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), 32)
|
||||
self.assertEqual(candidates[-1], chr(0x4E00 + 31))
|
||||
self.assertEqual(len(candidates), 128)
|
||||
self.assertEqual(candidates[-1], chr(0x4E00 + 127))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,34 +1,343 @@
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
#include "test.h"
|
||||
#include <limits.h>
|
||||
|
||||
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)
|
||||
{
|
||||
Rune heart[] = { 0x2764, 0xfe0f };
|
||||
int tinyheight[] = {
|
||||
1, PopupPad, 2*PopupPad + Fontsz - 1,
|
||||
};
|
||||
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;
|
||||
int x, y;
|
||||
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);
|
||||
|
||||
CT_EQ_INT(t, 1, popupcells(heart, nelem(heart)));
|
||||
memset(&caret, 0, sizeof caret);
|
||||
popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y);
|
||||
CT_EQ_INT(t, 50, x);
|
||||
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 = 70;
|
||||
caret.x = 2100;
|
||||
caret.y = 80;
|
||||
caret.h = 20;
|
||||
popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y);
|
||||
CT_EQ_INT(t, 70, x);
|
||||
popupposition(&caret, 0, 0, &area, 100, 60, &x, &y);
|
||||
CT_EQ_INT(t, 2100, x);
|
||||
CT_EQ_INT(t, 100, y);
|
||||
caret.x = 790;
|
||||
caret.y = 590;
|
||||
popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y);
|
||||
CT_EQ_INT(t, 700, x);
|
||||
CT_EQ_INT(t, 540, y);
|
||||
caret.x = INT_MIN;
|
||||
caret.y = INT_MAX;
|
||||
caret.h = INT_MAX;
|
||||
popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y);
|
||||
CT_EQ_INT(t, 0, x);
|
||||
CT_EQ_INT(t, 540, y);
|
||||
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);
|
||||
|
||||
if(!CT_CHECK(t, textinit()))
|
||||
return;
|
||||
|
||||
/* 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. */
|
||||
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, -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 */
|
||||
|
||||
/* The selected row wins over preedit and footer in one-row height. */
|
||||
dc.pre = mkstr("preedit");
|
||||
popuplayout(&dc, PopupBasew, 2*PopupPad + Fontsz, &p);
|
||||
CT_EQ_INT(t, -1, p.prey);
|
||||
CT_EQ_INT(t, 1, p.n);
|
||||
CT_EQ_INT(t, Maxdisp - 1, p.row0);
|
||||
CT_EQ_INT(t, -1, p.marky);
|
||||
|
||||
/* With less than one padded row, selected content starts at the top. */
|
||||
for(i = 0; i < nelem(tinyheight); i++){
|
||||
popuplayout(&dc, PopupBasew, tinyheight[i], &p);
|
||||
CT_EQ_INT(t, min(tinyheight[i], Fontsz), p.h);
|
||||
CT_EQ_INT(t, 0, p.rowsy);
|
||||
CT_EQ_INT(t, 0, p.sely);
|
||||
CT_EQ_INT(t, 1, p.n);
|
||||
CT_EQ_INT(t, Maxdisp - 1, p.row0);
|
||||
popupdraw(img, &dc, &p);
|
||||
CT_EQ_UINT(t, Colsel, pixel(img, &p, p.w - PopupPad - 1, 0));
|
||||
}
|
||||
free(img);
|
||||
}
|
||||
|
||||
@@ -3,25 +3,19 @@
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../srv.c"
|
||||
#include "../cutest/cutest.h"
|
||||
|
||||
void testengineinit(int);
|
||||
void testenginehandle(Keyreq*);
|
||||
void* testengineowner(void);
|
||||
void testenginepreedit(Str*);
|
||||
#include "srv.c"
|
||||
#include "test.h"
|
||||
|
||||
#undef recv
|
||||
|
||||
typedef struct Enginegate Enginegate;
|
||||
typedef struct Serverfix Serverfix;
|
||||
typedef struct Testclient Testclient;
|
||||
|
||||
struct Enginegate
|
||||
/* A held pump plus a private client-slot channel of the wanted depth. */
|
||||
struct Serverfix
|
||||
{
|
||||
Channel *seen;
|
||||
Channel *go;
|
||||
Channel *stop;
|
||||
Channel *done;
|
||||
Pump pump;
|
||||
Channel *oldclientc;
|
||||
};
|
||||
|
||||
struct Testclient
|
||||
@@ -34,30 +28,30 @@ struct Testclient
|
||||
};
|
||||
|
||||
static void
|
||||
enginegate(void *arg)
|
||||
serverbegin(Serverfix *f, int nclients)
|
||||
{
|
||||
Enginegate *g;
|
||||
Keyreq req;
|
||||
Drawcmd dc;
|
||||
uchar token;
|
||||
Alt alts[] = {
|
||||
{keyc, &req, CHANRCV, nil},
|
||||
{nil, &token, CHANRCV, nil},
|
||||
{nil, nil, CHANEND, nil},
|
||||
};
|
||||
|
||||
g = arg;
|
||||
alts[1].c = g->stop;
|
||||
for(;;)
|
||||
switch(alt(alts)){
|
||||
case 0:
|
||||
chansend(g->seen, &req);
|
||||
chanrecv(g->go, &token);
|
||||
testenginehandle(&req);
|
||||
break;
|
||||
case 1:
|
||||
chansend(g->done, &token);
|
||||
return;
|
||||
}
|
||||
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
|
||||
@@ -142,38 +136,70 @@ sendreset(struct ct *t, Testclient *client, int want)
|
||||
}
|
||||
|
||||
static Keyreq
|
||||
nextrequest(struct ct *t, Enginegate *g, int op)
|
||||
nextrequestcap(struct ct *t, Pump *p, int op, int cap)
|
||||
{
|
||||
Keyreq req;
|
||||
|
||||
memset(&req, 0, sizeof req);
|
||||
chanrecv(g->seen, &req);
|
||||
chanrecv(p->trace, &req);
|
||||
CT_EQ_INT(t, op, req.op);
|
||||
CT_EQ_INT(t, cap, req.cap);
|
||||
return req;
|
||||
}
|
||||
|
||||
static Keyreq
|
||||
nextrequest(struct ct *t, Pump *p, int op)
|
||||
{
|
||||
return nextrequestcap(t, p, op, Cclientpreedit);
|
||||
}
|
||||
|
||||
static void
|
||||
allowrequest(Enginegate *g)
|
||||
allowrequest(Pump *p)
|
||||
{
|
||||
uchar token;
|
||||
|
||||
token = 0;
|
||||
chansend(g->go, &token);
|
||||
chansend(p->go, &token);
|
||||
}
|
||||
|
||||
static int
|
||||
readreply(struct ct *t, Testclient *client, int want, char *preedit, int npreedit)
|
||||
readreply(struct ct *t, Testclient *client, int want, char *wantcommit,
|
||||
char *preedit)
|
||||
{
|
||||
char commit[Maxutf];
|
||||
char commit[Ipcfieldmax+1];
|
||||
Ipcresp resp;
|
||||
|
||||
if(ipcreadresp(client->peer, want, commit, sizeof commit,
|
||||
preedit, npreedit, &resp) < 0)
|
||||
if(ipcreadresp(client->peer, want, commit, preedit, &resp) < 0)
|
||||
return CT_ERRORF(t, "response read failed: %s", strerror(errno));
|
||||
CT_EQ_STR(t, "", commit);
|
||||
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)
|
||||
{
|
||||
@@ -191,127 +217,112 @@ waitclient(struct ct *t, Testclient *client)
|
||||
}
|
||||
|
||||
static void
|
||||
disconnectclient(struct ct *t, Enginegate *g, Testclient *client, void *owner)
|
||||
disconnectclient(struct ct *t, Pump *p, Testclient *client, void *owner)
|
||||
{
|
||||
Keyreq req;
|
||||
uchar token;
|
||||
|
||||
close(client->peer);
|
||||
client->peer = -1;
|
||||
req = nextrequest(t, g, Keyrelease);
|
||||
req = nextrequest(t, p, Keyrelease);
|
||||
if(owner != nil)
|
||||
CT_EQ_PTR(t, owner, req.owner);
|
||||
CT_CHECK(t, channbrecv(client->done, &token) <= 0);
|
||||
allowrequest(g);
|
||||
allowrequest(p);
|
||||
waitclient(t, client);
|
||||
}
|
||||
|
||||
void
|
||||
server_connection_ownership(struct ct *t)
|
||||
{
|
||||
Channel *oldclientc;
|
||||
Enginegate gate;
|
||||
Serverfix f;
|
||||
Testclient a, b, c;
|
||||
Keyreq req;
|
||||
Drawcmd dc;
|
||||
Str shown;
|
||||
char preedit[Maxutf];
|
||||
char preedit[Ipcfieldmax+1];
|
||||
void *aowner, *bowner, *cowner;
|
||||
uchar byte, token;
|
||||
ssize_t n;
|
||||
int gateactive;
|
||||
|
||||
memset(&gate, 0, sizeof gate);
|
||||
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;
|
||||
oldclientc = clientc;
|
||||
while(channbrecv(drawc, &dc) > 0)
|
||||
;
|
||||
clientc = chancreate(sizeof token, 3);
|
||||
gate.seen = chancreate(sizeof(Keyreq), 0);
|
||||
gate.go = chancreate(sizeof token, 0);
|
||||
gate.stop = chancreate(sizeof token, 0);
|
||||
gate.done = chancreate(sizeof token, 0);
|
||||
testengineinit(LangJP);
|
||||
gateactive = proccreate(enginegate, &gate, 8192) >= 0;
|
||||
if(!CT_CHECK(t, gateactive))
|
||||
goto cleanup;
|
||||
serverbegin(&f, 3);
|
||||
if(!startclient(t, &a) || !startclient(t, &b))
|
||||
goto cleanup;
|
||||
|
||||
if(!sendkey(t, &a, 1, 0, 'k'))
|
||||
goto cleanup;
|
||||
req = nextrequest(t, &gate, Keypress);
|
||||
req = nextrequest(t, &f.pump, Keypress);
|
||||
aowner = req.owner;
|
||||
allowrequest(&gate);
|
||||
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
|
||||
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, &gate, Keypress);
|
||||
req = nextrequest(t, &f.pump, Keypress);
|
||||
CT_EQ_PTR(t, aowner, req.owner);
|
||||
allowrequest(&gate);
|
||||
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
|
||||
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, &gate, Keypress);
|
||||
req = nextrequest(t, &f.pump, Keypress);
|
||||
bowner = req.owner;
|
||||
CT_CHECK(t, bowner != aowner);
|
||||
allowrequest(&gate);
|
||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
||||
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, &gate, Keyreset);
|
||||
req = nextrequest(t, &f.pump, Keyreset);
|
||||
CT_EQ_PTR(t, aowner, req.owner);
|
||||
allowrequest(&gate);
|
||||
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
|
||||
allowrequest(&f.pump);
|
||||
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, Kmodfirst))
|
||||
goto cleanup;
|
||||
req = nextrequest(t, &gate, Keypress);
|
||||
allowrequest(&gate);
|
||||
CT_CHECK(t, !readreply(t, &b, 1, preedit, sizeof preedit));
|
||||
req = nextrequest(t, &f.pump, Keypress);
|
||||
allowrequest(&f.pump);
|
||||
CT_CHECK(t, !readreply(t, &b, 1, "", preedit));
|
||||
CT_EQ_STR(t, "ん", preedit);
|
||||
|
||||
disconnectclient(t, &gate, &a, aowner);
|
||||
disconnectclient(t, &f.pump, &a, aowner);
|
||||
CT_EQ_PTR(t, bowner, testengineowner());
|
||||
if(!sendkey(t, &b, 1, 0, 'y'))
|
||||
goto cleanup;
|
||||
req = nextrequest(t, &gate, Keypress);
|
||||
allowrequest(&gate);
|
||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
||||
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, &gate, Keypress);
|
||||
allowrequest(&gate);
|
||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
||||
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, &gate, Keyreset);
|
||||
req = nextrequest(t, &f.pump, Keyreset);
|
||||
CT_EQ_PTR(t, bowner, req.owner);
|
||||
allowrequest(&gate);
|
||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
||||
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 = nextrequest(t, &gate, Keypress);
|
||||
req = nextrequestcap(t, &f.pump, Keypress, 0);
|
||||
CT_EQ_PTR(t, bowner, req.owner);
|
||||
allowrequest(&gate);
|
||||
CT_CHECK(t, readreply(t, &b, 0, nil, 0));
|
||||
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);
|
||||
@@ -319,12 +330,12 @@ server_connection_ownership(struct ct *t)
|
||||
CT_EQ_PTR(t, bowner, testengineowner());
|
||||
if(!sendkey(t, &b, 1, 0, 'a'))
|
||||
goto cleanup;
|
||||
req = nextrequest(t, &gate, Keypress);
|
||||
allowrequest(&gate);
|
||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
||||
req = nextrequest(t, &f.pump, Keypress);
|
||||
allowrequest(&f.pump);
|
||||
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
||||
CT_EQ_STR(t, "か", preedit);
|
||||
|
||||
disconnectclient(t, &gate, &b, bowner);
|
||||
disconnectclient(t, &f.pump, &b, bowner);
|
||||
CT_EQ_PTR(t, nil, testengineowner());
|
||||
testenginepreedit(&shown);
|
||||
CT_EQ_INT(t, 0, shown.n);
|
||||
@@ -333,38 +344,205 @@ server_connection_ownership(struct ct *t)
|
||||
goto cleanup;
|
||||
if(!sendkey(t, &c, 1, 0, 'k'))
|
||||
goto cleanup;
|
||||
req = nextrequest(t, &gate, Keypress);
|
||||
req = nextrequest(t, &f.pump, Keypress);
|
||||
cowner = req.owner;
|
||||
close(c.peer);
|
||||
c.peer = -1;
|
||||
allowrequest(&gate);
|
||||
req = nextrequest(t, &gate, Keyrelease);
|
||||
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(&gate);
|
||||
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, &gate, &a, aowner);
|
||||
disconnectclient(t, &f.pump, &a, aowner);
|
||||
if(b.peer >= 0)
|
||||
disconnectclient(t, &gate, &b, bowner);
|
||||
disconnectclient(t, &f.pump, &b, bowner);
|
||||
if(c.peer >= 0)
|
||||
disconnectclient(t, &gate, &c, cowner);
|
||||
if(gateactive){
|
||||
token = 0;
|
||||
chansend(gate.stop, &token);
|
||||
chanrecv(gate.done, &token);
|
||||
}
|
||||
chanfree(gate.seen);
|
||||
chanfree(gate.go);
|
||||
chanfree(gate.stop);
|
||||
chanfree(gate.done);
|
||||
chanfree(clientc);
|
||||
clientc = oldclientc;
|
||||
while(channbrecv(drawc, &dc) > 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, Cclientpreedit);
|
||||
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, Cclientpreedit);
|
||||
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
||||
goto cleanup;
|
||||
req = nextrequest(t, &f.pump, Keycap);
|
||||
allowrequest(&f.pump);
|
||||
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
||||
CT_EQ_STR(t, "", preedit);
|
||||
ipcpackreq(frame, 1, 0, 'n');
|
||||
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
||||
goto cleanup;
|
||||
req = nextrequest(t, &f.pump, Keypress);
|
||||
CT_EQ_INT(t, 1, req.caret.valid);
|
||||
CT_EQ_INT(t, -101, req.caret.x);
|
||||
allowrequest(&f.pump);
|
||||
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
||||
CT_EQ_STR(t, "ん", preedit);
|
||||
|
||||
disconnectclient(t, &f.pump, &a, aowner);
|
||||
|
||||
/* Disconnect drops state; a legacy client gets the historical defaults. */
|
||||
if(!startclient(t, &b) || !sendkey(t, &b, 1, 0, 'k'))
|
||||
goto cleanup;
|
||||
req = nextrequest(t, &f.pump, Keypress);
|
||||
bowner = req.owner;
|
||||
CT_EQ_INT(t, 0, req.caret.valid);
|
||||
allowrequest(&f.pump);
|
||||
CT_EQ_INT(t, 1, readreply(t, &b, 1, "", preedit));
|
||||
CT_EQ_STR(t, "k", preedit);
|
||||
|
||||
disconnectclient(t, &f.pump, &b, bowner);
|
||||
|
||||
cleanup:
|
||||
if(a.peer >= 0)
|
||||
disconnectclient(t, &f.pump, &a, aowner);
|
||||
if(b.peer >= 0)
|
||||
disconnectclient(t, &f.pump, &b, bowner);
|
||||
serverend(&f);
|
||||
}
|
||||
|
||||
void
|
||||
server_rejects_unknown_extension(struct ct *t)
|
||||
{
|
||||
Serverfix f;
|
||||
Testclient badver, badop, good;
|
||||
Keyreq req;
|
||||
uchar frame[Ipccaretsz];
|
||||
char preedit[Ipcfieldmax+1];
|
||||
void *owner;
|
||||
|
||||
memset(&badver, 0, sizeof badver);
|
||||
memset(&badop, 0, sizeof badop);
|
||||
memset(&good, 0, sizeof good);
|
||||
badver.fd = badver.peer = badop.fd = badop.peer = -1;
|
||||
good.fd = good.peer = -1;
|
||||
owner = nil;
|
||||
serverbegin(&f, 3);
|
||||
|
||||
if(!startclient(t, &badver))
|
||||
goto cleanup;
|
||||
ipcpackcaret(frame, 1, 3, 4, 5);
|
||||
frame[1]++;
|
||||
if(!sendframe(t, &badver, frame, sizeof frame, 0))
|
||||
goto cleanup;
|
||||
req = nextrequest(t, &f.pump, Keyrelease);
|
||||
allowrequest(&f.pump);
|
||||
waitclient(t, &badver);
|
||||
close(badver.peer);
|
||||
badver.peer = -1;
|
||||
|
||||
if(!startclient(t, &badop))
|
||||
goto cleanup;
|
||||
ipcpackcaret(frame, 1, 3, 4, 5);
|
||||
frame[2]++;
|
||||
if(!sendframe(t, &badop, frame, sizeof frame, 1))
|
||||
goto cleanup;
|
||||
req = nextrequest(t, &f.pump, Keyrelease);
|
||||
allowrequest(&f.pump);
|
||||
waitclient(t, &badop);
|
||||
close(badop.peer);
|
||||
badop.peer = -1;
|
||||
|
||||
/* A malformed peer must not disturb another client connection. */
|
||||
if(!startclient(t, &good) || !sendkey(t, &good, 1, 0, 'k'))
|
||||
goto cleanup;
|
||||
req = nextrequest(t, &f.pump, Keypress);
|
||||
owner = req.owner;
|
||||
allowrequest(&f.pump);
|
||||
CT_EQ_INT(t, 1, readreply(t, &good, 1, "", preedit));
|
||||
CT_EQ_STR(t, "k", preedit);
|
||||
disconnectclient(t, &f.pump, &good, owner);
|
||||
|
||||
cleanup:
|
||||
if(badver.peer >= 0)
|
||||
disconnectclient(t, &f.pump, &badver, nil);
|
||||
if(badop.peer >= 0)
|
||||
disconnectclient(t, &f.pump, &badop, nil);
|
||||
if(good.peer >= 0)
|
||||
disconnectclient(t, &f.pump, &good, owner);
|
||||
serverend(&f);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,52 @@
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
#include "test.h"
|
||||
|
||||
void
|
||||
str_init_utf8(struct ct *t)
|
||||
{
|
||||
static char badtail[] = { 'a', (char)0x80 };
|
||||
static char incomplete[] = { (char)0xea, (char)0xb0 };
|
||||
static char overlong[] = { (char)0xc0, (char)0xaf };
|
||||
static char outofrange[] = {
|
||||
(char)0xf4, (char)0x90, (char)0x80, (char)0x80,
|
||||
};
|
||||
static char surrogate[] = {
|
||||
(char)0xed, (char)0xa0, (char)0x80,
|
||||
};
|
||||
static char unexpected[] = { (char)0x80 };
|
||||
static const struct {
|
||||
char *name;
|
||||
char *src;
|
||||
int n;
|
||||
int ok;
|
||||
char *want;
|
||||
} cases[] = {
|
||||
{ "canonical Runeerror", "\xef\xbf\xbd", 3, 1, "\xef\xbf\xbd" },
|
||||
{ "incomplete", incomplete, sizeof incomplete, 0, "" },
|
||||
{ "unexpected continuation", unexpected, sizeof unexpected, 0, "" },
|
||||
{ "overlong", overlong, sizeof overlong, 0, "" },
|
||||
{ "surrogate", surrogate, sizeof surrogate, 0, "" },
|
||||
{ "out of range", outofrange, sizeof outofrange, 0, "" },
|
||||
{ "bad tail", badtail, sizeof badtail, 0, "" },
|
||||
};
|
||||
char full[Maxrunes+2];
|
||||
char incomplete[] = { (char)0xea, (char)0xb0 };
|
||||
Str s;
|
||||
int i;
|
||||
|
||||
s = mkstr("A한😀");
|
||||
CT_EQ_INT(t, 3, s.n);
|
||||
checkstr(t, "round trip", "A한😀", &s);
|
||||
sinit(&s, incomplete, sizeof incomplete);
|
||||
CT_EQ_INT(t, 0, s.n);
|
||||
for(i = 0; i < nelem(cases); i++){
|
||||
memset(&s, 0xa5, sizeof s);
|
||||
if(sinit(&s, cases[i].src, cases[i].n) != cases[i].ok)
|
||||
CT_ERRORF(t, "%s: wrong validity", cases[i].name);
|
||||
checkstr(t, cases[i].name, cases[i].want, &s);
|
||||
}
|
||||
memset(full, 'a', sizeof full);
|
||||
full[sizeof full-1] = '\0';
|
||||
sinit(&s, full, strlen(full));
|
||||
CT_EQ_INT(t, Maxrunes, s.n);
|
||||
CT_CHECK(t, !sinit(&s, full, strlen(full)));
|
||||
CT_EQ_INT(t, 0, s.n);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -45,7 +76,6 @@ str_utf8_capacity(struct ct *t)
|
||||
int n;
|
||||
char *want;
|
||||
} cases[] = {
|
||||
{ 0, 0, nil },
|
||||
{ 1, 0, "" },
|
||||
{ 2, 1, "a" },
|
||||
{ 3, 1, "a" },
|
||||
@@ -64,10 +94,6 @@ str_utf8_capacity(struct ct *t)
|
||||
if(n != cases[i].n)
|
||||
CT_ERRORF(t, "size %d: want length %d, got %d",
|
||||
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)
|
||||
CT_ERRORF(t, "size %d: want \"%s\", got \"%s\"",
|
||||
cases[i].size, cases[i].want, buf);
|
||||
@@ -89,10 +115,6 @@ str_invalid_and_full_appends(struct ct *t)
|
||||
Str s;
|
||||
int i;
|
||||
|
||||
sinit(&s, nil, 1);
|
||||
CT_EQ_INT(t, 0, s.n);
|
||||
sinit(&s, "x", -1);
|
||||
CT_EQ_INT(t, 0, s.n);
|
||||
for(i = 0; i < Maxrunes; i++)
|
||||
s.r[i] = 'a';
|
||||
s.n = Maxrunes;
|
||||
|
||||
74
tests/test.h
74
tests/test.h
@@ -1,22 +1,50 @@
|
||||
#include "../cutest/cutest.h"
|
||||
#include "../dat.h"
|
||||
#include "../fn.h"
|
||||
/* Included after dat.h and fn.h (or after the source under test). */
|
||||
#include "cutest/cutest.h"
|
||||
|
||||
extern Lang testvi;
|
||||
|
||||
/*
|
||||
* A test's stand-in for imthread: handles engine requests from keyc,
|
||||
* tracing each one first. Requests whose op is holdop (every request
|
||||
* when holdop is Pumpall) wait on go before the engine sees them.
|
||||
*/
|
||||
typedef struct Pump Pump;
|
||||
struct Pump
|
||||
{
|
||||
Channel *trace;
|
||||
Channel *go;
|
||||
Channel *stop;
|
||||
Channel *done;
|
||||
int holdop;
|
||||
int active;
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
Pumpnone = -1,
|
||||
Pumpall = -2,
|
||||
};
|
||||
|
||||
Str mkstr(char*);
|
||||
int checkstr(struct ct*, char*, char*, Str*);
|
||||
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_edit_and_alias(struct ct*);
|
||||
void str_utf8_capacity(struct ct*);
|
||||
void str_invalid_and_full_appends(struct ct*);
|
||||
void hmap_set_replace_and_grow(struct ct*);
|
||||
void hmap_long_utf8_keys(struct ct*);
|
||||
void hmap_binary_keys_and_invalid_lengths(struct ct*);
|
||||
void trie_exact_prefix_and_duplicate(struct ct*);
|
||||
void trie_optional_outputs_and_invalid_lengths(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*);
|
||||
@@ -32,10 +60,15 @@ void engine_clears_candidates(struct ct*);
|
||||
void engine_backspace_clears_candidates(struct ct*);
|
||||
void engine_selects_visible_candidate(struct ct*);
|
||||
void engine_candidate_shortcut_modifiers(struct ct*);
|
||||
void engine_dictionary_queue_latest_wins(struct ct*);
|
||||
void engine_candidate_page_metadata(struct ct*);
|
||||
void engine_candidate_page_snapshots(struct ct*);
|
||||
void engine_candidate_page_movement(struct ct*);
|
||||
void engine_candidate_completion(struct ct*);
|
||||
void engine_active_owner_lifecycle(struct ct*);
|
||||
void engine_active_owner_reset(struct ct*);
|
||||
void engine_active_owner_caret(struct ct*);
|
||||
void engine_popup_preedit_capability(struct ct*);
|
||||
void engine_vietnamese_client_preedit(struct ct*);
|
||||
void engine_commit_contract(struct ct*);
|
||||
void engine_language_switch_state(struct ct*);
|
||||
void engine_telex_history_bound(struct ct*);
|
||||
@@ -50,9 +83,9 @@ void engine_emoji_queries(struct ct*);
|
||||
void engine_emoji_japanese_and_multirune(struct ct*);
|
||||
void engine_emoji_digit_aliases(struct ct*);
|
||||
void engine_emoji_navigation(struct ct*);
|
||||
void engine_search_candidate_keys(struct ct*);
|
||||
void engine_emoji_preedit_languages(struct ct*);
|
||||
void engine_emoji_start_and_unknown(struct ct*);
|
||||
void engine_emoji_dictionary_identity(struct ct*);
|
||||
void engine_hanja_search(struct ct*);
|
||||
void engine_hanja_unknown_and_cancel(struct ct*);
|
||||
void engine_hanja_korean_keys(struct ct*);
|
||||
@@ -61,9 +94,10 @@ void engine_hanja_input_languages(struct ct*);
|
||||
void engine_randomized_stress(struct ct*);
|
||||
void engine_full_boundary_passthrough(struct ct*);
|
||||
void dictionary_candidates(struct ct*);
|
||||
void dictionary_misses_clear_result(struct ct*);
|
||||
void dictionary_misses(struct ct*);
|
||||
void dictionary_emoji_identity(struct ct*);
|
||||
void ipc_masks_modifiers(struct ct*);
|
||||
void ipc_control_and_caret_frames(struct ct*);
|
||||
void ipc_runtime_path(struct ct*);
|
||||
void ipc_response_pack_boundaries(struct ct*);
|
||||
void ipc_response_empty_and_preedit(struct ct*);
|
||||
@@ -71,7 +105,25 @@ 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_startup_requires_ownership(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 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*);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
#include "test.h"
|
||||
|
||||
Str
|
||||
@@ -21,22 +23,82 @@ checkstr(struct ct *t, char *where, char *want, Str *got)
|
||||
where, want, buf);
|
||||
}
|
||||
|
||||
Str
|
||||
shownpre(Im *state)
|
||||
void
|
||||
checkenginepreedit(struct ct *t, char *want)
|
||||
{
|
||||
Str mapped, shown;
|
||||
Str preedit;
|
||||
|
||||
if(state->l->lang == LangJP || state->l->lang == LangJPK){
|
||||
shown = state->pre;
|
||||
if(state->raw.n == 0)
|
||||
return shown;
|
||||
if(mapget(state->l->map, &state->raw, &mapped))
|
||||
sappend(&shown, &mapped);
|
||||
else
|
||||
sappend(&shown, &state->raw);
|
||||
return shown;
|
||||
}
|
||||
if(state->l->map != nil && mapget(state->l->map, &state->pre, &shown))
|
||||
return shown;
|
||||
return state->pre;
|
||||
testenginepreedit(&preedit);
|
||||
checkstr(t, "engine preedit", want, &preedit);
|
||||
}
|
||||
|
||||
static void
|
||||
pumpthread(void *arg)
|
||||
{
|
||||
Pump *p;
|
||||
Keyreq req;
|
||||
uchar token;
|
||||
Alt alts[] = {
|
||||
{nil, &req, CHANRCV, nil},
|
||||
{nil, &token, CHANRCV, nil},
|
||||
{nil, nil, CHANEND, nil},
|
||||
};
|
||||
|
||||
p = arg;
|
||||
alts[0].c = keyc;
|
||||
alts[1].c = p->stop;
|
||||
for(;;)
|
||||
switch(alt(alts)){
|
||||
case 0:
|
||||
chansend(p->trace, &req);
|
||||
if(p->holdop == Pumpall || p->holdop == req.op)
|
||||
chanrecv(p->go, &token);
|
||||
testenginehandle(&req);
|
||||
break;
|
||||
case 1:
|
||||
chansend(p->done, &token);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* ntrace is the trace channel's depth: 0 makes every trace a rendezvous. */
|
||||
void
|
||||
pumpstart(Pump *p, int ntrace)
|
||||
{
|
||||
memset(p, 0, sizeof *p);
|
||||
p->trace = chancreate(sizeof(Keyreq), ntrace);
|
||||
p->go = chancreate(sizeof(uchar), 0);
|
||||
p->stop = chancreate(sizeof(uchar), 0);
|
||||
p->done = chancreate(sizeof(uchar), 0);
|
||||
p->holdop = Pumpnone;
|
||||
/* Its own proc: tests block in socket I/O while the engine runs. */
|
||||
proccreate(pumpthread, p, 8192);
|
||||
p->active = 1;
|
||||
}
|
||||
|
||||
void
|
||||
pumphold(Pump *p, int op)
|
||||
{
|
||||
p->holdop = op;
|
||||
}
|
||||
|
||||
/* Every held request must have been let go before stopping. */
|
||||
void
|
||||
pumpstop(Pump *p)
|
||||
{
|
||||
Keyreq req;
|
||||
uchar token;
|
||||
|
||||
if(!p->active)
|
||||
return;
|
||||
while(channbrecv(p->trace, &req) > 0)
|
||||
;
|
||||
token = 0;
|
||||
chansend(p->stop, &token);
|
||||
chanrecv(p->done, &token);
|
||||
chanfree(p->trace);
|
||||
chanfree(p->go);
|
||||
chanfree(p->stop);
|
||||
chanfree(p->done);
|
||||
p->active = 0;
|
||||
}
|
||||
|
||||
@@ -1,54 +1,71 @@
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
#include "test.h"
|
||||
|
||||
void
|
||||
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;
|
||||
int n;
|
||||
Str key;
|
||||
int i, match, n;
|
||||
|
||||
trie = trieopen("data/trie.map");
|
||||
v = trieget(trie, "a", 1, &n);
|
||||
if(!CT_CHECK(t, v != nil))
|
||||
goto cleanup;
|
||||
CT_EQ_INT(t, 5, n);
|
||||
CT_EQ_MEM(t, "alpha", v, n);
|
||||
sentinel = "unchanged";
|
||||
v = sentinel;
|
||||
n = 77;
|
||||
CT_CHECK(t, trielookup(trie, "dupli", 5, &v, &n));
|
||||
CT_EQ_PTR(t, sentinel, v);
|
||||
CT_EQ_INT(t, 77, n);
|
||||
v = trieget(trie, "duplicate", 9, &n);
|
||||
if(!CT_CHECK(t, v != nil))
|
||||
goto cleanup;
|
||||
CT_EQ_INT(t, 6, n);
|
||||
CT_EQ_MEM(t, "second", v, n);
|
||||
CT_EQ_PTR(t, nil, trieget(trie, "missing", 7, &n));
|
||||
cleanup:
|
||||
for(i = 0; i < nelem(cases); i++){
|
||||
key = mkstr(cases[i].key);
|
||||
match = trielookup(trie, &key, &v, &n);
|
||||
if(match != cases[i].match){
|
||||
CT_ERRORF(t, "case %d: want match %d, got %d",
|
||||
i, cases[i].match, match);
|
||||
continue;
|
||||
}
|
||||
if(cases[i].want == nil){
|
||||
CT_EQ_PTR(t, nil, v);
|
||||
CT_EQ_INT(t, 0, n);
|
||||
}else{
|
||||
CT_EQ_INT(t, strlen(cases[i].want), n);
|
||||
CT_EQ_MEM(t, cases[i].want, v, n);
|
||||
}
|
||||
}
|
||||
trieclose(trie);
|
||||
}
|
||||
|
||||
void
|
||||
trie_optional_outputs_and_invalid_lengths(struct ct *t)
|
||||
trie_put_and_unloaded(struct ct *t)
|
||||
{
|
||||
char *v;
|
||||
Trie *trie;
|
||||
Str key;
|
||||
int n;
|
||||
|
||||
trie = trieopen("data/trie.map");
|
||||
CT_CHECK(t, trieget(trie, "a", 1, nil) != nil);
|
||||
v = nil;
|
||||
CT_CHECK(t, trielookup(trie, "a", 1, &v, nil));
|
||||
CT_CHECK(t, v != nil);
|
||||
n = -1;
|
||||
CT_CHECK(t, trielookup(trie, "a", 1, nil, &n));
|
||||
CT_EQ_INT(t, 5, n);
|
||||
CT_CHECK(t, trielookup(trie, "dupli", 5, nil, nil));
|
||||
CT_EQ_PTR(t, nil, trieget(trie, nil, 1, &n));
|
||||
CT_EQ_PTR(t, nil, trieget(trie, "a", -1, &n));
|
||||
CT_CHECK(t, !trielookup(trie, nil, 1, &v, &n));
|
||||
CT_CHECK(t, !trielookup(trie, "a", -1, &v, &n));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -84,13 +101,12 @@ transmap_states(struct ct *t)
|
||||
int eat;
|
||||
char *emit;
|
||||
char *next;
|
||||
char *mapped;
|
||||
} cases[] = {
|
||||
{ "", 'k', 1, "", "k", "" },
|
||||
{ "k", 'a', 1, "", "ka", "か" },
|
||||
{ "ka", 's', 1, "か", "s", "" },
|
||||
{ "ka", 'q', 0, "か", "", "" },
|
||||
{ "k", 'q', 0, "k", "", "" },
|
||||
{ "", 'k', 1, "", "k" },
|
||||
{ "k", 'a', 1, "", "ka" },
|
||||
{ "ka", 's', 1, "か", "s" },
|
||||
{ "ka", 'q', 0, "か", "" },
|
||||
{ "k", 'q', 0, "k", "" },
|
||||
};
|
||||
Emit e;
|
||||
Im state;
|
||||
@@ -110,7 +126,6 @@ transmap_states(struct ct *t)
|
||||
i, cases[i].eat, e.eat);
|
||||
checkstr(t, "emit", cases[i].emit, &e.s);
|
||||
checkstr(t, "next", cases[i].next, &e.next);
|
||||
checkstr(t, "mapped", cases[i].mapped, &e.dict);
|
||||
}
|
||||
trieclose(fixture);
|
||||
state.l->map = saved;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#define CT_IMPLEMENTATION
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
#include "test.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
Channel *drawc;
|
||||
Channel *keyc;
|
||||
Channel *dictreqc;
|
||||
Channel *dictresc;
|
||||
Lang testvi;
|
||||
|
||||
void
|
||||
@@ -61,16 +61,14 @@ testmapinit(void)
|
||||
testvi.map = trieopen("../map/telex.map");
|
||||
}
|
||||
|
||||
#ifndef STRESS
|
||||
static const struct ct_test tests[] = {
|
||||
{ "str/init-utf8", str_init_utf8 },
|
||||
{ "str/edit-and-alias", str_edit_and_alias },
|
||||
{ "str/utf8-capacity", str_utf8_capacity },
|
||||
{ "str/invalid-full-appends", str_invalid_and_full_appends },
|
||||
{ "hmap/set-replace-grow", hmap_set_replace_and_grow },
|
||||
{ "hmap/long-utf8-keys", hmap_long_utf8_keys },
|
||||
{ "hmap/binary-invalid-lengths", hmap_binary_keys_and_invalid_lengths },
|
||||
{ "trie/exact-prefix-duplicate", trie_exact_prefix_and_duplicate },
|
||||
{ "trie/optional-invalid-lengths", trie_optional_outputs_and_invalid_lengths },
|
||||
{ "trie/put-and-unloaded", trie_put_and_unloaded },
|
||||
{ "popup/layout", popup_layout },
|
||||
{ "font/render", font_render },
|
||||
{ "map/production-lifecycle", production_maps_load },
|
||||
@@ -86,10 +84,15 @@ static const struct ct_test tests[] = {
|
||||
{ "engine/backspace-clears-candidates", engine_backspace_clears_candidates },
|
||||
{ "engine/selects-visible-candidate", engine_selects_visible_candidate },
|
||||
{ "engine/candidate-shortcut-modifiers", engine_candidate_shortcut_modifiers },
|
||||
{ "engine/dictionary-queue-latest", engine_dictionary_queue_latest_wins },
|
||||
{ "engine/candidate-page-metadata", engine_candidate_page_metadata },
|
||||
{ "engine/candidate-page-snapshots", engine_candidate_page_snapshots },
|
||||
{ "engine/candidate-page-movement", engine_candidate_page_movement },
|
||||
{ "engine/candidate-completion", engine_candidate_completion },
|
||||
{ "engine/active-owner-lifecycle", engine_active_owner_lifecycle },
|
||||
{ "engine/active-owner-reset", engine_active_owner_reset },
|
||||
{ "engine/active-owner-caret", engine_active_owner_caret },
|
||||
{ "engine/popup-preedit-capability", engine_popup_preedit_capability },
|
||||
{ "engine/vietnamese-client-preedit", engine_vietnamese_client_preedit },
|
||||
{ "engine/commit-contract", engine_commit_contract },
|
||||
{ "engine/language-switch-state", engine_language_switch_state },
|
||||
{ "engine/telex-history-bound", engine_telex_history_bound },
|
||||
@@ -104,20 +107,20 @@ static const struct ct_test tests[] = {
|
||||
{ "engine/emoji-japanese-multirune", engine_emoji_japanese_and_multirune },
|
||||
{ "engine/emoji-digit-aliases", engine_emoji_digit_aliases },
|
||||
{ "engine/emoji-navigation", engine_emoji_navigation },
|
||||
{ "engine/search-candidate-keys", engine_search_candidate_keys },
|
||||
{ "engine/emoji-preedit-languages", engine_emoji_preedit_languages },
|
||||
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
|
||||
{ "engine/emoji-dictionary-identity", engine_emoji_dictionary_identity },
|
||||
{ "engine/hanja-search", engine_hanja_search },
|
||||
{ "engine/hanja-unknown-cancel", engine_hanja_unknown_and_cancel },
|
||||
{ "engine/hanja-korean-keys", engine_hanja_korean_keys },
|
||||
{ "engine/hanja-backspace", engine_hanja_backspace },
|
||||
{ "engine/hanja-input-languages", engine_hanja_input_languages },
|
||||
{ "engine/randomized-stress", engine_randomized_stress },
|
||||
{ "engine/full-boundary-passthrough", engine_full_boundary_passthrough },
|
||||
{ "dict/candidates", dictionary_candidates },
|
||||
{ "dict/misses-clear-result", dictionary_misses_clear_result },
|
||||
{ "dict/misses", dictionary_misses },
|
||||
{ "dict/emoji-identity", dictionary_emoji_identity },
|
||||
{ "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 },
|
||||
@@ -125,11 +128,34 @@ static const struct ct_test tests[] = {
|
||||
{ "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/startup-requires-ownership", ibus_startup_requires_ownership },
|
||||
{ "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 },
|
||||
{ "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 },
|
||||
};
|
||||
#else
|
||||
static const struct ct_test tests[] = {
|
||||
{ "engine/randomized-stress", engine_randomized_stress },
|
||||
};
|
||||
#endif
|
||||
|
||||
void
|
||||
threadmain(int argc, char **argv)
|
||||
@@ -138,8 +164,6 @@ threadmain(int argc, char **argv)
|
||||
|
||||
drawc = chancreate(sizeof(Drawcmd), 4);
|
||||
keyc = chancreate(sizeof(Keyreq), 0);
|
||||
dictreqc = chancreate(sizeof(Dictreq), 64);
|
||||
dictresc = chancreate(sizeof(Dictres), 0);
|
||||
testmapinit();
|
||||
status = CT_RUN_ARGS(tests, argc, argv);
|
||||
for(i = 0; i < nlang; i++){
|
||||
@@ -150,7 +174,5 @@ threadmain(int argc, char **argv)
|
||||
testvi.map = nil;
|
||||
chanfree(drawc);
|
||||
chanfree(keyc);
|
||||
chanfree(dictreqc);
|
||||
chanfree(dictresc);
|
||||
threadexitsall(status == 0 ? nil : "tests failed");
|
||||
}
|
||||
|
||||
@@ -1,25 +1,14 @@
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
#include "test.h"
|
||||
|
||||
static void
|
||||
typevi(struct ct *t, char *keys, char *want)
|
||||
{
|
||||
Emit e;
|
||||
Im state;
|
||||
Str out, shown;
|
||||
char *p;
|
||||
Str out, raw;
|
||||
|
||||
memset(&state, 0, sizeof state);
|
||||
state.l = &testvi;
|
||||
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);
|
||||
raw = mkstr(keys);
|
||||
transstr(&testvi, &raw, &out);
|
||||
checkstr(t, keys, want, &out);
|
||||
}
|
||||
|
||||
@@ -46,6 +35,11 @@ vietnamese_transitions(struct ct *t)
|
||||
{ "gias", "giá" },
|
||||
{ "Gias", "Giá" },
|
||||
{ "gif", "gì" },
|
||||
{ "cuar", "của" },
|
||||
{ "vowis", "với" },
|
||||
{ "cuwar", "cửa" },
|
||||
{ "nguyeenx", "nguyễn" },
|
||||
{ "dduwowngf", "đường" },
|
||||
{ "\\s", "s" },
|
||||
{ "a\\s", "as" },
|
||||
};
|
||||
@@ -78,7 +72,7 @@ vietnamese_backspace(struct ct *t)
|
||||
state.raw = mkstr(cases[i].raw);
|
||||
backvi(&state);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
1106
tests/xim_adapter_test.c
Normal file
1106
tests/xim_adapter_test.c
Normal file
File diff suppressed because it is too large
Load Diff
822
tests/xim_live_test.c
Normal file
822
tests/xim_live_test.c
Normal file
@@ -0,0 +1,822 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <locale.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "ipc.h"
|
||||
#include "live.h"
|
||||
|
||||
enum
|
||||
{
|
||||
Eventtimeout = 4000,
|
||||
};
|
||||
|
||||
typedef struct Child Child;
|
||||
typedef struct Run Run;
|
||||
typedef struct Prelog Prelog;
|
||||
|
||||
struct Child
|
||||
{
|
||||
pid_t pid;
|
||||
pid_t pgid;
|
||||
char log[320];
|
||||
};
|
||||
|
||||
struct Run
|
||||
{
|
||||
Live l;
|
||||
Child xvfb;
|
||||
Child daemon;
|
||||
char display[32];
|
||||
};
|
||||
|
||||
struct Prelog
|
||||
{
|
||||
int start;
|
||||
int nonempty;
|
||||
int empty;
|
||||
int done;
|
||||
int doneafterempty;
|
||||
int badorder;
|
||||
};
|
||||
|
||||
static int xerror;
|
||||
|
||||
static int
|
||||
childlog(char *path)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0600);
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int
|
||||
spawnxvfb(Run *r, char *program)
|
||||
{
|
||||
char fdstr[16], line[32];
|
||||
int displayfd[2], errfd, flags, n, status;
|
||||
int64_t deadline;
|
||||
pid_t pid;
|
||||
struct pollfd pfd;
|
||||
ssize_t nr;
|
||||
|
||||
if(pipe2(displayfd, O_CLOEXEC) < 0)
|
||||
return fail("create Xvfb display pipe: %s", strerror(errno));
|
||||
errfd = childlog(r->xvfb.log);
|
||||
if(errfd < 0){
|
||||
close(displayfd[0]);
|
||||
close(displayfd[1]);
|
||||
return fail("open Xvfb log: %s", strerror(errno));
|
||||
}
|
||||
pid = fork();
|
||||
if(pid < 0){
|
||||
close(displayfd[0]);
|
||||
close(displayfd[1]);
|
||||
close(errfd);
|
||||
return fail("fork Xvfb: %s", strerror(errno));
|
||||
}
|
||||
if(pid == 0){
|
||||
setpgid(0, 0);
|
||||
close(displayfd[0]);
|
||||
flags = fcntl(displayfd[1], F_GETFD);
|
||||
if(flags < 0 || fcntl(displayfd[1], F_SETFD,
|
||||
flags & ~FD_CLOEXEC) < 0)
|
||||
_exit(126);
|
||||
if(dup2(errfd, STDOUT_FILENO) < 0 ||
|
||||
dup2(errfd, STDERR_FILENO) < 0)
|
||||
_exit(126);
|
||||
close(errfd);
|
||||
snprintf(fdstr, sizeof fdstr, "%d", displayfd[1]);
|
||||
execlp(program, program, "-displayfd", fdstr,
|
||||
"-screen", "0", "800x600x24", "-nolisten", "tcp",
|
||||
"-noreset", (char*)0);
|
||||
_exit(127);
|
||||
}
|
||||
close(displayfd[1]);
|
||||
close(errfd);
|
||||
if((setpgid(pid, pid) < 0 && errno != EACCES) || getpgid(pid) != pid){
|
||||
kill(pid, SIGKILL);
|
||||
while(waitpid(pid, &status, 0) < 0 && errno == EINTR)
|
||||
;
|
||||
close(displayfd[0]);
|
||||
return fail("isolate Xvfb process group: %s", strerror(errno));
|
||||
}
|
||||
r->xvfb.pid = pid;
|
||||
r->xvfb.pgid = pid;
|
||||
pfd.fd = displayfd[0];
|
||||
pfd.events = POLLIN|POLLHUP;
|
||||
deadline = nowms() + Starttimeout;
|
||||
n = 0;
|
||||
while(n + 1 < (int)sizeof line){
|
||||
pfd.revents = 0;
|
||||
status = poll(&pfd, 1, leftms(deadline));
|
||||
if(status < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(status <= 0)
|
||||
break;
|
||||
nr = read(displayfd[0], line + n, sizeof line - n - 1);
|
||||
if(nr > 0){
|
||||
n += nr;
|
||||
line[n] = '\0';
|
||||
if(strchr(line, '\n') != NULL)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
if(nr < 0 && errno == EINTR)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
close(displayfd[0]);
|
||||
if(n == 0 || strchr(line, '\n') == NULL)
|
||||
return fail("Xvfb did not report a private display");
|
||||
line[strcspn(line, "\r\n")] = '\0';
|
||||
for(n = 0; line[n] != '\0'; n++)
|
||||
if(line[n] < '0' || line[n] > '9')
|
||||
return fail("invalid Xvfb display number %s", line);
|
||||
if(snprintf(r->display, sizeof r->display, ":%s", line)
|
||||
>= (int)sizeof r->display)
|
||||
return fail("Xvfb display number is too long");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
spawndaemon(Run *r, char *program, char *mapdir)
|
||||
{
|
||||
int errfd;
|
||||
pid_t pid;
|
||||
|
||||
errfd = childlog(r->daemon.log);
|
||||
if(errfd < 0)
|
||||
return fail("open daemon log: %s", strerror(errno));
|
||||
pid = fork();
|
||||
if(pid < 0){
|
||||
close(errfd);
|
||||
return fail("fork daemon: %s", strerror(errno));
|
||||
}
|
||||
if(pid == 0){
|
||||
setpgid(0, 0);
|
||||
if(dup2(errfd, STDOUT_FILENO) < 0 ||
|
||||
dup2(errfd, STDERR_FILENO) < 0)
|
||||
_exit(126);
|
||||
close(errfd);
|
||||
execl(program, program, mapdir, (char*)0);
|
||||
_exit(127);
|
||||
}
|
||||
close(errfd);
|
||||
if((setpgid(pid, pid) < 0 && errno != EACCES) || getpgid(pid) != pid){
|
||||
kill(pid, SIGKILL);
|
||||
while(waitpid(pid, NULL, 0) < 0 && errno == EINTR)
|
||||
;
|
||||
return fail("isolate daemon process group: %s", strerror(errno));
|
||||
}
|
||||
r->daemon.pid = pid;
|
||||
r->daemon.pgid = pid;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
waitsocket(Run *r)
|
||||
{
|
||||
struct stat st;
|
||||
int status;
|
||||
int64_t deadline;
|
||||
|
||||
deadline = nowms() + Starttimeout;
|
||||
while(leftms(deadline) > 0){
|
||||
if(lstat(r->l.socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
||||
(st.st_mode & 0777) == 0600)
|
||||
return 1;
|
||||
if(waitpid(r->daemon.pid, &status, WNOHANG) == r->daemon.pid){
|
||||
r->daemon.pid = -1;
|
||||
return fail("daemon exited before creating its IPC socket");
|
||||
}
|
||||
pausems(20);
|
||||
}
|
||||
return fail("timed out waiting for daemon IPC socket");
|
||||
}
|
||||
|
||||
static int
|
||||
start(Run *r, char *daemon, char *mapdir, char *xvfb)
|
||||
{
|
||||
memset(r, 0, sizeof *r);
|
||||
r->xvfb.pid = r->daemon.pid = -1;
|
||||
r->xvfb.pgid = r->daemon.pgid = -1;
|
||||
if(!livesetup(&r->l, "xim"))
|
||||
return 0;
|
||||
if(snprintf(r->xvfb.log, sizeof r->xvfb.log, "%s/xvfb.log", r->l.root)
|
||||
>= (int)sizeof r->xvfb.log ||
|
||||
snprintf(r->daemon.log, sizeof r->daemon.log, "%s/daemon.log", r->l.root)
|
||||
>= (int)sizeof r->daemon.log)
|
||||
return fail("temporary path is too long");
|
||||
if(!spawnxvfb(r, xvfb))
|
||||
return 0;
|
||||
if(setenv("DISPLAY", r->display, 1) < 0 ||
|
||||
setenv("XMODIFIERS", "@im=strans", 1) < 0 ||
|
||||
setenv("XDG_RUNTIME_DIR", r->l.runtime, 1) < 0 ||
|
||||
setenv("XDG_CONFIG_HOME", r->l.config, 1) < 0 ||
|
||||
setenv("HOME", r->l.home, 1) < 0 ||
|
||||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
||||
unsetenv("IBUS_ADDRESS") < 0)
|
||||
return fail("set private environment: %s", strerror(errno));
|
||||
if(!spawndaemon(r, daemon, mapdir))
|
||||
return 0;
|
||||
return waitsocket(r);
|
||||
}
|
||||
|
||||
static void
|
||||
showlog(char *name)
|
||||
{
|
||||
char buf[4096];
|
||||
ssize_t n;
|
||||
int fd;
|
||||
|
||||
fd = open(name, O_RDONLY|O_CLOEXEC);
|
||||
if(fd < 0)
|
||||
return;
|
||||
while((n = read(fd, buf, sizeof buf)) > 0)
|
||||
fwrite(buf, 1, n, stderr);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void
|
||||
stopchild(Child *c)
|
||||
{
|
||||
int i, status;
|
||||
pid_t n;
|
||||
|
||||
if(c->pgid > 0 && kill(-c->pgid, SIGTERM) < 0 && errno != ESRCH)
|
||||
fail("terminate process group %ld: %s", (long)c->pgid,
|
||||
strerror(errno));
|
||||
if(c->pid > 0)
|
||||
for(i = 0; i < Stoptimeout / 20; i++){
|
||||
n = waitpid(c->pid, &status, WNOHANG);
|
||||
if(n == c->pid || (n < 0 && errno == ECHILD)){
|
||||
c->pid = -1;
|
||||
break;
|
||||
}
|
||||
if(n < 0 && errno != EINTR)
|
||||
break;
|
||||
pausems(20);
|
||||
}
|
||||
if(c->pgid > 0)
|
||||
kill(-c->pgid, SIGKILL);
|
||||
if(c->pid > 0){
|
||||
while(waitpid(c->pid, &status, 0) < 0 && errno == EINTR)
|
||||
;
|
||||
c->pid = -1;
|
||||
}
|
||||
c->pgid = -1;
|
||||
}
|
||||
|
||||
static void
|
||||
cleanup(Run *r, int noisy)
|
||||
{
|
||||
stopchild(&r->daemon);
|
||||
stopchild(&r->xvfb);
|
||||
if(noisy){
|
||||
fprintf(stderr, "%s: daemon log:\n", testname);
|
||||
showlog(r->daemon.log);
|
||||
fprintf(stderr, "%s: Xvfb log:\n", testname);
|
||||
showlog(r->xvfb.log);
|
||||
}
|
||||
if(r->daemon.log[0] != '\0')
|
||||
unlink(r->daemon.log);
|
||||
if(r->xvfb.log[0] != '\0')
|
||||
unlink(r->xvfb.log);
|
||||
liveclean(&r->l);
|
||||
}
|
||||
|
||||
static int
|
||||
xerr(Display *dpy, XErrorEvent *e)
|
||||
{
|
||||
char msg[128];
|
||||
|
||||
xerror++;
|
||||
XGetErrorText(dpy, e->error_code, msg, sizeof msg);
|
||||
fail("X error: %s (request %d.%d)", msg, e->request_code, e->minor_code);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pumpinput(Display *dpy, XIC ic, char *commit, size_t cap, int timeout)
|
||||
{
|
||||
struct pollfd pfd;
|
||||
XEvent ev;
|
||||
KeySym sym;
|
||||
Status status;
|
||||
char buf[Ipcfieldmax+1];
|
||||
int n;
|
||||
|
||||
XFlush(dpy);
|
||||
if(XPending(dpy) == 0){
|
||||
pfd.fd = ConnectionNumber(dpy);
|
||||
pfd.events = POLLIN;
|
||||
pfd.revents = 0;
|
||||
while(poll(&pfd, 1, timeout) < 0 && errno == EINTR)
|
||||
;
|
||||
}
|
||||
while(XPending(dpy) != 0){
|
||||
XNextEvent(dpy, &ev);
|
||||
if(XFilterEvent(&ev, None) || ic == NULL || ev.type != KeyPress)
|
||||
continue;
|
||||
n = Xutf8LookupString(ic, &ev.xkey, buf, sizeof buf - 1,
|
||||
&sym, &status);
|
||||
if(n < 0 || ((status == XLookupChars || status == XLookupBoth) &&
|
||||
((size_t)n >= cap || commit == NULL)))
|
||||
return 0;
|
||||
if((status == XLookupChars || status == XLookupBoth) && n != 0){
|
||||
memcpy(commit, buf, n);
|
||||
commit[n] = '\0';
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
pump(Display *dpy, int timeout)
|
||||
{
|
||||
pumpinput(dpy, NULL, NULL, 0, timeout);
|
||||
}
|
||||
|
||||
static int
|
||||
prestart(XIC ic, XPointer data, XPointer call)
|
||||
{
|
||||
Prelog *p;
|
||||
|
||||
(void)ic;
|
||||
(void)call;
|
||||
p = (Prelog*)data;
|
||||
if(p->start != p->done)
|
||||
p->badorder++;
|
||||
p->start++;
|
||||
return Ipcfieldmax;
|
||||
}
|
||||
|
||||
static void
|
||||
predraw(XIM im, XPointer data, XPointer call)
|
||||
{
|
||||
Prelog *p;
|
||||
XIMPreeditDrawCallbackStruct *draw;
|
||||
|
||||
(void)im;
|
||||
p = (Prelog*)data;
|
||||
draw = (XIMPreeditDrawCallbackStruct*)call;
|
||||
if(draw != NULL && draw->text != NULL && draw->text->length != 0){
|
||||
if(p->start != p->done + 1)
|
||||
p->badorder++;
|
||||
p->nonempty++;
|
||||
}else{
|
||||
if(p->start != p->done + 1)
|
||||
p->badorder++;
|
||||
p->empty++;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
predone(XIM im, XPointer data, XPointer call)
|
||||
{
|
||||
Prelog *p;
|
||||
|
||||
(void)im;
|
||||
(void)call;
|
||||
p = (Prelog*)data;
|
||||
if(p->empty > p->done)
|
||||
p->doneafterempty++;
|
||||
else
|
||||
p->badorder++;
|
||||
p->done++;
|
||||
}
|
||||
|
||||
static void
|
||||
precaret(XIM im, XPointer data, XPointer call)
|
||||
{
|
||||
(void)im;
|
||||
(void)data;
|
||||
(void)call;
|
||||
}
|
||||
|
||||
static XIC
|
||||
callbackic(XIM im, Window win, Prelog *log)
|
||||
{
|
||||
union {
|
||||
int (*start)(XIC, XPointer, XPointer);
|
||||
XIMProc xim;
|
||||
} fn;
|
||||
XIMCallback start, draw, done, caret;
|
||||
XVaNestedList pre;
|
||||
XIC ic;
|
||||
|
||||
start.client_data = (XPointer)log;
|
||||
fn.start = prestart;
|
||||
start.callback = fn.xim;
|
||||
draw.client_data = (XPointer)log;
|
||||
draw.callback = predraw;
|
||||
done.client_data = (XPointer)log;
|
||||
done.callback = predone;
|
||||
caret.client_data = (XPointer)log;
|
||||
caret.callback = precaret;
|
||||
pre = XVaCreateNestedList(0,
|
||||
XNPreeditStartCallback, &start,
|
||||
XNPreeditDrawCallback, &draw,
|
||||
XNPreeditDoneCallback, &done,
|
||||
XNPreeditCaretCallback, &caret,
|
||||
NULL);
|
||||
if(pre == NULL)
|
||||
return NULL;
|
||||
ic = XCreateIC(im,
|
||||
XNInputStyle, XIMPreeditCallbacks|XIMStatusNothing,
|
||||
XNClientWindow, win,
|
||||
XNFocusWindow, win,
|
||||
XNPreeditAttributes, pre,
|
||||
NULL);
|
||||
XFree(pre);
|
||||
return ic;
|
||||
}
|
||||
|
||||
static int
|
||||
sendkey(Display *dpy, XIC ic, Window win, KeySym sym, unsigned int state,
|
||||
char *commit, size_t cap)
|
||||
{
|
||||
XKeyPressedEvent key;
|
||||
XEvent ev;
|
||||
KeySym got;
|
||||
Status status;
|
||||
char buf[Ipcfieldmax+1];
|
||||
int n;
|
||||
|
||||
if(cap != 0)
|
||||
commit[0] = '\0';
|
||||
memset(&key, 0, sizeof key);
|
||||
key.type = KeyPress;
|
||||
key.display = dpy;
|
||||
key.window = win;
|
||||
key.root = DefaultRootWindow(dpy);
|
||||
key.time = CurrentTime;
|
||||
key.x = key.y = key.x_root = key.y_root = 1;
|
||||
key.same_screen = True;
|
||||
key.keycode = XKeysymToKeycode(dpy, sym);
|
||||
key.state = state;
|
||||
memset(&ev, 0, sizeof ev);
|
||||
ev.xkey = key;
|
||||
if(XFilterEvent(&ev, None)){
|
||||
return pumpinput(dpy, ic, commit, cap, 20);
|
||||
}
|
||||
n = Xutf8LookupString(ic, &key, buf, sizeof buf - 1, &got, &status);
|
||||
if(n < 0)
|
||||
return fail("Xutf8LookupString failed for keysym %#lx",
|
||||
(unsigned long)sym);
|
||||
if((status == XLookupChars || status == XLookupBoth) && n != 0){
|
||||
if((size_t)n >= cap)
|
||||
return fail("commit buffer overflow");
|
||||
memcpy(commit, buf, n);
|
||||
commit[n] = '\0';
|
||||
}
|
||||
return pumpinput(dpy, ic, commit, cap, 20);
|
||||
}
|
||||
|
||||
static int
|
||||
waitcommit(Display *dpy, XIC ic, char *commit, size_t cap)
|
||||
{
|
||||
int64_t deadline;
|
||||
|
||||
deadline = nowms() + Eventtimeout;
|
||||
while(leftms(deadline) > 0){
|
||||
if(!pumpinput(dpy, ic, commit, cap, leftms(deadline)))
|
||||
return fail("read committed XIM text");
|
||||
if(commit[0] != '\0')
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
waitpreedit(Display *dpy, Prelog *p)
|
||||
{
|
||||
int64_t deadline;
|
||||
|
||||
deadline = nowms() + Eventtimeout;
|
||||
while(leftms(deadline) > 0){
|
||||
pump(dpy, leftms(deadline));
|
||||
if(p->empty != 0 || p->done != 0 || p->badorder != 0)
|
||||
return -1;
|
||||
if(p->start != 0 && p->nonempty != 0)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static XIM
|
||||
openim(Display *dpy)
|
||||
{
|
||||
XIM im;
|
||||
int64_t deadline;
|
||||
|
||||
deadline = nowms() + Starttimeout;
|
||||
do{
|
||||
im = XOpenIM(dpy, NULL, "strans", "Strans");
|
||||
if(im != NULL)
|
||||
return im;
|
||||
pump(dpy, 20);
|
||||
}while(leftms(deadline) > 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
testcallbacks(Display *dpy, XIM im, Window win)
|
||||
{
|
||||
char commit[Ipcfieldmax+1], *reset;
|
||||
Prelog log;
|
||||
XIC ic;
|
||||
|
||||
memset(&log, 0, sizeof log);
|
||||
ic = callbackic(im, win, &log);
|
||||
if(ic == NULL)
|
||||
return fail("create PreeditCallbacks input context");
|
||||
XSetICFocus(ic);
|
||||
if(!sendkey(dpy, ic, win, XK_s, ControlMask, commit, sizeof commit) ||
|
||||
!sendkey(dpy, ic, win, XK_r, 0, commit, sizeof commit)){
|
||||
XDestroyIC(ic);
|
||||
return 0;
|
||||
}
|
||||
if(waitpreedit(dpy, &log) != 1){
|
||||
int start, draw, empty, done;
|
||||
|
||||
start = log.start;
|
||||
draw = log.nonempty;
|
||||
empty = log.empty;
|
||||
done = log.done;
|
||||
XDestroyIC(ic);
|
||||
return fail("invalid callback preedit: start=%d draw=%d empty=%d done=%d",
|
||||
start, draw, empty, done);
|
||||
}
|
||||
pump(dpy, 50);
|
||||
if(log.start != 1 || log.nonempty < 1 || log.done != 0 ||
|
||||
log.empty != 0 || log.badorder != 0){
|
||||
int start, draw, empty, done, ordered, bad;
|
||||
|
||||
start = log.start;
|
||||
draw = log.nonempty;
|
||||
empty = log.empty;
|
||||
done = log.done;
|
||||
ordered = log.doneafterempty;
|
||||
bad = log.badorder;
|
||||
XDestroyIC(ic);
|
||||
return fail("bad initial callback sequence: start=%d draw=%d empty=%d done=%d ordered=%d bad=%d",
|
||||
start, draw, empty, done, ordered, bad);
|
||||
}
|
||||
reset = Xutf8ResetIC(ic);
|
||||
if(reset == NULL || strcmp(reset, "ㄱ") != 0){
|
||||
if(reset != NULL)
|
||||
XFree(reset);
|
||||
XDestroyIC(ic);
|
||||
return fail("ResetIC did not return pending preedit");
|
||||
}
|
||||
XFree(reset);
|
||||
if(!sendkey(dpy, ic, win, XK_r, 0, commit, sizeof commit) ||
|
||||
!sendkey(dpy, ic, win, XK_k, 0, commit, sizeof commit) ||
|
||||
!sendkey(dpy, ic, win, XK_Return, 0, commit, sizeof commit)){
|
||||
XDestroyIC(ic);
|
||||
return 0;
|
||||
}
|
||||
if(commit[0] == '\0' && !waitcommit(dpy, ic, commit, sizeof commit)){
|
||||
XDestroyIC(ic);
|
||||
return fail("input context did not compose after ResetIC");
|
||||
}
|
||||
if(strcmp(commit, "가") != 0){
|
||||
XDestroyIC(ic);
|
||||
return fail("post-reset composition did not contain 가");
|
||||
}
|
||||
XUnsetICFocus(ic);
|
||||
XDestroyIC(ic);
|
||||
pump(dpy, 100);
|
||||
if(log.badorder != 0)
|
||||
return fail("bad callback order after ResetIC");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* The popup is the one viewable override-redirect window on the private
|
||||
* display; created before the client window, it must still stack above it.
|
||||
*/
|
||||
static int
|
||||
popupabove(Display *dpy, Window client)
|
||||
{
|
||||
XWindowAttributes wa;
|
||||
Window root, parent, *kids;
|
||||
unsigned i, n;
|
||||
int64_t deadline;
|
||||
int above;
|
||||
|
||||
deadline = nowms() + Eventtimeout;
|
||||
for(;;){
|
||||
above = 0;
|
||||
root = DefaultRootWindow(dpy);
|
||||
if(!XQueryTree(dpy, root, &root, &parent, &kids, &n))
|
||||
return fail("query the window tree");
|
||||
for(i = 0; i < n; i++){
|
||||
if(kids[i] == client)
|
||||
above = 1;
|
||||
else if(XGetWindowAttributes(dpy, kids[i], &wa) &&
|
||||
wa.override_redirect && wa.map_state == IsViewable)
|
||||
break;
|
||||
}
|
||||
XFree(kids);
|
||||
if(i < n)
|
||||
return above || fail("popup is stacked below the client");
|
||||
if(leftms(deadline) == 0)
|
||||
return fail("popup did not appear");
|
||||
pump(dpy, 20);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
testnothing(Display *dpy, XIM im, Window win)
|
||||
{
|
||||
char commit[Ipcfieldmax+1];
|
||||
XIC ic;
|
||||
|
||||
ic = XCreateIC(im,
|
||||
XNInputStyle, XIMPreeditNothing|XIMStatusNothing,
|
||||
XNClientWindow, win,
|
||||
XNFocusWindow, win,
|
||||
NULL);
|
||||
if(ic == NULL)
|
||||
return fail("create PreeditNothing input context");
|
||||
XSetICFocus(ic);
|
||||
if(!sendkey(dpy, ic, win, XK_s, ControlMask, commit, sizeof commit)){
|
||||
XDestroyIC(ic);
|
||||
return 0;
|
||||
}
|
||||
pump(dpy, 100);
|
||||
if(!sendkey(dpy, ic, win, XK_r, 0, commit, sizeof commit) ||
|
||||
!popupabove(dpy, win) ||
|
||||
!sendkey(dpy, ic, win, XK_k, 0, commit, sizeof commit) ||
|
||||
!sendkey(dpy, ic, win, XK_Return, 0, commit, sizeof commit)){
|
||||
XDestroyIC(ic);
|
||||
return 0;
|
||||
}
|
||||
if(commit[0] == '\0' &&
|
||||
!waitcommit(dpy, ic, commit, sizeof commit)){
|
||||
XDestroyIC(ic);
|
||||
return fail("PreeditNothing composition produced no commit");
|
||||
}
|
||||
if(strcmp(commit, "\352\260\200") != 0){
|
||||
XDestroyIC(ic);
|
||||
return fail("PreeditNothing commit did not contain 가");
|
||||
}
|
||||
XUnsetICFocus(ic);
|
||||
XDestroyIC(ic);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
testposition(Display *dpy, XIM im, Window client, Window focus)
|
||||
{
|
||||
char commit[Ipcfieldmax+1];
|
||||
char **missing, *def;
|
||||
int nmissing;
|
||||
XPoint spot;
|
||||
XVaNestedList pre;
|
||||
XFontSet fontset;
|
||||
XIC ic;
|
||||
|
||||
missing = NULL;
|
||||
nmissing = 0;
|
||||
def = NULL;
|
||||
fontset = XCreateFontSet(dpy, "fixed", &missing, &nmissing, &def);
|
||||
if(missing != NULL)
|
||||
XFreeStringList(missing);
|
||||
if(fontset == NULL)
|
||||
return fail("create XIM position font set");
|
||||
spot.x = 7;
|
||||
spot.y = 11;
|
||||
pre = XVaCreateNestedList(0, XNSpotLocation, &spot,
|
||||
XNFontSet, fontset, NULL);
|
||||
if(pre == NULL){
|
||||
XFreeFontSet(dpy, fontset);
|
||||
return fail("create XNSpotLocation list");
|
||||
}
|
||||
ic = XCreateIC(im,
|
||||
XNInputStyle, XIMPreeditPosition|XIMStatusNothing,
|
||||
XNClientWindow, client,
|
||||
XNFocusWindow, focus,
|
||||
XNPreeditAttributes, pre,
|
||||
NULL);
|
||||
XFree(pre);
|
||||
if(ic == NULL){
|
||||
XFreeFontSet(dpy, fontset);
|
||||
return fail("create nested PreeditPosition input context");
|
||||
}
|
||||
XSetICFocus(ic);
|
||||
if(!sendkey(dpy, ic, focus, XK_s, ControlMask, commit, sizeof commit) ||
|
||||
!sendkey(dpy, ic, focus, XK_r, 0, commit, sizeof commit)){
|
||||
XDestroyIC(ic);
|
||||
XFreeFontSet(dpy, fontset);
|
||||
return 0;
|
||||
}
|
||||
spot.x = 13;
|
||||
spot.y = 17;
|
||||
pre = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
|
||||
if(pre == NULL || XSetICValues(ic, XNPreeditAttributes, pre, NULL) != NULL){
|
||||
if(pre != NULL)
|
||||
XFree(pre);
|
||||
XDestroyIC(ic);
|
||||
XFreeFontSet(dpy, fontset);
|
||||
return fail("update nested XNSpotLocation");
|
||||
}
|
||||
XFree(pre);
|
||||
if(!sendkey(dpy, ic, focus, XK_Escape, 0, commit, sizeof commit)){
|
||||
XDestroyIC(ic);
|
||||
XFreeFontSet(dpy, fontset);
|
||||
return 0;
|
||||
}
|
||||
XUnsetICFocus(ic);
|
||||
XDestroyIC(ic);
|
||||
XFreeFontSet(dpy, fontset);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
Display *dpy;
|
||||
Window client, focus, root;
|
||||
XIM im;
|
||||
Run run;
|
||||
int ok;
|
||||
char *xvfb;
|
||||
|
||||
testname = "xim_live_test";
|
||||
if(argc != 3 && argc != 4){
|
||||
fprintf(stderr, "usage: xim_live_test daemon mapdir [Xvfb]\n");
|
||||
return 2;
|
||||
}
|
||||
memset(&run, 0, sizeof run);
|
||||
run.xvfb.pid = run.daemon.pid = -1;
|
||||
run.xvfb.pgid = run.daemon.pgid = -1;
|
||||
xvfb = argc == 4 ? argv[3] : "Xvfb";
|
||||
ok = start(&run, argv[1], argv[2], xvfb);
|
||||
if(!ok){
|
||||
cleanup(&run, 1);
|
||||
return 1;
|
||||
}
|
||||
if(setlocale(LC_CTYPE, "C.UTF-8") == NULL || !XSupportsLocale() ||
|
||||
XSetLocaleModifiers("@im=strans") == NULL){
|
||||
fail("initialize UTF-8 X locale for strans");
|
||||
cleanup(&run, 1);
|
||||
return 1;
|
||||
}
|
||||
XSetErrorHandler(xerr);
|
||||
dpy = XOpenDisplay(run.display);
|
||||
if(dpy == NULL){
|
||||
fail("open private display %s", run.display);
|
||||
cleanup(&run, 1);
|
||||
return 1;
|
||||
}
|
||||
im = openim(dpy);
|
||||
if(im == NULL){
|
||||
fail("open strans XIM on private display %s", run.display);
|
||||
XCloseDisplay(dpy);
|
||||
cleanup(&run, 1);
|
||||
return 1;
|
||||
}
|
||||
root = DefaultRootWindow(dpy);
|
||||
client = XCreateSimpleWindow(dpy, root, 41, 53, 240, 100, 0, 0, 0);
|
||||
focus = XCreateSimpleWindow(dpy, client, 17, 19, 160, 50, 0, 0, 0);
|
||||
XSelectInput(dpy, client, KeyPressMask|StructureNotifyMask);
|
||||
XSelectInput(dpy, focus, KeyPressMask|StructureNotifyMask);
|
||||
XMapWindow(dpy, client);
|
||||
XMapWindow(dpy, focus);
|
||||
XSync(dpy, False);
|
||||
ok = testcallbacks(dpy, im, client) &&
|
||||
testnothing(dpy, im, client) &&
|
||||
testposition(dpy, im, client, focus);
|
||||
XDestroyWindow(dpy, focus);
|
||||
XDestroyWindow(dpy, client);
|
||||
XCloseIM(im);
|
||||
XSync(dpy, False);
|
||||
XCloseDisplay(dpy);
|
||||
if(xerror != 0){
|
||||
fail("observed %d X protocol errors", xerror);
|
||||
ok = 0;
|
||||
}
|
||||
cleanup(&run, !ok);
|
||||
if(!ok)
|
||||
return 1;
|
||||
printf("xim_live_test: ok\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <xcb-imdkit/encoding.h>
|
||||
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||
|
||||
uint32_t keymaplookup(const uint32_t*, int, uint16_t);
|
||||
char *ximcompound(const char*, size_t, size_t*);
|
||||
|
||||
enum
|
||||
{
|
||||
ShiftMask = 1<<0,
|
||||
LockMask = 1<<1,
|
||||
Group1 = 1<<13,
|
||||
};
|
||||
|
||||
static void
|
||||
checktext(const char *s)
|
||||
{
|
||||
char *ct, *utf8;
|
||||
size_t clen, len, ulen;
|
||||
|
||||
len = strlen(s);
|
||||
ct = ximcompound(s, len, &clen);
|
||||
assert(ct != NULL);
|
||||
utf8 = xcb_compound_text_to_utf8(ct, clen, &ulen);
|
||||
assert(utf8 != NULL);
|
||||
assert(ulen == len);
|
||||
assert(memcmp(utf8, s, len) == 0);
|
||||
free(utf8);
|
||||
free(ct);
|
||||
}
|
||||
|
||||
static void
|
||||
checkutf8run(void)
|
||||
{
|
||||
static const char s[] = "😀";
|
||||
char *ct;
|
||||
size_t n;
|
||||
|
||||
ct = ximcompound(s, sizeof s - 1, &n);
|
||||
assert(ct != NULL);
|
||||
assert(n >= 6);
|
||||
assert(memcmp(ct, "\033%G", 3) == 0);
|
||||
assert(memcmp(ct+n-3, "\033%@", 3) == 0);
|
||||
free(ct);
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
static const uint32_t letters[] = {
|
||||
XKB_KEY_a, XKB_KEY_A, XKB_KEY_Cyrillic_ef, XKB_KEY_Cyrillic_EF,
|
||||
};
|
||||
static const uint32_t inferred[] = { XKB_KEY_a, XKB_KEY_NoSymbol };
|
||||
static const uint32_t punctuation[] = { XKB_KEY_1, XKB_KEY_exclam };
|
||||
static const uint32_t missing[] = {
|
||||
XKB_KEY_a, XKB_KEY_A, XKB_KEY_NoSymbol, XKB_KEY_NoSymbol,
|
||||
};
|
||||
|
||||
xcb_compound_text_init();
|
||||
|
||||
assert(keymaplookup(letters, 4, 0) == XKB_KEY_a);
|
||||
assert(keymaplookup(letters, 4, ShiftMask) == XKB_KEY_A);
|
||||
assert(keymaplookup(letters, 4, LockMask) == XKB_KEY_A);
|
||||
assert(keymaplookup(letters, 4, LockMask|ShiftMask) == XKB_KEY_a);
|
||||
assert(keymaplookup(letters, 4, Group1) == XKB_KEY_Cyrillic_ef);
|
||||
assert(keymaplookup(letters, 4, Group1|ShiftMask) == XKB_KEY_Cyrillic_EF);
|
||||
assert(keymaplookup(inferred, 2, ShiftMask) == XKB_KEY_A);
|
||||
assert(keymaplookup(punctuation, 2, LockMask) == XKB_KEY_1);
|
||||
assert(keymaplookup(punctuation, 2, LockMask|ShiftMask) == XKB_KEY_exclam);
|
||||
assert(keymaplookup(missing, 4, Group1) == XKB_KEY_a);
|
||||
assert(keymaplookup(NULL, 0, 0) == XKB_KEY_NoSymbol);
|
||||
checktext("한글");
|
||||
checktext("😀");
|
||||
checktext("❤️");
|
||||
checktext("A😀한");
|
||||
checkutf8run();
|
||||
return 0;
|
||||
}
|
||||
157
trie.c
157
trie.c
@@ -1,12 +1,19 @@
|
||||
#include <errno.h>
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
|
||||
/*
|
||||
* A byte trie over UTF-8 keys. Maps need prefix matches while composing;
|
||||
* dictionaries need exact matches; both are small enough for one structure.
|
||||
* Node 0 is the root.
|
||||
*/
|
||||
|
||||
static int
|
||||
newnode(Trie *t)
|
||||
{
|
||||
int i;
|
||||
|
||||
if(t->n >= t->cap){
|
||||
if(t->n == t->cap){
|
||||
t->cap *= 2;
|
||||
t->nodes = erealloc(t->nodes, t->cap * sizeof(Tnode));
|
||||
}
|
||||
@@ -40,13 +47,25 @@ add(Trie *t, int ni, char c)
|
||||
return pi;
|
||||
}
|
||||
|
||||
static void
|
||||
insert(Trie *t, char *key, int klen, char *val, int vlen)
|
||||
Trie*
|
||||
trienew(void)
|
||||
{
|
||||
int ni, ci;
|
||||
int i;
|
||||
Trie *t;
|
||||
|
||||
ni = t->root;
|
||||
t = emalloc(sizeof(*t));
|
||||
t->cap = 1024;
|
||||
t->nodes = emalloc(t->cap * sizeof(Tnode));
|
||||
t->n = 0;
|
||||
newnode(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
void
|
||||
trieput(Trie *t, char *key, int klen, char *val, int vlen)
|
||||
{
|
||||
int ni, ci, i;
|
||||
|
||||
ni = 0;
|
||||
for(i = 0; i < klen; i++){
|
||||
ci = find(t, ni, key[i]);
|
||||
if(ci < 0)
|
||||
@@ -60,47 +79,64 @@ insert(Trie *t, char *key, int klen, char *val, int vlen)
|
||||
t->nodes[ni].vlen = vlen;
|
||||
}
|
||||
|
||||
static char*
|
||||
readline(Biobuf *b, char *path)
|
||||
{
|
||||
char *line;
|
||||
|
||||
errno = 0;
|
||||
line = Brdstr(b, '\n', 1);
|
||||
if(errno != 0)
|
||||
die("can't read %s: %r", path);
|
||||
return line;
|
||||
}
|
||||
|
||||
/*
|
||||
* A file holds "key<tab>value" lines; ';' starts a comment. Keys and each
|
||||
* space-separated word of a value must fit a Str, which is what lookups
|
||||
* hand back.
|
||||
*/
|
||||
Trie*
|
||||
trieopen(char *path)
|
||||
{
|
||||
Trie *t;
|
||||
Biobuf *b;
|
||||
char *line, *tab, *key, *val;
|
||||
int klen, vlen;
|
||||
Str s;
|
||||
char *e, *line, *p, *tab;
|
||||
int len, lineno;
|
||||
|
||||
b = Bopen(path, OREAD);
|
||||
if(b == nil)
|
||||
die("can't open: %s", path);
|
||||
t = emalloc(sizeof(*t));
|
||||
t->cap = 1024;
|
||||
t->nodes = emalloc(t->cap * sizeof(Tnode));
|
||||
t->n = 0;
|
||||
t->root = newnode(t);
|
||||
while((line = Brdstr(b, '\n', 1)) != nil){
|
||||
vlen = strlen(line);
|
||||
if(vlen > 0 && line[vlen-1] == '\r')
|
||||
line[--vlen] = '\0';
|
||||
if(line[0] == '\0' || line[0] == ';'){
|
||||
die("can't open %s: %r", path);
|
||||
t = trienew();
|
||||
for(lineno = 1; (line = readline(b, path)) != nil; lineno++){
|
||||
len = Blinelen(b);
|
||||
if(memchr(line, '\0', len) != nil)
|
||||
die("%s:%d: NUL byte", path, lineno);
|
||||
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 || tab[1] == '\0' ||
|
||||
strchr(tab+1, '\t') != nil)
|
||||
die("malformed map: %s", path);
|
||||
*tab = '\0';
|
||||
key = line;
|
||||
klen = tab - line;
|
||||
if(utflen(key) > Maxrunes)
|
||||
die("map key too long: %s", path);
|
||||
val = tab + 1;
|
||||
vlen = strlen(val);
|
||||
if(utflen(val) > Maxrunes)
|
||||
die("map value too long: %s", path);
|
||||
insert(t, key, klen, val, vlen);
|
||||
tab = memchr(line, '\t', len);
|
||||
if(tab == nil || tab == line || tab == line+len-1 ||
|
||||
memchr(tab+1, '\t', line+len-(tab+1)) != nil)
|
||||
die("%s:%d: expected key, tab, value", path, lineno);
|
||||
if(!sinit(&s, line, tab-line))
|
||||
die("%s:%d: invalid or oversized key", path, lineno);
|
||||
for(p = tab+1; p < line+len; p = e+1){
|
||||
e = memchr(p, ' ', line+len-p);
|
||||
if(e == nil)
|
||||
e = line+len;
|
||||
if(!sinit(&s, p, e-p))
|
||||
die("%s:%d: invalid or oversized value", path, lineno);
|
||||
}
|
||||
trieput(t, line, tab-line, tab+1, line+len-(tab+1));
|
||||
free(line);
|
||||
}
|
||||
Bterm(b);
|
||||
if(Bterm(b) < 0)
|
||||
die("can't close %s: %r", path);
|
||||
return t;
|
||||
}
|
||||
|
||||
@@ -117,46 +153,27 @@ trieclose(Trie *t)
|
||||
free(t);
|
||||
}
|
||||
|
||||
char*
|
||||
trieget(Trie *t, char *key, int klen, int *vlen)
|
||||
/* A nil trie is an unloaded map: every key misses. */
|
||||
int
|
||||
trielookup(Trie *t, Str *key, char **val, int *vlen)
|
||||
{
|
||||
int ni;
|
||||
int i;
|
||||
char buf[Maxutf];
|
||||
int i, klen, ni;
|
||||
|
||||
if(t == nil || klen < 0 || (klen > 0 && key == nil))
|
||||
return nil;
|
||||
ni = t->root;
|
||||
*val = nil;
|
||||
*vlen = 0;
|
||||
if(t == nil)
|
||||
return TrieMiss;
|
||||
klen = stoutf(key, buf, sizeof buf);
|
||||
ni = 0;
|
||||
for(i = 0; i < klen; i++){
|
||||
ni = find(t, ni, key[i]);
|
||||
ni = find(t, ni, buf[i]);
|
||||
if(ni < 0)
|
||||
return nil;
|
||||
return TrieMiss;
|
||||
}
|
||||
if(t->nodes[ni].val == nil)
|
||||
return nil;
|
||||
if(vlen != nil)
|
||||
*vlen = t->nodes[ni].vlen;
|
||||
return t->nodes[ni].val;
|
||||
}
|
||||
|
||||
int
|
||||
trielookup(Trie *t, char *key, int klen, char **val, int *vlen)
|
||||
{
|
||||
int ni;
|
||||
int i;
|
||||
|
||||
if(t == nil || klen < 0 || (klen > 0 && key == nil))
|
||||
return 0;
|
||||
ni = t->root;
|
||||
for(i = 0; i < klen; i++){
|
||||
ni = find(t, ni, key[i]);
|
||||
if(ni < 0)
|
||||
return 0;
|
||||
}
|
||||
if(t->nodes[ni].val != nil){
|
||||
if(val != nil)
|
||||
*val = t->nodes[ni].val;
|
||||
if(vlen != nil)
|
||||
*vlen = t->nodes[ni].vlen;
|
||||
}
|
||||
return 1;
|
||||
return TriePrefix;
|
||||
*val = t->nodes[ni].val;
|
||||
*vlen = t->nodes[ni].vlen;
|
||||
return TrieExact;
|
||||
}
|
||||
|
||||
69
vi.c
69
vi.c
@@ -31,12 +31,6 @@ static struct {
|
||||
{L'Y', {L'Ý', L'Ỳ', L'Ỷ', L'Ỹ', L'Ỵ'}},
|
||||
};
|
||||
|
||||
static int
|
||||
istone(Rune c)
|
||||
{
|
||||
return c == 's' || c == 'f' || c == 'r' || c == 'x' || c == 'j';
|
||||
}
|
||||
|
||||
static int
|
||||
toneidx(Rune c)
|
||||
{
|
||||
@@ -114,20 +108,44 @@ onsetglide(Str *m)
|
||||
return -1;
|
||||
}
|
||||
|
||||
Emit
|
||||
transvi(Im *im, Rune c)
|
||||
/*
|
||||
* The pending text is the key sequence itself, read through the map for
|
||||
* display; raw keeps every key of it so Backspace can replay one fewer.
|
||||
*/
|
||||
static Emit
|
||||
history(Im *im, Rune c, Emit e)
|
||||
{
|
||||
Emit e, mappedkey;
|
||||
Str mapped;
|
||||
|
||||
e.raw = im->raw;
|
||||
if(e.s.n > 0 || !e.eat)
|
||||
sclear(&e.raw);
|
||||
if(!e.eat || e.next.n == 0)
|
||||
return e;
|
||||
if(e.raw.n < Maxrunes){
|
||||
sputr(&e.raw, c);
|
||||
return e;
|
||||
}
|
||||
/* The history is full: flush the composed text as it stands. */
|
||||
if(!mapget(im->l->map, &e.next, &mapped))
|
||||
mapped = e.next;
|
||||
sappend(&e.s, &mapped);
|
||||
sclear(&e.next);
|
||||
sclear(&e.raw);
|
||||
return e;
|
||||
}
|
||||
|
||||
static Emit
|
||||
tone(Im *im, Rune c)
|
||||
{
|
||||
Emit e;
|
||||
Str mapped, pre;
|
||||
int i, tidx, vi, last, penult, glide;
|
||||
Rune v, b1, b2;
|
||||
|
||||
if(!istone(c) && c != 'z')
|
||||
return transmap(im, c);
|
||||
mappedkey = transmap(im, c);
|
||||
if(mappedkey.eat)
|
||||
return mappedkey;
|
||||
|
||||
e = transmap(im, c);
|
||||
if(e.eat)
|
||||
return e;
|
||||
memset(&e, 0, sizeof e);
|
||||
if(im->pre.n == 0)
|
||||
return e;
|
||||
@@ -175,17 +193,24 @@ transvi(Im *im, Rune c)
|
||||
return e;
|
||||
}
|
||||
|
||||
if(c == 'z')
|
||||
tidx = toneidx(c);
|
||||
if(tidx < 0)
|
||||
mapped.r[vi] = removetone(mapped.r[vi]);
|
||||
else{
|
||||
tidx = toneidx(c);
|
||||
else
|
||||
mapped.r[vi] = applytone(mapped.r[vi], tidx);
|
||||
}
|
||||
e.eat = 1;
|
||||
e.next = mapped;
|
||||
return e;
|
||||
}
|
||||
|
||||
Emit
|
||||
transvi(Im *im, Rune c)
|
||||
{
|
||||
if(toneidx(c) < 0 && c != 'z')
|
||||
return history(im, c, transmap(im, c));
|
||||
return history(im, c, tone(im, c));
|
||||
}
|
||||
|
||||
void
|
||||
backvi(Im *im)
|
||||
{
|
||||
@@ -200,10 +225,10 @@ backvi(Im *im)
|
||||
raw = im->raw;
|
||||
spopr(&raw);
|
||||
sclear(&im->pre);
|
||||
sclear(&im->raw);
|
||||
for(i = 0; i < raw.n; i++){
|
||||
e = transvi(im, raw.r[i]);
|
||||
sclear(&im->pre);
|
||||
sappend(&im->pre, &e.next);
|
||||
im->pre = e.next;
|
||||
im->raw = e.raw;
|
||||
}
|
||||
im->raw = raw;
|
||||
}
|
||||
|
||||
314
win.c
314
win.c
@@ -1,21 +1,17 @@
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/randr.h>
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
|
||||
enum {
|
||||
Asciitofull = 0xFEE0,
|
||||
};
|
||||
|
||||
extern char **fontfiles;
|
||||
extern int nfontfiles;
|
||||
|
||||
static xcb_connection_t *conn;
|
||||
static xcb_screen_t *scr;
|
||||
static xcb_window_t win;
|
||||
static xcb_gcontext_t gc;
|
||||
static xcb_pixmap_t pix;
|
||||
static u32int *img;
|
||||
static int depth;
|
||||
static xcb_atom_t currentdesktop;
|
||||
static xcb_atom_t workarea;
|
||||
static int hasrandr, imgh, imgw;
|
||||
|
||||
static xcb_screen_t*
|
||||
getscr(xcb_connection_t *c, int n)
|
||||
@@ -46,6 +42,7 @@ getvisual(xcb_screen_t *s)
|
||||
return nil;
|
||||
}
|
||||
|
||||
/* putimage writes native-endian x8r8g8b8; the root must take that. */
|
||||
static int
|
||||
validformat(xcb_connection_t *c, xcb_screen_t *s)
|
||||
{
|
||||
@@ -57,9 +54,8 @@ validformat(xcb_connection_t *c, xcb_screen_t *s)
|
||||
|
||||
setup = xcb_get_setup(c);
|
||||
v = getvisual(s);
|
||||
if(setup == nil || v == nil || s->root_depth != 24 ||
|
||||
if(v == nil || s->root_depth != 24 ||
|
||||
v->_class != XCB_VISUAL_CLASS_TRUE_COLOR ||
|
||||
v->bits_per_rgb_value != 8 ||
|
||||
v->red_mask != 0xff0000 || v->green_mask != 0x00ff00 ||
|
||||
v->blue_mask != 0x0000ff)
|
||||
return 0;
|
||||
@@ -76,19 +72,129 @@ validformat(xcb_connection_t *c, xcb_screen_t *s)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static xcb_atom_t
|
||||
getatom(char *name)
|
||||
{
|
||||
xcb_intern_atom_cookie_t cookie;
|
||||
xcb_intern_atom_reply_t *reply;
|
||||
xcb_atom_t atom;
|
||||
|
||||
cookie = xcb_intern_atom(conn, 0, strlen(name), name);
|
||||
reply = xcb_intern_atom_reply(conn, cookie, nil);
|
||||
atom = reply == nil ? XCB_ATOM_NONE : reply->atom;
|
||||
free(reply);
|
||||
return atom;
|
||||
}
|
||||
|
||||
static int
|
||||
getcardinals(xcb_atom_t atom, u32int off, int n, u32int *v)
|
||||
{
|
||||
xcb_get_property_cookie_t cookie;
|
||||
xcb_get_property_reply_t *reply;
|
||||
int ok;
|
||||
|
||||
if(atom == XCB_ATOM_NONE)
|
||||
return 0;
|
||||
cookie = xcb_get_property(conn, 0, scr->root, atom,
|
||||
XCB_ATOM_CARDINAL, off, n);
|
||||
reply = xcb_get_property_reply(conn, cookie, nil);
|
||||
ok = reply != nil && reply->type == XCB_ATOM_CARDINAL &&
|
||||
reply->format == 32 &&
|
||||
xcb_get_property_value_length(reply) == n*(int)sizeof v[0];
|
||||
if(ok)
|
||||
memmove(v, xcb_get_property_value(reply), n*sizeof v[0]);
|
||||
free(reply);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
getworkarea(Area *a)
|
||||
{
|
||||
u32int desktop, v[4];
|
||||
|
||||
if(!getcardinals(currentdesktop, 0, 1, &desktop) ||
|
||||
desktop > 0x3fffffff ||
|
||||
!getcardinals(workarea, desktop*4, 4, v) ||
|
||||
v[0] > 0x7fffffff || v[1] > 0x7fffffff ||
|
||||
v[2] == 0 || v[2] > 0x7fffffff ||
|
||||
v[3] == 0 || v[3] > 0x7fffffff)
|
||||
return 0;
|
||||
a->x = v[0];
|
||||
a->y = v[1];
|
||||
a->w = v[2];
|
||||
a->h = v[3];
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
getrootarea(Area *a)
|
||||
{
|
||||
xcb_get_geometry_cookie_t cookie;
|
||||
xcb_get_geometry_reply_t *reply;
|
||||
|
||||
a->x = 0;
|
||||
a->y = 0;
|
||||
a->w = scr->width_in_pixels;
|
||||
a->h = scr->height_in_pixels;
|
||||
cookie = xcb_get_geometry(conn, scr->root);
|
||||
reply = xcb_get_geometry_reply(conn, cookie, nil);
|
||||
if(reply != nil && reply->width > 0 && reply->height > 0){
|
||||
a->w = reply->width;
|
||||
a->h = reply->height;
|
||||
}
|
||||
free(reply);
|
||||
}
|
||||
|
||||
/* The area the popup may use: the RandR monitor under (x, y), or the
|
||||
* root, cut down to the EWMH work area. */
|
||||
static void
|
||||
popupwork(int x, int y, Area *out)
|
||||
{
|
||||
xcb_randr_get_monitors_cookie_t cookie;
|
||||
xcb_randr_get_monitors_reply_t *reply;
|
||||
xcb_randr_monitor_info_iterator_t it;
|
||||
Area root, net, *mon;
|
||||
int n;
|
||||
|
||||
mon = nil;
|
||||
n = 0;
|
||||
reply = nil;
|
||||
if(hasrandr){
|
||||
cookie = xcb_randr_get_monitors(conn, scr->root, 1);
|
||||
reply = xcb_randr_get_monitors_reply(conn, cookie, nil);
|
||||
}
|
||||
if(reply != nil && xcb_randr_get_monitors_monitors_length(reply) > 0){
|
||||
mon = emalloc(xcb_randr_get_monitors_monitors_length(reply) *
|
||||
sizeof mon[0]);
|
||||
it = xcb_randr_get_monitors_monitors_iterator(reply);
|
||||
for(; it.rem; xcb_randr_monitor_info_next(&it)){
|
||||
if(it.data->width == 0 || it.data->height == 0)
|
||||
continue;
|
||||
mon[n].x = it.data->x;
|
||||
mon[n].y = it.data->y;
|
||||
mon[n].w = it.data->width;
|
||||
mon[n].h = it.data->height;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
if(n == 0){
|
||||
free(mon);
|
||||
getrootarea(&root);
|
||||
mon = &root;
|
||||
n = 1;
|
||||
}
|
||||
popuparea(mon, n, getworkarea(&net) ? &net : nil, x, y, out);
|
||||
if(mon != &root)
|
||||
free(mon);
|
||||
free(reply);
|
||||
}
|
||||
|
||||
static void
|
||||
wincleanup(void)
|
||||
{
|
||||
textclose();
|
||||
free(img);
|
||||
img = nil;
|
||||
if(conn != nil){
|
||||
xcb_disconnect(conn);
|
||||
conn = nil;
|
||||
}
|
||||
scr = nil;
|
||||
win = 0;
|
||||
gc = 0;
|
||||
pix = 0;
|
||||
xcb_disconnect(conn);
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -96,11 +202,17 @@ wininit(void)
|
||||
{
|
||||
int n;
|
||||
u32int mask, vals[4];
|
||||
xcb_intern_atom_cookie_t c1, c2;
|
||||
xcb_intern_atom_reply_t *r1, *r2;
|
||||
xcb_atom_t type, tooltip;
|
||||
xcb_randr_query_version_cookie_t rc;
|
||||
xcb_randr_query_version_reply_t *rr;
|
||||
const xcb_query_extension_reply_t *rext;
|
||||
char *scale;
|
||||
|
||||
scale = getenv("GDK_SCALE");
|
||||
if(scale != nil && atoi(scale) > 1)
|
||||
popupscale = min(atoi(scale), 4);
|
||||
conn = xcb_connect(nil, &n);
|
||||
if(conn == nil || xcb_connection_has_error(conn)){
|
||||
if(xcb_connection_has_error(conn)){
|
||||
fprint(2, "strans: popup disabled: cannot connect to X display\n");
|
||||
wincleanup();
|
||||
return 0;
|
||||
@@ -111,7 +223,16 @@ wininit(void)
|
||||
wincleanup();
|
||||
return 0;
|
||||
}
|
||||
depth = scr->root_depth;
|
||||
rext = xcb_get_extension_data(conn, &xcb_randr_id);
|
||||
if(rext != nil && rext->present){
|
||||
rc = xcb_randr_query_version(conn, 1, 5);
|
||||
rr = xcb_randr_query_version_reply(conn, rc, nil);
|
||||
hasrandr = rr != nil && (rr->major_version > 1 ||
|
||||
(rr->major_version == 1 && rr->minor_version >= 5));
|
||||
free(rr);
|
||||
}
|
||||
currentdesktop = getatom("_NET_CURRENT_DESKTOP");
|
||||
workarea = getatom("_NET_WORKAREA");
|
||||
win = xcb_generate_id(conn);
|
||||
mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
|
||||
XCB_CW_OVERRIDE_REDIRECT | XCB_CW_SAVE_UNDER;
|
||||
@@ -122,26 +243,14 @@ wininit(void)
|
||||
xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, scr->root,
|
||||
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||
scr->root_visual, mask, vals);
|
||||
c1 = xcb_intern_atom(conn, 0,
|
||||
strlen("_NET_WM_WINDOW_TYPE"), "_NET_WM_WINDOW_TYPE");
|
||||
c2 = xcb_intern_atom(conn, 0,
|
||||
strlen("_NET_WM_WINDOW_TYPE_TOOLTIP"),
|
||||
"_NET_WM_WINDOW_TYPE_TOOLTIP");
|
||||
r1 = xcb_intern_atom_reply(conn, c1, nil);
|
||||
r2 = xcb_intern_atom_reply(conn, c2, nil);
|
||||
if(r1 != nil && r2 != nil)
|
||||
type = getatom("_NET_WM_WINDOW_TYPE");
|
||||
tooltip = getatom("_NET_WM_WINDOW_TYPE_TOOLTIP");
|
||||
if(type != XCB_ATOM_NONE && tooltip != XCB_ATOM_NONE)
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE,
|
||||
win, r1->atom, XCB_ATOM_ATOM, 32, 1, &r2->atom);
|
||||
free(r1);
|
||||
free(r2);
|
||||
win, type, XCB_ATOM_ATOM, 32, 1, &tooltip);
|
||||
gc = xcb_generate_id(conn);
|
||||
xcb_create_gc(conn, gc, win, 0, nil);
|
||||
pix = xcb_generate_id(conn);
|
||||
xcb_create_pixmap(conn, depth, pix, win, Imgw, Imgh);
|
||||
mask = XCB_CW_BACK_PIXMAP;
|
||||
xcb_change_window_attributes(conn, win, mask, &pix);
|
||||
img = emalloc(Imgw * Imgh * sizeof(img[0]));
|
||||
if(!fontinit(fontfiles, nfontfiles)){
|
||||
if(!textinit()){
|
||||
fprint(2, "strans: popup disabled: no usable fonts\n");
|
||||
wincleanup();
|
||||
return 0;
|
||||
@@ -149,53 +258,50 @@ wininit(void)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
drawstr(u32int *buf, int x, int y, Rune *r, int n, int maxw, int maxh)
|
||||
/* The image and its retained pixmap only ever grow. */
|
||||
static int
|
||||
resizebacking(int w, int h)
|
||||
{
|
||||
while(n-- > 0){
|
||||
if(*r != 0xfe0e && *r != 0xfe0f){
|
||||
putfont(buf, maxw, maxh, x, y, *r);
|
||||
x += Fontsz;
|
||||
}
|
||||
r++;
|
||||
xcb_generic_error_t *err;
|
||||
xcb_pixmap_t old, new;
|
||||
xcb_void_cookie_t cookie;
|
||||
int nh, nw;
|
||||
|
||||
if(w <= imgw && h <= imgh)
|
||||
return 1;
|
||||
nw = max(w, imgw);
|
||||
nh = max(h, imgh);
|
||||
img = erealloc(img, (ulong)nw * nh * sizeof img[0]);
|
||||
new = xcb_generate_id(conn);
|
||||
cookie = xcb_create_pixmap_checked(conn, scr->root_depth, new, win,
|
||||
nw, nh);
|
||||
err = xcb_request_check(conn, cookie);
|
||||
if(err != nil){
|
||||
free(err);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fill(u32int *buf, int n, u32int color)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; i < n; i++)
|
||||
buf[i] = color;
|
||||
}
|
||||
|
||||
static void
|
||||
drawkouho(Drawcmd *dc, int n, int w, int h)
|
||||
{
|
||||
int npre, sely, y, i;
|
||||
Str *s;
|
||||
|
||||
fill(img, w * h, Colbg);
|
||||
npre = dc->pre.n != 0;
|
||||
if(npre)
|
||||
drawstr(img, 0, 0, dc->pre.r, dc->pre.n, w, h);
|
||||
if(dc->sel >= 0 && dc->sel < n){
|
||||
sely = (npre + dc->sel) * Fontsz;
|
||||
fill(img + sely * w, Fontsz * w, Colsel);
|
||||
}
|
||||
for(i = 0, y = npre * Fontsz; i < n; i++, y += Fontsz){
|
||||
s = &dc->kouho[i];
|
||||
putfont(img, w, h, 0, y, '1' + i + Asciitofull);
|
||||
drawstr(img, 2*Fontsz, y, s->r, s->n, w, h);
|
||||
cookie = xcb_change_window_attributes_checked(conn, win,
|
||||
XCB_CW_BACK_PIXMAP, &new);
|
||||
err = xcb_request_check(conn, cookie);
|
||||
if(err != nil){
|
||||
free(err);
|
||||
xcb_free_pixmap(conn, new);
|
||||
return 0;
|
||||
}
|
||||
old = pix;
|
||||
pix = new;
|
||||
imgw = nw;
|
||||
imgh = nh;
|
||||
if(old != 0)
|
||||
xcb_free_pixmap(conn, old);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
putimage(int w, int h)
|
||||
{
|
||||
xcb_put_image(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc,
|
||||
w, h, 0, 0, 0, depth, w * h * 4, (u8int*)img);
|
||||
w, h, 0, 0, 0, scr->root_depth, w * h * 4, (u8int*)img);
|
||||
/* The retained background pixmap lets the server repaint exposures. */
|
||||
xcb_clear_area(conn, 0, win, 0, 0, w, h);
|
||||
}
|
||||
@@ -207,24 +313,19 @@ winhide(void)
|
||||
return xcb_flush(conn) > 0;
|
||||
}
|
||||
|
||||
/* Draws dc at the caret, or by the pointer when the caret is unknown. */
|
||||
static int
|
||||
winshow(Drawcmd *dc)
|
||||
{
|
||||
int npre, px, py, w, h, i, n, maxw;
|
||||
u32int vals[4];
|
||||
Area area;
|
||||
Popup p;
|
||||
int ax, ay, px, py, x, y;
|
||||
u32int vals[5];
|
||||
xcb_query_pointer_reply_t *ptr;
|
||||
xcb_query_pointer_cookie_t cookie;
|
||||
|
||||
n = min(max(dc->nkouho, 0), Maxdisp);
|
||||
npre = dc->pre.n != 0;
|
||||
if(n == 0 && npre == 0){
|
||||
if(dc->nkouho == 0 && dc->pre.n == 0)
|
||||
return winhide();
|
||||
}
|
||||
maxw = popupcells(dc->pre.r, dc->pre.n);
|
||||
for(i = 0; i < n; i++)
|
||||
maxw = max(maxw, popupcells(dc->kouho[i].r, dc->kouho[i].n));
|
||||
vals[3] = h = (n + npre) * Fontsz;
|
||||
vals[2] = w = (maxw + 3) * Fontsz;
|
||||
px = py = 0;
|
||||
if(!dc->caret.valid){
|
||||
cookie = xcb_query_pointer(conn, scr->root);
|
||||
@@ -235,17 +336,26 @@ winshow(Drawcmd *dc)
|
||||
py = ptr->root_y;
|
||||
free(ptr);
|
||||
}
|
||||
popupposition(&dc->caret, px, py, scr->width_in_pixels,
|
||||
scr->height_in_pixels, w, h, &px, &py);
|
||||
vals[1] = py;
|
||||
vals[0] = px;
|
||||
ax = dc->caret.valid ? dc->caret.x : px;
|
||||
ay = dc->caret.valid ? dc->caret.y : py;
|
||||
popupwork(ax, ay, &area);
|
||||
popuplayout(dc, area.w, area.h, &p);
|
||||
if(p.w <= 0 || p.h <= 0 || !resizebacking(p.w, p.h))
|
||||
return 0;
|
||||
popupposition(&dc->caret, px, py, &area, p.w, p.h, &x, &y);
|
||||
/* Mapping does not restack: raise above windows opened since. */
|
||||
vals[0] = x;
|
||||
vals[1] = y;
|
||||
vals[2] = p.w;
|
||||
vals[3] = p.h;
|
||||
vals[4] = XCB_STACK_MODE_ABOVE;
|
||||
xcb_configure_window(conn, win,
|
||||
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
|
||||
vals);
|
||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
|
||||
XCB_CONFIG_WINDOW_STACK_MODE, vals);
|
||||
popupdraw(img, dc, &p);
|
||||
putimage(p.w, p.h);
|
||||
xcb_map_window(conn, win);
|
||||
drawkouho(dc, n, w, h);
|
||||
putimage(w, h);
|
||||
return xcb_flush(conn) > 0;
|
||||
}
|
||||
|
||||
@@ -257,14 +367,8 @@ drawthread(void*)
|
||||
threadsetname("draw");
|
||||
if(!wininit())
|
||||
return;
|
||||
while(chanrecv(drawc, &dc) > 0){
|
||||
while(channbrecv(drawc, &dc) > 0)
|
||||
;
|
||||
if(dc.nkouho == 0 && dc.pre.n == 0){
|
||||
if(!winhide())
|
||||
break;
|
||||
}else if(!winshow(&dc))
|
||||
while(chanrecv(drawc, &dc) > 0)
|
||||
if(!winshow(&dc))
|
||||
break;
|
||||
}
|
||||
wincleanup();
|
||||
}
|
||||
|
||||
27
xim/Makefile
27
xim/Makefile
@@ -1,27 +0,0 @@
|
||||
CC = cc
|
||||
XIM_CFLAGS = $(shell pkg-config --cflags xcb-imdkit xkbcommon)
|
||||
XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon)
|
||||
CFLAGS = -Wall -Wextra -O2 -I.. $(XIM_CFLAGS)
|
||||
PROG = strans-xim
|
||||
|
||||
SRCS = $(wildcard *.c)
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
TEST = xim_test
|
||||
|
||||
all: $(PROG)
|
||||
|
||||
$(PROG): $(OBJS) ../ipc.c ../ipc.h
|
||||
$(CC) $(CFLAGS) -o $@ $(OBJS) ../ipc.c $(XIM_LIBS)
|
||||
|
||||
$(OBJS): ../ipc.h
|
||||
|
||||
$(TEST): ../tests/xim_test.c keymap.c ximtext.c
|
||||
$(CC) $(CFLAGS) -o $@ ../tests/xim_test.c keymap.c ximtext.c $(XIM_LIBS)
|
||||
|
||||
check: $(TEST)
|
||||
./$(TEST)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(PROG) $(TEST)
|
||||
|
||||
.PHONY: all check clean
|
||||
60
xim/keymap.c
60
xim/keymap.c
@@ -1,37 +1,37 @@
|
||||
#include <stdint.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbcommon-names.h>
|
||||
|
||||
enum
|
||||
{
|
||||
ShiftMask = 1<<0,
|
||||
LockMask = 1<<1,
|
||||
GroupShift = 13,
|
||||
GroupMask = 3,
|
||||
};
|
||||
|
||||
/* The keysym for an X core key event, with its modifier and group state. */
|
||||
uint32_t
|
||||
keymaplookup(const uint32_t *syms, int nsyms, uint16_t state)
|
||||
keymaplookup(struct xkb_state *state, uint8_t keycode, uint16_t corestate)
|
||||
{
|
||||
uint32_t lo, hi, lower, upper;
|
||||
int col, group, shift;
|
||||
static const char *modname[] = {
|
||||
XKB_MOD_NAME_SHIFT,
|
||||
XKB_MOD_NAME_CAPS,
|
||||
XKB_MOD_NAME_CTRL,
|
||||
XKB_MOD_NAME_MOD1,
|
||||
XKB_MOD_NAME_MOD2,
|
||||
XKB_MOD_NAME_MOD3,
|
||||
XKB_MOD_NAME_MOD4,
|
||||
XKB_MOD_NAME_MOD5,
|
||||
};
|
||||
struct xkb_keymap *keymap;
|
||||
xkb_mod_index_t index;
|
||||
xkb_mod_mask_t mods;
|
||||
int i;
|
||||
|
||||
if(syms == NULL || nsyms <= 0)
|
||||
return XKB_KEY_NoSymbol;
|
||||
group = (state >> GroupShift) & GroupMask;
|
||||
col = group * 2;
|
||||
if(col >= nsyms || syms[col] == XKB_KEY_NoSymbol)
|
||||
col = 0;
|
||||
lo = syms[col];
|
||||
if(lo == XKB_KEY_NoSymbol)
|
||||
return XKB_KEY_NoSymbol;
|
||||
hi = col + 1 < nsyms ? syms[col + 1] : XKB_KEY_NoSymbol;
|
||||
lower = xkb_keysym_to_lower(lo);
|
||||
upper = xkb_keysym_to_upper(lo);
|
||||
if(hi == XKB_KEY_NoSymbol)
|
||||
hi = lower != upper ? upper : lo;
|
||||
shift = (state & ShiftMask) != 0;
|
||||
/* Lock reverses Shift only for keys with an alphabetic case pair. */
|
||||
if((state & LockMask) && lower != upper)
|
||||
shift = !shift;
|
||||
return shift ? hi : lo;
|
||||
keymap = xkb_state_get_keymap(state);
|
||||
mods = 0;
|
||||
for(i = 0; i < (int)(sizeof modname / sizeof modname[0]); i++){
|
||||
if(!(corestate & (1U << i)))
|
||||
continue;
|
||||
index = xkb_keymap_mod_get_index(keymap, modname[i]);
|
||||
if(index != XKB_MOD_INVALID)
|
||||
mods |= (xkb_mod_mask_t)1 << index;
|
||||
}
|
||||
xkb_state_update_mask(state, mods, 0, 0, 0, 0,
|
||||
(corestate >> 13) & 3);
|
||||
return xkb_state_key_get_one_sym(state, keycode);
|
||||
}
|
||||
|
||||
298
xim/main.c
298
xim/main.c
@@ -1,298 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xcb-imdkit/imdkit.h>
|
||||
#include <xcb-imdkit/encoding.h>
|
||||
#include "ipc.h"
|
||||
|
||||
uint32_t keymaplookup(const uint32_t*, int, uint16_t);
|
||||
char *ximcompound(const char*, size_t, size_t*);
|
||||
|
||||
typedef struct Ic Ic;
|
||||
struct Ic
|
||||
{
|
||||
int fd;
|
||||
};
|
||||
|
||||
static xcb_connection_t *conn;
|
||||
static xcb_im_t *xim;
|
||||
static xcb_keysym_t *kmap;
|
||||
static uint8_t minkc, maxkc;
|
||||
static uint8_t symsper;
|
||||
|
||||
static char *encs[] = {"COMPOUND_TEXT"};
|
||||
static uint32_t styles[] = {
|
||||
XCB_IM_PreeditNothing | XCB_IM_StatusNothing,
|
||||
XCB_IM_PreeditNone | XCB_IM_StatusNone,
|
||||
};
|
||||
|
||||
static void
|
||||
die(char *msg)
|
||||
{
|
||||
fprintf(stderr, "strans-xim: %s\n", msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void
|
||||
kinit(void)
|
||||
{
|
||||
xcb_get_keyboard_mapping_cookie_t c;
|
||||
xcb_get_keyboard_mapping_reply_t *r;
|
||||
const xcb_setup_t *setup;
|
||||
xcb_keysym_t *syms;
|
||||
int n;
|
||||
|
||||
setup = xcb_get_setup(conn);
|
||||
if(setup == NULL)
|
||||
die("xcb_get_setup failed");
|
||||
minkc = setup->min_keycode;
|
||||
maxkc = setup->max_keycode;
|
||||
c = xcb_get_keyboard_mapping(conn, minkc, maxkc - minkc + 1);
|
||||
r = xcb_get_keyboard_mapping_reply(conn, c, NULL);
|
||||
if(r == NULL)
|
||||
die("keyboard mapping failed");
|
||||
symsper = r->keysyms_per_keycode;
|
||||
n = xcb_get_keyboard_mapping_keysyms_length(r);
|
||||
syms = malloc(n * sizeof(xcb_keysym_t));
|
||||
if(syms == NULL)
|
||||
die("malloc failed");
|
||||
memcpy(syms, xcb_get_keyboard_mapping_keysyms(r),
|
||||
n * sizeof(xcb_keysym_t));
|
||||
free(kmap);
|
||||
kmap = syms;
|
||||
free(r);
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
kget(uint8_t kc, uint16_t state)
|
||||
{
|
||||
if(kmap == NULL || kc < minkc || kc > maxkc)
|
||||
return 0;
|
||||
return keymaplookup(kmap + (kc - minkc) * symsper, symsper, state);
|
||||
}
|
||||
|
||||
static xcb_screen_t*
|
||||
getscreen(int scr)
|
||||
{
|
||||
xcb_screen_iterator_t iter;
|
||||
const xcb_setup_t *setup;
|
||||
|
||||
setup = xcb_get_setup(conn);
|
||||
if(setup == NULL)
|
||||
die("xcb_get_setup failed");
|
||||
iter = xcb_setup_roots_iterator(setup);
|
||||
for(; iter.rem; scr--, xcb_screen_next(&iter))
|
||||
if(scr == 0)
|
||||
return iter.data;
|
||||
die("no screen");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
commit(xcb_im_input_context_t *ic, char *s, int len)
|
||||
{
|
||||
char *ct;
|
||||
size_t clen;
|
||||
|
||||
if(len == 0)
|
||||
return;
|
||||
ct = ximcompound(s, len, &clen);
|
||||
if(ct == NULL)
|
||||
return;
|
||||
xcb_im_commit_string(xim, ic, XCB_XIM_LOOKUP_CHARS, ct, clen, 0);
|
||||
xcb_flush(conn);
|
||||
free(ct);
|
||||
}
|
||||
|
||||
static void
|
||||
srvclose(Ic *state)
|
||||
{
|
||||
if(state == NULL || state->fd < 0)
|
||||
return;
|
||||
close(state->fd);
|
||||
state->fd = -1;
|
||||
}
|
||||
|
||||
static int
|
||||
srvconnect(Ic *state)
|
||||
{
|
||||
if(state->fd >= 0)
|
||||
return 0;
|
||||
state->fd = ipcconnect();
|
||||
if(state->fd < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
readresp(Ic *state, xcb_im_input_context_t *ic)
|
||||
{
|
||||
char buf[Ipcfieldmax+1];
|
||||
Ipcresp resp;
|
||||
|
||||
if(ipcreadresp(state->fd, 0, buf, sizeof buf, NULL, 0, &resp) < 0)
|
||||
return -1;
|
||||
if(resp.ncommit > 0)
|
||||
commit(ic, buf, resp.ncommit);
|
||||
return resp.eaten;
|
||||
}
|
||||
|
||||
static void
|
||||
kpress(Ic *state, xcb_im_input_context_t *ic, xcb_key_press_event_t *ev)
|
||||
{
|
||||
unsigned char buf[Ipcreqsz];
|
||||
uint32_t key, rune;
|
||||
int eaten;
|
||||
|
||||
key = kget(ev->detail, ev->state);
|
||||
rune = xkb_keysym_to_utf32(key);
|
||||
if(rune >= ' ' && rune != 0x7f)
|
||||
key = rune;
|
||||
else if(key >= 0xff00 && key <= 0xffff)
|
||||
key = Kspec + (key - 0xff00);
|
||||
else
|
||||
key = rune;
|
||||
ipcpackreq(buf, 0, ev->state, key);
|
||||
eaten = 0;
|
||||
if(srvconnect(state) == 0){
|
||||
if(ipcsend(state->fd, buf, sizeof buf) < 0 ||
|
||||
(eaten = readresp(state, ic)) < 0){
|
||||
srvclose(state);
|
||||
eaten = 0;
|
||||
}
|
||||
}
|
||||
if(eaten == 0)
|
||||
xcb_im_forward_event(xim, ic, ev);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
static void
|
||||
reset(Ic *state, xcb_im_input_context_t *ic, int release)
|
||||
{
|
||||
unsigned char buf[Ipcreqsz];
|
||||
|
||||
if(state == NULL || state->fd < 0)
|
||||
return;
|
||||
ipcpackreset(buf, 0);
|
||||
if(ipcsend(state->fd, buf, sizeof buf) < 0 ||
|
||||
readresp(state, ic) < 0)
|
||||
release = 1;
|
||||
if(release)
|
||||
srvclose(state);
|
||||
}
|
||||
|
||||
static void
|
||||
icfree(void *p)
|
||||
{
|
||||
Ic *state;
|
||||
|
||||
state = p;
|
||||
/* imdkit frees this data on context destruction and client loss. */
|
||||
srvclose(state);
|
||||
free(state);
|
||||
}
|
||||
|
||||
static void
|
||||
iccreate(xcb_im_input_context_t *ic)
|
||||
{
|
||||
Ic *state;
|
||||
|
||||
if(ic == NULL || xcb_im_input_context_get_data(ic) != NULL)
|
||||
return;
|
||||
state = calloc(1, sizeof *state);
|
||||
if(state == NULL)
|
||||
return;
|
||||
state->fd = -1;
|
||||
xcb_im_input_context_set_data(ic, state, icfree);
|
||||
}
|
||||
|
||||
static void
|
||||
callback(xcb_im_t *im, xcb_im_client_t *client, xcb_im_input_context_t *ic,
|
||||
const xcb_im_packet_header_fr_t *hdr, void *frame, void *arg, void *user)
|
||||
{
|
||||
xcb_key_press_event_t *ev;
|
||||
Ic *state;
|
||||
|
||||
(void)im;
|
||||
(void)client;
|
||||
(void)frame;
|
||||
(void)user;
|
||||
|
||||
if(hdr->major_opcode == XCB_XIM_CREATE_IC){
|
||||
iccreate(ic);
|
||||
return;
|
||||
}
|
||||
if(ic == NULL)
|
||||
return;
|
||||
state = xcb_im_input_context_get_data(ic);
|
||||
if(state == NULL)
|
||||
return;
|
||||
switch(hdr->major_opcode){
|
||||
case XCB_XIM_FORWARD_EVENT:
|
||||
ev = arg;
|
||||
if(ev != NULL && (ev->response_type & ~0x80) == XCB_KEY_PRESS)
|
||||
kpress(state, ic, ev);
|
||||
break;
|
||||
case XCB_XIM_RESET_IC:
|
||||
/* ResetIC ends this context's ownership, like focus loss. */
|
||||
reset(state, ic, 1);
|
||||
break;
|
||||
case XCB_XIM_UNSET_IC_FOCUS:
|
||||
reset(state, ic, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ximinit(void)
|
||||
{
|
||||
xcb_screen_t *screen;
|
||||
xcb_window_t win;
|
||||
xcb_im_styles_t st;
|
||||
xcb_im_encodings_t enc;
|
||||
int scr;
|
||||
|
||||
st.nStyles = 2;
|
||||
st.styles = styles;
|
||||
enc.nEncodings = 1;
|
||||
enc.encodings = encs;
|
||||
xcb_compound_text_init();
|
||||
conn = xcb_connect(NULL, &scr);
|
||||
if(conn == NULL || xcb_connection_has_error(conn))
|
||||
die("xcb_connect failed");
|
||||
screen = getscreen(scr);
|
||||
kinit();
|
||||
win = xcb_generate_id(conn);
|
||||
xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, screen->root,
|
||||
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||
screen->root_visual, 0, NULL);
|
||||
xim = xcb_im_create(conn, scr, win, "strans",
|
||||
XCB_IM_ALL_LOCALES, &st, NULL, NULL, &enc,
|
||||
XCB_EVENT_MASK_KEY_PRESS, callback, NULL);
|
||||
if(xim == NULL || !xcb_im_open_im(xim))
|
||||
die("xcb_im failed");
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
xcb_generic_event_t *ev;
|
||||
uint8_t type;
|
||||
|
||||
ximinit();
|
||||
for(;;){
|
||||
ev = xcb_wait_for_event(conn);
|
||||
if(ev == NULL)
|
||||
break;
|
||||
type = ev->response_type & ~0x80;
|
||||
if(type == XCB_MAPPING_NOTIFY)
|
||||
kinit();
|
||||
else
|
||||
xcb_im_filter_event(xim, ev);
|
||||
free(ev);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
599
xim/xim.c
Normal file
599
xim/xim.c
Normal file
@@ -0,0 +1,599 @@
|
||||
#include "dat.h"
|
||||
#include "fn.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <locale.h>
|
||||
#include <poll.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xkb.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbcommon-compose.h>
|
||||
#include <xkbcommon/xkbcommon-x11.h>
|
||||
#include <xcb-imdkit/imdkit.h>
|
||||
#include <xcb-imdkit/encoding.h>
|
||||
|
||||
u32int keymaplookup(struct xkb_state*, uint8_t, u16int);
|
||||
|
||||
/*
|
||||
* One XIM input context. Only PreeditCallbacks clients draw the preedit
|
||||
* themselves (cap); the popup shows it for the others. engaged means the
|
||||
* engine has seen a real key from this context and owes it a release.
|
||||
*/
|
||||
typedef struct Ic Ic;
|
||||
struct Ic
|
||||
{
|
||||
Ic *next;
|
||||
xcb_im_input_context_t *xic;
|
||||
xcb_im_client_t *client;
|
||||
int engaged;
|
||||
int cap;
|
||||
int prestarted;
|
||||
int nprerune;
|
||||
Caret caret;
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
Ownerpoll = 200, /* ms between owner checks while a preedit shows */
|
||||
};
|
||||
|
||||
static xcb_connection_t *conn;
|
||||
static xcb_im_t *xim;
|
||||
static struct xkb_state *kstate;
|
||||
static struct xkb_compose_state *compose;
|
||||
static uint8_t xkbevent;
|
||||
static xcb_window_t rootwin;
|
||||
static Ic *ics;
|
||||
static Ic *preowner;
|
||||
static Channel *replyc;
|
||||
|
||||
static char *encs[] = {"COMPOUND_TEXT"};
|
||||
static u32int styles[] = {
|
||||
XCB_IM_PreeditPosition | XCB_IM_StatusNothing,
|
||||
XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing,
|
||||
XCB_IM_PreeditNothing | XCB_IM_StatusNothing,
|
||||
};
|
||||
|
||||
static void
|
||||
ximlog(char *msg)
|
||||
{
|
||||
fprint(2, "strans: xim: %s\n", msg);
|
||||
}
|
||||
|
||||
/* Reads the server's keymap; XKB-aware clients get XKB events, not MappingNotify. */
|
||||
static int
|
||||
kinit(void)
|
||||
{
|
||||
struct xkb_context *context;
|
||||
struct xkb_keymap *keymap;
|
||||
struct xkb_state *state;
|
||||
int32_t device;
|
||||
int ok;
|
||||
|
||||
keymap = nil;
|
||||
state = nil;
|
||||
ok = -1;
|
||||
context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||
if(context == nil || !xkb_x11_setup_xkb_extension(conn,
|
||||
XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION,
|
||||
XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, nil, nil, &xkbevent, nil))
|
||||
goto out;
|
||||
device = xkb_x11_get_core_keyboard_device_id(conn);
|
||||
if(device < 0)
|
||||
goto out;
|
||||
keymap = xkb_x11_keymap_new_from_device(context, conn, device,
|
||||
XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||
if(keymap == nil)
|
||||
goto out;
|
||||
state = xkb_state_new(keymap);
|
||||
if(state == nil)
|
||||
goto out;
|
||||
xkb_state_unref(kstate);
|
||||
kstate = state;
|
||||
state = nil;
|
||||
ok = 0;
|
||||
out:
|
||||
xkb_state_unref(state);
|
||||
xkb_keymap_unref(keymap);
|
||||
xkb_context_unref(context);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* Dead keys and Compose: Xlib leaves them to the input method server. */
|
||||
static void
|
||||
cinit(void)
|
||||
{
|
||||
struct xkb_context *context;
|
||||
struct xkb_compose_table *table;
|
||||
char *locale;
|
||||
|
||||
locale = setlocale(LC_CTYPE, "");
|
||||
context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||
if(context == nil)
|
||||
return;
|
||||
table = xkb_compose_table_new_from_locale(context,
|
||||
locale != nil ? locale : "C", XKB_COMPOSE_COMPILE_NO_FLAGS);
|
||||
if(table != nil)
|
||||
compose = xkb_compose_state_new(table, XKB_COMPOSE_STATE_NO_FLAGS);
|
||||
xkb_compose_table_unref(table);
|
||||
xkb_context_unref(context);
|
||||
}
|
||||
|
||||
static void
|
||||
kwatch(void)
|
||||
{
|
||||
u16int events, parts;
|
||||
|
||||
events = XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
|
||||
XCB_XKB_EVENT_TYPE_MAP_NOTIFY;
|
||||
parts = XCB_XKB_MAP_PART_KEY_TYPES | XCB_XKB_MAP_PART_KEY_SYMS |
|
||||
XCB_XKB_MAP_PART_MODIFIER_MAP |
|
||||
XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
|
||||
XCB_XKB_MAP_PART_KEY_ACTIONS | XCB_XKB_MAP_PART_VIRTUAL_MODS |
|
||||
XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP;
|
||||
xcb_xkb_select_events(conn, XCB_XKB_ID_USE_CORE_KBD, events, 0,
|
||||
events, parts, parts, nil);
|
||||
}
|
||||
|
||||
static xcb_screen_t*
|
||||
getscreen(int scr)
|
||||
{
|
||||
xcb_screen_iterator_t iter;
|
||||
|
||||
iter = xcb_setup_roots_iterator(xcb_get_setup(conn));
|
||||
for(; iter.rem; scr--, xcb_screen_next(&iter))
|
||||
if(scr == 0)
|
||||
return iter.data;
|
||||
return nil;
|
||||
}
|
||||
|
||||
static int
|
||||
translate(xcb_window_t win, int x, int y, Caret *caret)
|
||||
{
|
||||
xcb_translate_coordinates_cookie_t cookie;
|
||||
xcb_translate_coordinates_reply_t *reply;
|
||||
|
||||
if(win == XCB_NONE)
|
||||
return 0;
|
||||
cookie = xcb_translate_coordinates(conn, win, rootwin, x, y);
|
||||
reply = xcb_translate_coordinates_reply(conn, cookie, nil);
|
||||
if(reply == nil || !reply->same_screen){
|
||||
free(reply);
|
||||
return 0;
|
||||
}
|
||||
caret->valid = 1;
|
||||
caret->x = reply->dst_x;
|
||||
caret->y = reply->dst_y;
|
||||
caret->h = 0;
|
||||
free(reply);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
placebottom(Ic *state, xcb_window_t win)
|
||||
{
|
||||
xcb_get_geometry_cookie_t gcookie;
|
||||
xcb_get_geometry_reply_t *geometry;
|
||||
int height;
|
||||
|
||||
if(win == XCB_NONE)
|
||||
return 0;
|
||||
gcookie = xcb_get_geometry(conn, win);
|
||||
geometry = xcb_get_geometry_reply(conn, gcookie, nil);
|
||||
if(geometry == nil)
|
||||
return 0;
|
||||
height = geometry->height;
|
||||
free(geometry);
|
||||
if(!translate(win, 0, 0, &state->caret))
|
||||
return 0;
|
||||
state->caret.y += height;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* The popup goes at the client's spot location when it sends one, else
|
||||
* under the focus window, else under the client window. An unset focus
|
||||
* window means the client window, as the XIM spec says.
|
||||
*/
|
||||
static void
|
||||
place(Ic *state)
|
||||
{
|
||||
const xcb_im_preedit_attr_t *attr;
|
||||
xcb_window_t clientwin, focuswin;
|
||||
|
||||
memset(&state->caret, 0, sizeof state->caret);
|
||||
clientwin = xcb_im_input_context_get_client_window(state->xic);
|
||||
focuswin = xcb_im_input_context_get_focus_window(state->xic);
|
||||
if(focuswin == XCB_NONE)
|
||||
focuswin = clientwin;
|
||||
if(xcb_im_input_context_get_preedit_attr_mask(state->xic) &
|
||||
XCB_XIM_XNSpotLocation_MASK){
|
||||
attr = xcb_im_input_context_get_preedit_attr(state->xic);
|
||||
if(translate(focuswin, attr->spot_location.x,
|
||||
attr->spot_location.y, &state->caret))
|
||||
return;
|
||||
}
|
||||
if(placebottom(state, focuswin))
|
||||
return;
|
||||
if(clientwin != focuswin)
|
||||
placebottom(state, clientwin);
|
||||
}
|
||||
|
||||
/* XIM text goes out as COMPOUND_TEXT; imdkit wraps UTF-8 in ESC%G. */
|
||||
static void
|
||||
commit(Ic *state, char *s, int nbyte)
|
||||
{
|
||||
char *wire;
|
||||
size_t nwire;
|
||||
|
||||
if(nbyte == 0)
|
||||
return;
|
||||
wire = xcb_utf8_to_compound_text(s, nbyte, &nwire);
|
||||
if(wire == nil)
|
||||
return;
|
||||
xcb_im_commit_string(xim, state->xic, XCB_XIM_LOOKUP_CHARS,
|
||||
wire, (u32int)nwire, 0);
|
||||
free(wire);
|
||||
}
|
||||
|
||||
static void
|
||||
clearpreedit(Ic *state)
|
||||
{
|
||||
xcb_im_preedit_draw_fr_t frame;
|
||||
|
||||
if(!state->prestarted)
|
||||
return;
|
||||
memset(&frame, 0, sizeof frame);
|
||||
frame.chg_length = state->nprerune;
|
||||
frame.status = 1;
|
||||
xcb_im_preedit_draw_callback(xim, state->xic, &frame);
|
||||
xcb_im_preedit_done_callback(xim, state->xic);
|
||||
state->prestarted = 0;
|
||||
state->nprerune = 0;
|
||||
if(preowner == state)
|
||||
preowner = nil;
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
static void
|
||||
updatepreedit(Ic *state, Str *pre)
|
||||
{
|
||||
xcb_im_preedit_draw_fr_t frame;
|
||||
u32int feedback[Maxrunes];
|
||||
char utf[Maxutf], *wire;
|
||||
size_t nwire;
|
||||
int i, nbyte;
|
||||
|
||||
if(!(state->cap & Cclientpreedit))
|
||||
return;
|
||||
if(pre->n == 0){
|
||||
clearpreedit(state);
|
||||
return;
|
||||
}
|
||||
nbyte = stoutf(pre, utf, sizeof utf);
|
||||
wire = xcb_utf8_to_compound_text(utf, nbyte, &nwire);
|
||||
if(wire == nil)
|
||||
return;
|
||||
for(i = 0; i < pre->n; i++)
|
||||
feedback[i] = XCB_XIM_UNDERLINE;
|
||||
if(!state->prestarted){
|
||||
xcb_im_preedit_start_callback(xim, state->xic);
|
||||
state->prestarted = 1;
|
||||
}
|
||||
memset(&frame, 0, sizeof frame);
|
||||
frame.caret = pre->n;
|
||||
frame.chg_length = state->nprerune;
|
||||
frame.length_of_preedit_string = (u16int)nwire;
|
||||
frame.preedit_string = (uchar*)wire;
|
||||
frame.feedback_array.size = pre->n;
|
||||
frame.feedback_array.items = feedback;
|
||||
xcb_im_preedit_draw_callback(xim, state->xic, &frame);
|
||||
state->nprerune = pre->n;
|
||||
preowner = state;
|
||||
free(wire);
|
||||
}
|
||||
|
||||
static void
|
||||
sendrequest(Ic *state, int op, u32int key, u32int mod, Keyres *res)
|
||||
{
|
||||
Keyreq kr;
|
||||
|
||||
memset(&kr, 0, sizeof kr);
|
||||
kr.owner = state;
|
||||
kr.cap = state->cap;
|
||||
kr.op = op;
|
||||
kr.ks = key;
|
||||
kr.mod = ipcmod(mod);
|
||||
kr.caret = state->caret;
|
||||
kr.reply = replyc;
|
||||
chansend(keyc, &kr);
|
||||
chanrecv(replyc, res);
|
||||
}
|
||||
|
||||
/* Another frontend may have taken the engine; then our preedit is stale. */
|
||||
static void
|
||||
checkpreowner(void)
|
||||
{
|
||||
Keyres res;
|
||||
|
||||
if(preowner == nil)
|
||||
return;
|
||||
sendrequest(preowner, Keycap, 0, 0, &res);
|
||||
if(!res.eaten)
|
||||
clearpreedit(preowner);
|
||||
}
|
||||
|
||||
static void
|
||||
keypress(Ic *state, u32int key, u32int mod, Keyres *res)
|
||||
{
|
||||
if(keymeaningful(key)){
|
||||
if(preowner != nil && preowner != state)
|
||||
clearpreedit(preowner);
|
||||
state->engaged = 1;
|
||||
}
|
||||
sendrequest(state, Keypress, key, mod, res);
|
||||
}
|
||||
|
||||
/* Losing the engine commits the pending text; the release hands it back. */
|
||||
static void
|
||||
release(Ic *state)
|
||||
{
|
||||
Keyres res;
|
||||
char buf[Maxutf];
|
||||
int n;
|
||||
|
||||
if(!state->engaged){
|
||||
clearpreedit(state);
|
||||
return;
|
||||
}
|
||||
sendrequest(state, Keyrelease, 0, 0, &res);
|
||||
state->engaged = 0;
|
||||
n = stoutf(&res.commit, buf, sizeof buf);
|
||||
commit(state, buf, n);
|
||||
clearpreedit(state);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/* XIM reset hands the pending text back to the client as committed text. */
|
||||
static void
|
||||
resetic(Ic *state, xcb_im_reset_ic_reply_fr_t *reply)
|
||||
{
|
||||
Keyres res;
|
||||
char utf[Maxutf], *wire;
|
||||
size_t nwire;
|
||||
int nbyte;
|
||||
|
||||
if(!state->engaged){
|
||||
clearpreedit(state);
|
||||
return;
|
||||
}
|
||||
sendrequest(state, Keyreset, 0, 0, &res);
|
||||
clearpreedit(state);
|
||||
if(reply == nil || res.commit.n == 0)
|
||||
return;
|
||||
nbyte = stoutf(&res.commit, utf, sizeof utf);
|
||||
wire = xcb_utf8_to_compound_text(utf, nbyte, &nwire);
|
||||
if(wire == nil)
|
||||
return;
|
||||
reply->committed_string = (uchar*)wire;
|
||||
reply->byte_length_of_committed_string = nwire;
|
||||
}
|
||||
|
||||
static void
|
||||
kpress(Ic *state, xcb_key_press_event_t *ev)
|
||||
{
|
||||
Keyres res;
|
||||
char buf[Maxutf];
|
||||
u32int key, sym;
|
||||
int composed, n, wasvalid;
|
||||
|
||||
sym = keymaplookup(kstate, ev->detail, ev->state);
|
||||
composed = 0;
|
||||
if(compose != nil &&
|
||||
xkb_compose_state_feed(compose, sym) == XKB_COMPOSE_FEED_ACCEPTED)
|
||||
switch(xkb_compose_state_get_status(compose)){
|
||||
case XKB_COMPOSE_COMPOSING:
|
||||
case XKB_COMPOSE_CANCELLED:
|
||||
return;
|
||||
case XKB_COMPOSE_COMPOSED:
|
||||
sym = xkb_compose_state_get_one_sym(compose);
|
||||
composed = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
key = ipckeysym(sym, xkb_keysym_to_utf32(sym));
|
||||
if(keymeaningful(key)){
|
||||
wasvalid = state->caret.valid;
|
||||
place(state);
|
||||
if(state->engaged && wasvalid && !state->caret.valid)
|
||||
sendrequest(state, Keycaret, 0, 0, &res);
|
||||
}
|
||||
keypress(state, key, ev->state, &res);
|
||||
n = stoutf(&res.commit, buf, sizeof buf);
|
||||
commit(state, buf, n);
|
||||
updatepreedit(state, &res.preedit);
|
||||
if(res.eaten)
|
||||
;
|
||||
else if(composed){
|
||||
/* The event holds the last key of the sequence, not its result. */
|
||||
n = xkb_keysym_to_utf8(sym, buf, sizeof buf);
|
||||
commit(state, buf, max(n - 1, 0));
|
||||
}else
|
||||
xcb_im_forward_event(xim, state->xic, ev);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
static void
|
||||
icunlink(Ic *state)
|
||||
{
|
||||
Ic **p;
|
||||
|
||||
for(p = &ics; *p != nil; p = &(*p)->next)
|
||||
if(*p == state){
|
||||
*p = state->next;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
icfree(void *p)
|
||||
{
|
||||
Ic *state;
|
||||
|
||||
state = p;
|
||||
/* The owner remains valid until imthread acknowledges its release. */
|
||||
release(state);
|
||||
icunlink(state);
|
||||
free(state);
|
||||
}
|
||||
|
||||
static void
|
||||
iccreate(xcb_im_client_t *client, xcb_im_input_context_t *ic)
|
||||
{
|
||||
Ic *state;
|
||||
|
||||
state = emalloc(sizeof(Ic));
|
||||
state->xic = ic;
|
||||
state->client = client;
|
||||
if(xcb_im_input_context_get_input_style(ic) ==
|
||||
(XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing))
|
||||
state->cap = Cclientpreedit;
|
||||
state->next = ics;
|
||||
ics = state;
|
||||
xcb_im_input_context_set_data(ic, state, icfree);
|
||||
}
|
||||
|
||||
static void
|
||||
callback(xcb_im_t *im, xcb_im_client_t *client, xcb_im_input_context_t *ic,
|
||||
const xcb_im_packet_header_fr_t *hdr, void *frame, void *arg, void *user)
|
||||
{
|
||||
xcb_key_press_event_t *ev;
|
||||
Keyres res;
|
||||
Ic *state;
|
||||
|
||||
USED(im);
|
||||
USED(frame);
|
||||
USED(user);
|
||||
if(hdr->major_opcode == XCB_XIM_DISCONNECT ||
|
||||
hdr->major_opcode == XCB_XIM_CLOSE){
|
||||
for(state = ics; state != nil; state = state->next)
|
||||
if(state->client == client)
|
||||
release(state);
|
||||
return;
|
||||
}
|
||||
if(hdr->major_opcode == XCB_XIM_CREATE_IC){
|
||||
iccreate(client, ic);
|
||||
return;
|
||||
}
|
||||
if(ic == nil)
|
||||
return;
|
||||
state = xcb_im_input_context_get_data(ic);
|
||||
switch(hdr->major_opcode){
|
||||
case XCB_XIM_FORWARD_EVENT:
|
||||
ev = arg;
|
||||
if(ev != nil && (ev->response_type & ~0x80) == XCB_KEY_PRESS)
|
||||
kpress(state, ev);
|
||||
break;
|
||||
case XCB_XIM_SET_IC_VALUES:
|
||||
case XCB_XIM_SET_IC_FOCUS:
|
||||
place(state);
|
||||
if(state->engaged)
|
||||
sendrequest(state, Keycaret, 0, 0, &res);
|
||||
break;
|
||||
case XCB_XIM_DESTROY_IC:
|
||||
case XCB_XIM_UNSET_IC_FOCUS:
|
||||
release(state);
|
||||
break;
|
||||
case XCB_XIM_RESET_IC:
|
||||
resetic(state, arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
ximinit(void)
|
||||
{
|
||||
xcb_screen_t *screen;
|
||||
xcb_window_t win;
|
||||
xcb_im_styles_t st;
|
||||
xcb_im_encodings_t enc;
|
||||
int scr;
|
||||
|
||||
replyc = chancreate(sizeof(Keyres), 0);
|
||||
st.nStyles = nelem(styles);
|
||||
st.styles = styles;
|
||||
enc.nEncodings = nelem(encs);
|
||||
enc.encodings = encs;
|
||||
xcb_compound_text_init();
|
||||
conn = xcb_connect(nil, &scr);
|
||||
if(xcb_connection_has_error(conn)){
|
||||
ximlog("cannot connect to X server");
|
||||
return -1;
|
||||
}
|
||||
screen = getscreen(scr);
|
||||
if(screen == nil){
|
||||
ximlog("cannot find X screen");
|
||||
return -1;
|
||||
}
|
||||
rootwin = screen->root;
|
||||
if(kinit() < 0){
|
||||
ximlog("cannot read keyboard mapping");
|
||||
return -1;
|
||||
}
|
||||
cinit();
|
||||
kwatch();
|
||||
win = xcb_generate_id(conn);
|
||||
xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, screen->root,
|
||||
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||
screen->root_visual, 0, nil);
|
||||
xim = xcb_im_create(conn, scr, win, "strans",
|
||||
XCB_IM_ALL_LOCALES, &st, nil, nil, &enc,
|
||||
XCB_EVENT_MASK_KEY_PRESS, callback, nil);
|
||||
if(xim == nil){
|
||||
ximlog("cannot create XIM server");
|
||||
return -1;
|
||||
}
|
||||
if(!xcb_im_open_im(xim)){
|
||||
ximlog("cannot claim XIM selection");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
ximthread(void *arg)
|
||||
{
|
||||
struct pollfd pfd;
|
||||
xcb_generic_event_t *ev;
|
||||
uint8_t type;
|
||||
int n;
|
||||
|
||||
USED(arg);
|
||||
threadsetname("xim");
|
||||
if(ximinit() < 0)
|
||||
die("xim: initialization failed");
|
||||
pfd.fd = xcb_get_file_descriptor(conn);
|
||||
pfd.events = POLLIN;
|
||||
for(;;){
|
||||
pfd.revents = 0;
|
||||
n = poll(&pfd, 1, preowner != nil ? Ownerpoll : -1);
|
||||
if(n < 0 && errno != EINTR)
|
||||
break;
|
||||
while((ev = xcb_poll_for_event(conn)) != nil){
|
||||
type = ev->response_type & ~0x80;
|
||||
if(type == xkbevent){
|
||||
if(kinit() < 0)
|
||||
ximlog("cannot refresh keyboard mapping");
|
||||
}else
|
||||
xcb_im_filter_event(xim, ev);
|
||||
free(ev);
|
||||
}
|
||||
checkpreowner();
|
||||
if(pfd.revents & (POLLERR|POLLHUP|POLLNVAL))
|
||||
break;
|
||||
}
|
||||
if(xcb_connection_has_error(conn))
|
||||
die("xim: X server disconnected");
|
||||
die("xim: frontend stopped");
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <xcb-imdkit/encoding.h>
|
||||
|
||||
static char*
|
||||
ximutf8compound(const char *s, size_t len, size_t *outlen)
|
||||
{
|
||||
static const char begin[] = "\033%G";
|
||||
static const char end[] = "\033%@";
|
||||
char *ct;
|
||||
size_t n;
|
||||
|
||||
if(s == NULL || len > SIZE_MAX - (sizeof begin + sizeof end - 1))
|
||||
return NULL;
|
||||
n = len + sizeof begin + sizeof end - 2;
|
||||
ct = malloc(n + 1);
|
||||
if(ct == NULL)
|
||||
return NULL;
|
||||
memcpy(ct, begin, sizeof begin - 1);
|
||||
memcpy(ct + sizeof begin - 1, s, len);
|
||||
memcpy(ct + sizeof begin - 1 + len, end, sizeof end - 1);
|
||||
ct[n] = '\0';
|
||||
if(outlen != NULL)
|
||||
*outlen = n;
|
||||
return ct;
|
||||
}
|
||||
|
||||
char*
|
||||
ximcompound(const char *s, size_t len, size_t *outlen)
|
||||
{
|
||||
char *ct;
|
||||
|
||||
if(s == NULL)
|
||||
return NULL;
|
||||
ct = xcb_utf8_to_compound_text(s, len, outlen);
|
||||
if(ct != NULL)
|
||||
return ct;
|
||||
/* X.Org Compound Text extension for characters in no legacy charset. */
|
||||
return ximutf8compound(s, len, outlen);
|
||||
}
|
||||
Reference in New Issue
Block a user