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
|
/strans
|
||||||
/tests/*.o
|
/tests/*.o
|
||||||
/tests/unit_test
|
/tests/unit_test
|
||||||
|
/tests/stress_test
|
||||||
/tests/ibus_live_test
|
/tests/ibus_live_test
|
||||||
|
/tests/ibus_client_smoke
|
||||||
|
/tests/gtk_live_test
|
||||||
|
/tests/xim_live_test
|
||||||
/tests/ipc_live_test
|
/tests/ipc_live_test
|
||||||
/tests/daemon_collision_test
|
/tests/daemon_collision_test
|
||||||
/tests/daemon_failure_test
|
/tests/daemon_failure_test
|
||||||
/tests/daemon_restart_test
|
/tests/daemon_restart_test
|
||||||
/xim/*.o
|
/xim/*.o
|
||||||
/xim/strans-xim
|
|
||||||
/xim/xim_test
|
/xim/xim_test
|
||||||
/gtk/im-strans.so
|
/gtk/im-strans.so
|
||||||
/bench/bench
|
/bench/bench
|
||||||
|
|||||||
19
Dockerfile
19
Dockerfile
@@ -1,23 +1,40 @@
|
|||||||
FROM archlinux:base@sha256:b0deabeb3d283da2c7f7dbf0eea051b7b2cd0554e0b737cc457fd21683bdcdd1
|
FROM archlinux:base@sha256:b0deabeb3d283da2c7f7dbf0eea051b7b2cd0554e0b737cc457fd21683bdcdd1
|
||||||
|
|
||||||
RUN pacman -Syu --noconfirm --needed \
|
RUN pacman -Syu --noconfirm --needed \
|
||||||
|
cairo \
|
||||||
|
fontconfig \
|
||||||
diffutils \
|
diffutils \
|
||||||
freetype2 \
|
|
||||||
gcc \
|
gcc \
|
||||||
gtk3 \
|
gtk3 \
|
||||||
|
ibus \
|
||||||
|
harfbuzz \
|
||||||
|
libx11 \
|
||||||
libxcb \
|
libxcb \
|
||||||
libxkbcommon \
|
libxkbcommon \
|
||||||
make \
|
make \
|
||||||
|
noto-fonts-emoji \
|
||||||
|
pango \
|
||||||
pkgconf \
|
pkgconf \
|
||||||
plan9port \
|
plan9port \
|
||||||
python \
|
python \
|
||||||
ttf-dejavu \
|
ttf-dejavu \
|
||||||
ttf-jigmo \
|
ttf-jigmo \
|
||||||
|
valgrind \
|
||||||
which \
|
which \
|
||||||
|
xorg-server-xvfb \
|
||||||
xcb-imdkit \
|
xcb-imdkit \
|
||||||
dbus \
|
dbus \
|
||||||
&& pacman -Scc --noconfirm
|
&& 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 printf "egrep='grep -E'\n" > /usr/lib/plan9/config
|
||||||
RUN dbus-uuidgen --ensure=/etc/machine-id
|
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
|
- `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
|
`map/kanji.dict` grants the option to use GPL version 2 or any later
|
||||||
version.
|
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.
|
No repository-wide license has been declared for original strans source.
|
||||||
|
|||||||
76
Makefile
76
Makefile
@@ -1,37 +1,50 @@
|
|||||||
CC = 9c
|
CC = 9c
|
||||||
LD = 9l
|
LD = 9l
|
||||||
PKG_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
|
PKG_CONFIG ?= pkg-config
|
||||||
PKG_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
|
CFLAGS ?= -O2 -g
|
||||||
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
|
WARN_CFLAGS = -Wall -Wextra
|
||||||
FT_LIBS = $(shell pkg-config --libs freetype2)
|
IBUS_CFLAGS = $(shell $(PKG_CONFIG) --cflags dbus-1 xkbcommon)
|
||||||
CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS)
|
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
|
PROG = strans
|
||||||
DOCKER_IMAGE = strans-build
|
DOCKER_IMAGE = strans-build
|
||||||
DOCKER_RUN = docker run --rm --user "$$(id -u):$$(id -g)" \
|
DOCKER_RUN = docker run --rm --user "$$(id -u):$$(id -g)" \
|
||||||
-v "$(CURDIR):/src" $(DOCKER_IMAGE)
|
-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
|
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)
|
$(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
|
$(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:
|
clean:
|
||||||
rm -f $(OBJS) $(PROG)
|
rm -f $(OBJS) $(PROG)
|
||||||
$(MAKE) -C tests clean
|
$(MAKE) -C tests clean
|
||||||
$(MAKE) -C xim/ clean
|
|
||||||
$(MAKE) -C gtk/ clean
|
$(MAKE) -C gtk/ clean
|
||||||
$(MAKE) -C bench/ clean
|
$(MAKE) -C bench/ clean
|
||||||
|
|
||||||
xim:
|
|
||||||
$(MAKE) -C xim/
|
|
||||||
|
|
||||||
gtk:
|
gtk:
|
||||||
$(MAKE) -C gtk/
|
$(MAKE) -C gtk/
|
||||||
|
|
||||||
@@ -42,22 +55,38 @@ docker-image: Dockerfile
|
|||||||
docker build -t $(DOCKER_IMAGE) - < Dockerfile
|
docker build -t $(DOCKER_IMAGE) - < Dockerfile
|
||||||
|
|
||||||
docker-build:
|
docker-build:
|
||||||
$(DOCKER_RUN) make -B all
|
$(DOCKER_RUN) sh -c 'make clean && make all'
|
||||||
|
|
||||||
docker-check:
|
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-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
|
docker: docker-image
|
||||||
$(MAKE) docker-build
|
$(MAKE) docker-build
|
||||||
|
|
||||||
check: verify-map test
|
check: verify-map
|
||||||
$(MAKE) -C xim check
|
$(MAKE) -C tests check UNITARGS="$(UNITARGS)"
|
||||||
|
|
||||||
test: $(PROG)
|
check-live: $(PROG) gtk
|
||||||
$(MAKE) -C tests check TESTARGS="$(TESTARGS)"
|
$(MAKE) -C tests check-live
|
||||||
|
|
||||||
|
check-stress: $(PROG)
|
||||||
|
$(MAKE) -C tests check-stress
|
||||||
|
|
||||||
|
test: check
|
||||||
|
|
||||||
verify-map:
|
verify-map:
|
||||||
python3 map/mktelex.py | cmp - map/telex.map
|
python3 map/mktelex.py | cmp - map/telex.map
|
||||||
@@ -68,5 +97,6 @@ verify-map:
|
|||||||
python3 -B tests/mkhanja_test.py
|
python3 -B tests/mkhanja_test.py
|
||||||
python3 -B tests/skk2ktrans_test.py
|
python3 -B tests/skk2ktrans_test.py
|
||||||
|
|
||||||
.PHONY: all check test verify-map clean xim gtk bench docker docker-image \
|
.PHONY: all check check-live check-stress test verify-map clean gtk bench \
|
||||||
docker-build docker-check docker-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+V` | Vietnamese Telex |
|
||||||
| `Ctrl+E` | Emoji and symbol search |
|
| `Ctrl+E` | Emoji and symbol search |
|
||||||
| `Ctrl+H` | One-shot Hanja 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,
|
Use `Up`/`Down` to move through candidates and `PageUp`/`PageDown` to move by
|
||||||
`Tab` to cycle temporary search results, and `Esc` to cancel. `0` commits the
|
nine without wrapping. An unmodified `1`-`9` selects that row on the current
|
||||||
current reading without conversion.
|
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.
|
Hiragana mode composes a complete reading before offering Kanji candidates.
|
||||||
Katakana mode does not perform Kanji conversion. Emoji and Hanja searches
|
Katakana mode does not perform Kanji conversion. Emoji and Hanja searches
|
||||||
return to the previous mode after use. Hanja lookup currently supports one
|
return to the previous mode after use; switching language during a search
|
||||||
modern Hangul syllable at a time.
|
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
|
## 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
|
```sh
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
make docker-image
|
make docker-image
|
||||||
make docker-build
|
make docker-build
|
||||||
make docker-check
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Build output:
|
Build output:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
strans daemon and IBus frontend
|
strans daemon with IBus and XIM frontends
|
||||||
xim/strans-xim XIM frontend
|
gtk/im-strans.so GTK 3 frontend module
|
||||||
gtk/im-strans.so GTK 3 frontend
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Use `make docker-check TESTARGS=hangul` to run selected C tests. Native
|
Tests have three tiers:
|
||||||
`make all`, `make check`, and `make bench` are also available when the
|
|
||||||
dependencies listed in [`Dockerfile`](Dockerfile) are installed.
|
```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
|
## Run
|
||||||
|
|
||||||
The popup needs one to four scalable font files. No fonts are bundled.
|
The popup uses the normal system fonts found by Pango and Fontconfig; no font
|
||||||
`run.sh` uses installed DejaVu, Jigmo, or Noto CJK fonts when available, or
|
file argument or system-wide strans installation is required. Run it directly
|
||||||
accepts explicit files in fallback order:
|
from the build tree:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./run.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
|
`run.sh` stops every same-user process whose exact name is `strans`, starts one
|
||||||
restarts any existing `strans` processes owned by the current user.
|
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:
|
Configure clients as needed:
|
||||||
|
|
||||||
@@ -71,14 +134,31 @@ export XMODIFIERS=@im=strans
|
|||||||
|
|
||||||
# GTK 3 module
|
# GTK 3 module
|
||||||
doas make -C gtk install
|
doas make -C gtk install
|
||||||
|
export GTK_IM_MODULE=strans
|
||||||
|
|
||||||
# IBus client example
|
# IBus client example
|
||||||
GLFW_IM_MODULE=ibus kitty
|
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
|
strans provides its own IBus endpoint; `ibus-daemon` and fcitx are not
|
||||||
required. Without `DISPLAY`, the daemon and IBus frontend still work, but the
|
required. Without `DISPLAY`, the daemon and IBus frontend still work, but XIM
|
||||||
popup is disabled.
|
is not started and the popup is disabled.
|
||||||
|
|
||||||
## Dictionary data
|
## Dictionary data
|
||||||
|
|
||||||
@@ -93,9 +173,14 @@ make verify-map
|
|||||||
See [`map/README`](map/README) for Hanja import and Japanese dictionary
|
See [`map/README`](map/README) for Hanja import and Japanese dictionary
|
||||||
generation.
|
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
|
## Licensing
|
||||||
|
|
||||||
Data and third-party notices are in
|
Third-party license notices are in [`LICENSES`](LICENSES). The repository does
|
||||||
[`docs/PROVENANCE.md`](docs/PROVENANCE.md) and
|
not currently declare a license for the strans source as a whole.
|
||||||
[`font/PROVENANCE`](font/PROVENANCE). 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
|
cd "$(dirname "$0")" || exit 1
|
||||||
|
|
||||||
case ${XDG_RUNTIME_DIR-} in
|
if test "$#" -ne 0; then
|
||||||
/) ipc=/strans.sock ;;
|
echo "usage: bench.sh" >&2
|
||||||
/*/) 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
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -29,70 +19,36 @@ cleanup()
|
|||||||
if test -n "$perf_pid"; then
|
if test -n "$perf_pid"; then
|
||||||
kill -INT "$perf_pid" 2>/dev/null || :
|
kill -INT "$perf_pid" 2>/dev/null || :
|
||||||
wait "$perf_pid" 2>/dev/null || :
|
wait "$perf_pid" 2>/dev/null || :
|
||||||
perf_pid=
|
|
||||||
fi
|
fi
|
||||||
if test -n "$strans_pid"; then
|
if test -n "$strans_pid"; then
|
||||||
kill "$strans_pid" 2>/dev/null || :
|
kill "$strans_pid" 2>/dev/null || :
|
||||||
wait "$strans_pid" 2>/dev/null || :
|
wait "$strans_pid" 2>/dev/null || :
|
||||||
strans_pid=
|
|
||||||
fi
|
fi
|
||||||
exit "$status"
|
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 cleanup 0
|
||||||
trap 'exit 129' 1
|
trap 'exit 129' 1
|
||||||
trap 'exit 130' 2
|
trap 'exit 130' 2
|
||||||
trap 'exit 143' 15
|
trap 'exit 143' 15
|
||||||
|
|
||||||
./strans map "$@" &
|
./strans map &
|
||||||
strans_pid=$!
|
strans_pid=$!
|
||||||
|
|
||||||
|
# The daemon is ready once the benchmark client can complete a round.
|
||||||
attempt=0
|
attempt=0
|
||||||
while test "$attempt" -lt 10; do
|
until ./bench/bench bench/bench.keys 1 >/dev/null 2>&1; do
|
||||||
if test -S "$ipc" && ibusready; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
if ! kill -0 "$strans_pid" 2>/dev/null; then
|
if ! kill -0 "$strans_pid" 2>/dev/null; then
|
||||||
if wait "$strans_pid"; then
|
echo "bench.sh: daemon exited before accepting IPC clients" >&2
|
||||||
status=0
|
exit 1
|
||||||
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"
|
|
||||||
fi
|
fi
|
||||||
attempt=$((attempt + 1))
|
attempt=$((attempt + 1))
|
||||||
if test "$attempt" -lt 10; then
|
if test "$attempt" -ge 50; then
|
||||||
sleep 1
|
echo "bench.sh: daemon did not accept IPC clients within 10 seconds" >&2
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
sleep 0.2
|
||||||
done
|
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
|
# warm up the renderer
|
||||||
./bench/bench bench/bench.keys 100
|
./bench/bench bench/bench.keys 100
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
CC = cc
|
CC = cc
|
||||||
CFLAGS = -Wall -O2
|
CFLAGS ?= -O2
|
||||||
|
|
||||||
bench: main.c ../ipc.c ../ipc.h
|
bench: main.c ../ipc.c ../ipc.h
|
||||||
$(CC) $(CFLAGS) -I.. -o $@ main.c ../ipc.c
|
$(CC) $(CPPFLAGS) -I.. -Wall $(CFLAGS) $(LDFLAGS) -o $@ \
|
||||||
|
main.c ../ipc.c $(LDLIBS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f bench
|
rm -f bench
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include "../ipc.h"
|
#include "ipc.h"
|
||||||
|
|
||||||
typedef struct Key Key;
|
typedef struct Key Key;
|
||||||
struct Key {
|
struct Key {
|
||||||
@@ -128,10 +128,10 @@ sendkey(int key, int mod)
|
|||||||
static int
|
static int
|
||||||
readresp(void)
|
readresp(void)
|
||||||
{
|
{
|
||||||
char buf[Ipcfieldmax+1];
|
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
|
|
||||||
return ipcreadresp(fd, 0, buf, sizeof buf, NULL, 0, &resp);
|
return ipcreadresp(fd, 0, commit, preedit, &resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static double
|
static double
|
||||||
|
|||||||
120
dat.h
120
dat.h
@@ -7,6 +7,7 @@
|
|||||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||||
|
|
||||||
|
/* A language id is the control code of the Ctrl key that selects it. */
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
LangEN = 0x14,
|
LangEN = 0x14,
|
||||||
@@ -17,8 +18,6 @@ enum
|
|||||||
LangEMOJI = 0x05,
|
LangEMOJI = 0x05,
|
||||||
LangVI = 0x16,
|
LangVI = 0x16,
|
||||||
|
|
||||||
Fontsz = 32,
|
|
||||||
Maxfonts = 4,
|
|
||||||
Maxrunes = 64,
|
Maxrunes = 64,
|
||||||
Maxutf = Maxrunes * UTFmax + 1,
|
Maxutf = Maxrunes * UTFmax + 1,
|
||||||
};
|
};
|
||||||
@@ -26,16 +25,26 @@ enum
|
|||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Maxclients = 64,
|
Maxclients = 64,
|
||||||
Maxkouho = 32,
|
Maxkouho = 128,
|
||||||
Maxdisp = 9,
|
Maxdisp = 9,
|
||||||
Imgw = (Maxrunes + 3) * Fontsz,
|
|
||||||
Imgh = (Maxdisp + 1) * Fontsz,
|
|
||||||
|
|
||||||
Colfg = 0x000000,
|
Colfg = 0x000000,
|
||||||
Colbg = 0xffffff,
|
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;
|
typedef struct Str Str;
|
||||||
struct Str
|
struct Str
|
||||||
{
|
{
|
||||||
@@ -43,53 +52,43 @@ struct Str
|
|||||||
int n;
|
int n;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* What one key does to a composition: text to commit, the new pending
|
||||||
|
* text, and the new raw state (Telex key history, or pending Japanese
|
||||||
|
* romaji), given the old ones in Im.
|
||||||
|
*/
|
||||||
typedef struct Emit Emit;
|
typedef struct Emit Emit;
|
||||||
struct Emit
|
struct Emit
|
||||||
{
|
{
|
||||||
int eat;
|
int eat;
|
||||||
Str s;
|
Str s;
|
||||||
Str next;
|
Str next;
|
||||||
Str dict;
|
Str raw;
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct Hnode Hnode;
|
|
||||||
struct Hnode
|
|
||||||
{
|
|
||||||
int filled;
|
|
||||||
int next;
|
|
||||||
char *key;
|
|
||||||
int klen;
|
|
||||||
char *val;
|
|
||||||
int vlen;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Tnode Tnode;
|
typedef struct Tnode Tnode;
|
||||||
struct Tnode
|
struct Tnode
|
||||||
{
|
{
|
||||||
|
char *val;
|
||||||
|
int vlen;
|
||||||
int child;
|
int child;
|
||||||
int sibling;
|
int sibling;
|
||||||
char c;
|
char c;
|
||||||
char *val;
|
|
||||||
int vlen;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Trie Trie;
|
typedef struct Trie Trie;
|
||||||
struct Trie
|
struct Trie
|
||||||
{
|
{
|
||||||
int root;
|
|
||||||
Tnode *nodes;
|
Tnode *nodes;
|
||||||
int n;
|
int n;
|
||||||
int cap;
|
int cap;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Hmap Hmap;
|
enum
|
||||||
struct Hmap
|
|
||||||
{
|
{
|
||||||
int nbs;
|
TrieMiss,
|
||||||
int nsz;
|
TriePrefix,
|
||||||
int len;
|
TrieExact,
|
||||||
int cap;
|
|
||||||
uchar *nodes;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Lang Lang;
|
typedef struct Lang Lang;
|
||||||
@@ -101,9 +100,8 @@ struct Lang
|
|||||||
char *dictname;
|
char *dictname;
|
||||||
Emit (*trans)(Im*, Rune);
|
Emit (*trans)(Im*, Rune);
|
||||||
void (*back)(Im*);
|
void (*back)(Im*);
|
||||||
void (*dictq)(Im*);
|
|
||||||
Trie *map;
|
Trie *map;
|
||||||
Hmap *dict;
|
Trie *dict;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Im
|
struct Im
|
||||||
@@ -119,6 +117,15 @@ struct Im
|
|||||||
|
|
||||||
typedef struct Drawcmd Drawcmd;
|
typedef struct Drawcmd Drawcmd;
|
||||||
typedef struct Caret Caret;
|
typedef struct Caret Caret;
|
||||||
|
typedef struct Area Area;
|
||||||
|
struct Area
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
int w;
|
||||||
|
int h;
|
||||||
|
};
|
||||||
|
|
||||||
struct Caret
|
struct Caret
|
||||||
{
|
{
|
||||||
int valid;
|
int valid;
|
||||||
@@ -133,21 +140,52 @@ struct Drawcmd
|
|||||||
Str kouho[Maxdisp];
|
Str kouho[Maxdisp];
|
||||||
int nkouho;
|
int nkouho;
|
||||||
int sel;
|
int sel;
|
||||||
|
int first;
|
||||||
|
int total;
|
||||||
Caret caret;
|
Caret caret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Popup geometry from popuplayout: n rows from row0 of the candidates,
|
||||||
|
* each Fontsz tall; a section's y is -1 when it is absent. Numbers
|
||||||
|
* start at PopupPad, text at PopupPad+PopupNumw.
|
||||||
|
*/
|
||||||
|
typedef struct Popup Popup;
|
||||||
|
struct Popup
|
||||||
|
{
|
||||||
|
int w;
|
||||||
|
int h;
|
||||||
|
int n;
|
||||||
|
int row0;
|
||||||
|
int prey;
|
||||||
|
int sepy;
|
||||||
|
int rowsy;
|
||||||
|
int textw;
|
||||||
|
int marky;
|
||||||
|
int markx;
|
||||||
|
int markw;
|
||||||
|
Str mark;
|
||||||
|
int sely;
|
||||||
|
int selw;
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct Keyreq Keyreq;
|
typedef struct Keyreq Keyreq;
|
||||||
typedef struct Keyres Keyres;
|
typedef struct Keyres Keyres;
|
||||||
enum
|
enum
|
||||||
|
{
|
||||||
|
Cclientpreedit = 1<<0,
|
||||||
|
};
|
||||||
|
enum
|
||||||
{
|
{
|
||||||
Keypress,
|
Keypress,
|
||||||
Keyreset,
|
Keyreset,
|
||||||
Keyrelease,
|
Keyrelease,
|
||||||
Keycaret,
|
Keycaret,
|
||||||
|
Keycap,
|
||||||
};
|
};
|
||||||
struct Keyres
|
struct Keyres
|
||||||
{
|
{
|
||||||
int eaten;
|
int eaten; /* Keycap: caller is active and capability was applied. */
|
||||||
Str commit;
|
Str commit;
|
||||||
Str preedit;
|
Str preedit;
|
||||||
};
|
};
|
||||||
@@ -155,6 +193,7 @@ struct Keyres
|
|||||||
struct Keyreq
|
struct Keyreq
|
||||||
{
|
{
|
||||||
void *owner; /* stable until the context's release is acknowledged */
|
void *owner; /* stable until the context's release is acknowledged */
|
||||||
|
int cap;
|
||||||
int op;
|
int op;
|
||||||
u32int ks;
|
u32int ks;
|
||||||
u32int mod;
|
u32int mod;
|
||||||
@@ -162,26 +201,7 @@ struct Keyreq
|
|||||||
Channel *reply;
|
Channel *reply;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Dictreq Dictreq;
|
|
||||||
struct Dictreq
|
|
||||||
{
|
|
||||||
Str key;
|
|
||||||
Str pre;
|
|
||||||
int lang;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct Dictres Dictres;
|
|
||||||
struct Dictres
|
|
||||||
{
|
|
||||||
Str key;
|
|
||||||
Str kouho[Maxkouho];
|
|
||||||
int nkouho;
|
|
||||||
int lang;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Lang langs[];
|
extern Lang langs[];
|
||||||
extern int nlang;
|
extern int nlang;
|
||||||
extern Channel *drawc;
|
extern Channel *drawc;
|
||||||
extern Channel *keyc;
|
extern Channel *keyc;
|
||||||
extern Channel *dictreqc;
|
|
||||||
extern Channel *dictresc;
|
|
||||||
|
|||||||
141
dict.c
141
dict.c
@@ -1,122 +1,59 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.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;
|
char *p, *e, *sp;
|
||||||
Str tmp;
|
Str tmp;
|
||||||
|
int n, vlen;
|
||||||
|
|
||||||
res->key = req->pre;
|
if(l == nil || key->n == 0 ||
|
||||||
res->nkouho = 0;
|
trielookup(l->dict, key, &p, &vlen) != TrieExact)
|
||||||
res->lang = req->lang;
|
return 0;
|
||||||
if(req->key.n == 0)
|
n = 0;
|
||||||
return;
|
e = p + vlen;
|
||||||
l = getlang(req->lang);
|
while(n < max && p < e){
|
||||||
dict = l ? l->dict : nil;
|
|
||||||
if(dict == nil)
|
|
||||||
return;
|
|
||||||
n = hmapget(dict, &req->key);
|
|
||||||
if(n == nil || n->vlen == 0)
|
|
||||||
return;
|
|
||||||
p = n->val;
|
|
||||||
e = p + n->vlen;
|
|
||||||
while(res->nkouho < Maxkouho && p < e){
|
|
||||||
while(p < e && *p == ' ')
|
while(p < e && *p == ' ')
|
||||||
p++;
|
p++;
|
||||||
if(p == e)
|
|
||||||
break;
|
|
||||||
sp = p;
|
sp = p;
|
||||||
while(p < e && *p != ' ')
|
while(p < e && *p != ' ')
|
||||||
p++;
|
p++;
|
||||||
sinit(&tmp, sp, p - sp);
|
if(sinit(&tmp, sp, p - sp) && tmp.n > 0 &&
|
||||||
if(req->lang == LangEMOJI || scmp(&tmp, &req->key) != 0)
|
(l->lang == LangEMOJI || scmp(&tmp, key) != 0))
|
||||||
res->kouho[res->nkouho++] = tmp;
|
out[n++] = tmp;
|
||||||
if(p < e)
|
|
||||||
p++;
|
|
||||||
}
|
}
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static Trie*
|
||||||
dictthread(void*)
|
langopen(char *dir, char *name, char *ext)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
char *path;
|
char *path;
|
||||||
int i;
|
Trie *t;
|
||||||
|
|
||||||
for(i = 0; i < nlang; i++){
|
path = smprint("%s/%s.%s", dir, name, ext);
|
||||||
if(langs[i].dictname == nil)
|
if(path == nil)
|
||||||
continue;
|
die("out of memory");
|
||||||
path = smprint("%s/%s.dict", dir, langs[i].dictname);
|
t = trieopen(path);
|
||||||
if(path == nil)
|
free(path);
|
||||||
die("out of memory");
|
return t;
|
||||||
langs[i].dict = dictopen(path);
|
}
|
||||||
free(path);
|
|
||||||
|
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 die(char*, ...);
|
||||||
|
|
||||||
void sinit(Str*, char*, int);
|
int sinit(Str*, char*, int);
|
||||||
void sclear(Str*);
|
void sclear(Str*);
|
||||||
void sputr(Str*, Rune);
|
void sputr(Str*, Rune);
|
||||||
void spopr(Str*);
|
void spopr(Str*);
|
||||||
@@ -9,28 +9,28 @@ int scmp(Str*, Str*);
|
|||||||
int stoutf(Str*, char*, int);
|
int stoutf(Str*, char*, int);
|
||||||
Rune slastr(Str*);
|
Rune slastr(Str*);
|
||||||
|
|
||||||
Hmap* hmapalloc(int);
|
|
||||||
void hmapfree(Hmap*);
|
|
||||||
void hmapset(Hmap**, Str*, const char*, int);
|
|
||||||
Hnode* hmapget(Hmap*, Str*);
|
|
||||||
int mapget(Trie*, Str*, Str*);
|
int mapget(Trie*, Str*, Str*);
|
||||||
|
|
||||||
|
Trie* trienew(void);
|
||||||
|
void trieput(Trie*, char*, int, char*, int);
|
||||||
Trie* trieopen(char*);
|
Trie* trieopen(char*);
|
||||||
void trieclose(Trie*);
|
void trieclose(Trie*);
|
||||||
char* trieget(Trie*, char*, int, int*);
|
int trielookup(Trie*, Str*, char**, int*);
|
||||||
int trielookup(Trie*, char*, int, char**, int*);
|
|
||||||
|
|
||||||
Lang* getlang(int);
|
Lang* getlang(int);
|
||||||
void mapinit(char*);
|
void langinit(char*);
|
||||||
void dictinit(char*);
|
|
||||||
|
|
||||||
void dictthread(void*);
|
int dictlookup(Lang*, Str*, Str*, int);
|
||||||
void dictlookup(Dictreq*, Dictres*);
|
|
||||||
|
|
||||||
void drawthread(void*);
|
void drawthread(void*);
|
||||||
int popupcells(Rune*, int);
|
void popuparea(Area*, int, Area*, int, int, Area*);
|
||||||
void popupposition(Caret*, int, int, int, int, int, int, int*, int*);
|
void popupposition(Caret*, int, int, Area*, int, int, int*, int*);
|
||||||
|
int pagemarker(char*, int, int, int, int);
|
||||||
|
void popuplayout(Drawcmd*, int, int, Popup*);
|
||||||
|
void popupdraw(u32int*, Drawcmd*, Popup*);
|
||||||
void imthread(void*);
|
void imthread(void*);
|
||||||
|
void impre(Im*, Str*);
|
||||||
|
void transstr(Lang*, Str*, Str*);
|
||||||
Emit transmap(Im*, Rune);
|
Emit transmap(Im*, Rune);
|
||||||
Emit transko(Im*, Rune);
|
Emit transko(Im*, Rune);
|
||||||
Emit transvi(Im*, Rune);
|
Emit transvi(Im*, Rune);
|
||||||
@@ -40,9 +40,13 @@ void backvi(Im*);
|
|||||||
void srvinit(void);
|
void srvinit(void);
|
||||||
void srvthread(void*);
|
void srvthread(void*);
|
||||||
void ibusthread(void*);
|
void ibusthread(void*);
|
||||||
|
void ximthread(void*);
|
||||||
|
int keymeaningful(u32int);
|
||||||
|
|
||||||
void* emalloc(ulong);
|
void* emalloc(ulong);
|
||||||
void* erealloc(void*, ulong);
|
void* erealloc(void*, ulong);
|
||||||
|
|
||||||
int fontinit(char**, int);
|
int textinit(void);
|
||||||
void putfont(u32int*, int, int, int, int, Rune);
|
void textclose(void);
|
||||||
|
int textwidth(Str*);
|
||||||
|
void textdraw(u32int*, int, int, int, int, int, u32int, Str*);
|
||||||
|
|||||||
314
font.c
314
font.c
@@ -1,168 +1,168 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include <ft2build.h>
|
#include <fontconfig/fontconfig.h>
|
||||||
#include FT_FREETYPE_H
|
#include <pango/pangocairo.h>
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
typedef struct Font Font;
|
static PangoFontMap *fontmap;
|
||||||
struct Font
|
static PangoContext *context;
|
||||||
{
|
static PangoLayout *layout;
|
||||||
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 void
|
static void
|
||||||
clearfonts(void)
|
resetlayout(void)
|
||||||
{
|
{
|
||||||
int i;
|
pango_layout_set_width(layout, -1);
|
||||||
|
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
|
textclose(void)
|
||||||
{
|
{
|
||||||
FT_UInt g;
|
if(layout != nil)
|
||||||
int f;
|
g_object_unref(layout);
|
||||||
|
if(context != nil)
|
||||||
if(buf == nil || w <= 0 || h <= 0)
|
g_object_unref(context);
|
||||||
return;
|
if(fontmap != nil)
|
||||||
for(f = 0; f < nfonts; f++){
|
g_object_unref(fontmap);
|
||||||
g = FT_Get_Char_Index(fonts[f].face, r);
|
layout = nil;
|
||||||
if(g == 0 || FT_Load_Glyph(fonts[f].face, g, FT_LOAD_DEFAULT) != 0 ||
|
context = nil;
|
||||||
FT_Render_Glyph(fonts[f].face->glyph, FT_RENDER_MODE_NORMAL) != 0)
|
fontmap = nil;
|
||||||
continue;
|
}
|
||||||
if(drawglyph(&fonts[f], buf, w, h, px, py))
|
|
||||||
return;
|
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
|
PROG = im-strans.so
|
||||||
CFLAGS = -Wall -O2 -I.. $(shell pkg-config --cflags gtk+-3.0)
|
PKG_CONFIG ?= pkg-config
|
||||||
LDFLAGS = $(shell pkg-config --libs gtk+-3.0)
|
CFLAGS ?= -O2
|
||||||
LIBDIR ?= /usr/lib
|
GTK_CFLAGS = $(shell $(PKG_CONFIG) --cflags gtk+-3.0)
|
||||||
GTK_MODULE_DIR = $(LIBDIR)/gtk-3.0/3.0.0/immodules
|
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)
|
all: $(PROG)
|
||||||
|
|
||||||
$(PROG): main.c ../ipc.c ../ipc.h
|
$(PROG): main.c ../ipc.c ../ipc.h
|
||||||
$(CC) -shared -fPIC $(CFLAGS) -o $@ main.c ../ipc.c $(LDFLAGS)
|
$(CC) $(CPPFLAGS) -I.. $(GTK_CFLAGS) -Wall $(CFLAGS) $(LDFLAGS) \
|
||||||
|
-shared -fPIC -o $@ main.c ../ipc.c $(GTK_LIBS) $(LDLIBS)
|
||||||
|
|
||||||
|
CHECKDIR = test -n "$$module_dir" || { \
|
||||||
|
echo "cannot find GTK 3 module directory; set GTK_MODULE_DIR" >&2; \
|
||||||
|
exit 1; }
|
||||||
|
REFRESH = test -n "$(DESTDIR)" || { \
|
||||||
|
unset GTK_PATH GTK_IM_MODULE_FILE; $(GTK_QUERY_IMMODULES) --update-cache; }
|
||||||
|
|
||||||
install: $(PROG)
|
install: $(PROG)
|
||||||
test -n "$(LIBDIR)"
|
module_dir="$(GTK_MODULE_DIR)"; $(CHECKDIR); \
|
||||||
mkdir -p "$(DESTDIR)$(GTK_MODULE_DIR)"
|
mkdir -p "$(DESTDIR)$$module_dir" && \
|
||||||
cp $(PROG) "$(DESTDIR)$(GTK_MODULE_DIR)/"
|
cp "$(PROG)" "$(DESTDIR)$$module_dir/" && $(REFRESH)
|
||||||
if test -z "$(DESTDIR)"; then gtk-query-immodules-3.0 --update-cache; fi
|
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
test -n "$(LIBDIR)"
|
module_dir="$(GTK_MODULE_DIR)"; $(CHECKDIR); \
|
||||||
rm -f "$(DESTDIR)$(GTK_MODULE_DIR)/$(PROG)"
|
rm -f "$(DESTDIR)$$module_dir/$(PROG)" && $(REFRESH)
|
||||||
if test -z "$(DESTDIR)"; then gtk-query-immodules-3.0 --update-cache; fi
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(PROG)
|
rm -f $(PROG)
|
||||||
|
|||||||
455
gtk/main.c
455
gtk/main.c
@@ -1,132 +1,246 @@
|
|||||||
#include <stdio.h>
|
#include <errno.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
#ifdef GDK_WINDOWING_X11
|
||||||
|
#include <gdk/gdkx.h>
|
||||||
|
#endif
|
||||||
#include "ipc.h"
|
#include "ipc.h"
|
||||||
|
|
||||||
typedef struct Im Im;
|
typedef struct Im Im;
|
||||||
struct Im
|
struct Im
|
||||||
{
|
{
|
||||||
GtkIMContext parent;
|
GtkIMContextSimple parent;
|
||||||
int fd;
|
int fd;
|
||||||
|
int usepreedit;
|
||||||
|
int ext;
|
||||||
|
int private;
|
||||||
|
int simpleactive;
|
||||||
|
int simpledone;
|
||||||
char pre[Ipcfieldmax+1];
|
char pre[Ipcfieldmax+1];
|
||||||
int prelen;
|
int prelen;
|
||||||
|
GdkWindow *win;
|
||||||
|
GdkRectangle cursor;
|
||||||
|
int cursorvalid;
|
||||||
|
int caretsent;
|
||||||
|
unsigned char sent[Ipccaretsz];
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct ImClass ImClass;
|
typedef struct ImClass ImClass;
|
||||||
struct ImClass
|
struct ImClass
|
||||||
{
|
{
|
||||||
GtkIMContextClass parent;
|
GtkIMContextSimpleClass parent;
|
||||||
};
|
};
|
||||||
|
|
||||||
static GType imtype;
|
static GType imtype;
|
||||||
|
static GObjectClass *parentobject;
|
||||||
|
static GtkIMContextClass *parentim;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
srvconnect(Im *im)
|
setpreedit(Im *im, const char *s, int n)
|
||||||
|
{
|
||||||
|
int was;
|
||||||
|
|
||||||
|
if(n < 0 || n > Ipcfieldmax)
|
||||||
|
n = 0;
|
||||||
|
if(n == 0 && im->prelen == 0)
|
||||||
|
return;
|
||||||
|
was = im->prelen;
|
||||||
|
if(n > 0)
|
||||||
|
memcpy(im->pre, s, n);
|
||||||
|
im->pre[n] = '\0';
|
||||||
|
im->prelen = n;
|
||||||
|
if(was == 0 && n > 0)
|
||||||
|
g_signal_emit_by_name(im, "preedit-start");
|
||||||
|
g_signal_emit_by_name(im, "preedit-changed");
|
||||||
|
if(was > 0 && n == 0)
|
||||||
|
g_signal_emit_by_name(im, "preedit-end");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
srvdrop(Im *im, int notify)
|
||||||
{
|
{
|
||||||
if(im->fd >= 0)
|
if(im->fd >= 0)
|
||||||
return;
|
close(im->fd);
|
||||||
im->fd = ipcconnect();
|
im->fd = -1;
|
||||||
|
im->ext = 0;
|
||||||
|
im->caretsent = 0;
|
||||||
|
if(notify)
|
||||||
|
setpreedit(im, "", 0);
|
||||||
|
else{
|
||||||
|
im->pre[0] = '\0';
|
||||||
|
im->prelen = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
srvclose(Im *im)
|
srvclose(Im *im)
|
||||||
{
|
{
|
||||||
int was;
|
srvdrop(im, 1);
|
||||||
|
}
|
||||||
|
|
||||||
if(im->fd >= 0)
|
/* Both buffers are char[Ipcfieldmax+1]. */
|
||||||
close(im->fd);
|
static int
|
||||||
im->fd = -1;
|
readresp(Im *im, int want, char *commit, char *pre, Ipcresp *resp)
|
||||||
was = im->prelen;
|
{
|
||||||
im->pre[0] = '\0';
|
if(ipcreadresp(im->fd, want, commit, pre, resp) < 0)
|
||||||
im->prelen = 0;
|
return -1;
|
||||||
if(was > 0){
|
if(!g_utf8_validate(commit, resp->commitlen, NULL) ||
|
||||||
g_signal_emit_by_name(im, "preedit-changed");
|
(want && !g_utf8_validate(pre, resp->preeditlen, NULL))){
|
||||||
g_signal_emit_by_name(im, "preedit-end");
|
errno = EPROTO;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dropwindow(Im *im)
|
||||||
|
{
|
||||||
|
if(im->win != NULL)
|
||||||
|
g_object_unref(im->win);
|
||||||
|
im->win = NULL;
|
||||||
|
im->cursorvalid = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Root-relative caret in device pixels; returns whether it is known. */
|
||||||
|
static int
|
||||||
|
caretget(Im *im, int32_t *x, int32_t *y, int32_t *h)
|
||||||
|
{
|
||||||
|
gint rx, ry, scale;
|
||||||
|
int64_t sx, sy, sh;
|
||||||
|
|
||||||
|
*x = *y = *h = 0;
|
||||||
|
if(im->win != NULL && gdk_window_is_destroyed(im->win))
|
||||||
|
dropwindow(im);
|
||||||
|
if(!im->cursorvalid || im->win == NULL || im->cursor.height < 0)
|
||||||
|
return 0;
|
||||||
|
#ifdef GDK_WINDOWING_X11
|
||||||
|
if(!GDK_IS_X11_WINDOW(im->win))
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
scale = gdk_window_get_scale_factor(im->win);
|
||||||
|
if(scale <= 0)
|
||||||
|
return 0;
|
||||||
|
/* GDK translates the client-relative point before device scaling. */
|
||||||
|
gdk_window_get_root_coords(im->win, 0, 0, &rx, &ry);
|
||||||
|
sx = ((int64_t)rx + im->cursor.x) * scale;
|
||||||
|
sy = ((int64_t)ry + im->cursor.y) * scale;
|
||||||
|
sh = (int64_t)im->cursor.height * scale;
|
||||||
|
if(sx < INT32_MIN || sx > INT32_MAX ||
|
||||||
|
sy < INT32_MIN || sy > INT32_MAX || sh > INT32_MAX)
|
||||||
|
return 0;
|
||||||
|
*x = sx;
|
||||||
|
*y = sy;
|
||||||
|
*h = sh;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
readresp(Im *im, char *buf, int bufsz)
|
sendcaret(Im *im)
|
||||||
{
|
{
|
||||||
Ipcresp resp;
|
unsigned char buf[Ipccaretsz];
|
||||||
int was;
|
int32_t x, y, h;
|
||||||
|
int valid;
|
||||||
|
|
||||||
was = im->prelen;
|
if(im->fd < 0 || !im->ext)
|
||||||
if(ipcreadresp(im->fd, 1, buf, bufsz, im->pre,
|
return 0;
|
||||||
sizeof(im->pre), &resp) < 0)
|
valid = caretget(im, &x, &y, &h);
|
||||||
|
ipcpackcaret(buf, valid, x, y, h);
|
||||||
|
if(im->caretsent && memcmp(buf, im->sent, sizeof buf) == 0)
|
||||||
|
return 0;
|
||||||
|
if(ipcsend(im->fd, buf, sizeof buf) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
im->prelen = resp.npreedit;
|
memcpy(im->sent, buf, sizeof buf);
|
||||||
if(was == 0 && im->prelen > 0)
|
im->caretsent = 1;
|
||||||
g_signal_emit_by_name(im, "preedit-start");
|
return 0;
|
||||||
if(was != 0 || im->prelen != 0)
|
|
||||||
g_signal_emit_by_name(im, "preedit-changed");
|
|
||||||
if(was > 0 && im->prelen == 0)
|
|
||||||
g_signal_emit_by_name(im, "preedit-end");
|
|
||||||
return resp.eaten;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t
|
static int
|
||||||
kget(uint32_t gdk)
|
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(im->fd >= 0)
|
||||||
if((gdk & 0xff000000) == 0x01000000 && u != 0)
|
return 0;
|
||||||
return u;
|
im->fd = ipcconnect();
|
||||||
if(gdk >= 0xff00 && gdk <= 0xffff)
|
if(im->fd < 0)
|
||||||
return Kspec + (gdk - 0xff00);
|
return -1;
|
||||||
return u;
|
/* 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
|
static void
|
||||||
mget(uint32_t state)
|
simplecommit(GtkIMContext *ctx, const char *s, Im *im)
|
||||||
{
|
{
|
||||||
uint32_t m;
|
(void)ctx;
|
||||||
|
(void)s;
|
||||||
m = 0;
|
im->simpledone = 1;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
simpleend(GtkIMContext *ctx, Im *im)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
im->simpledone = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GtkIMContextSimple composes dead keys and Compose sequences; it keeps
|
||||||
|
* the key stream until it commits, ends its preedit, or rejects a press.
|
||||||
|
*/
|
||||||
|
static gboolean
|
||||||
|
simplefilter(GtkIMContext *ctx, GdkEventKey *ev, int release)
|
||||||
|
{
|
||||||
|
Im *im;
|
||||||
|
gboolean r;
|
||||||
|
|
||||||
|
im = (Im*)ctx;
|
||||||
|
im->simpleactive = 1;
|
||||||
|
im->simpledone = 0;
|
||||||
|
r = parentim->filter_keypress(ctx, ev);
|
||||||
|
if((!r && !release) || im->simpledone)
|
||||||
|
im->simpleactive = 0;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The daemon hands the pending text back; it becomes committed text. */
|
||||||
static void
|
static void
|
||||||
sendreset(Im *im)
|
sendreset(Im *im)
|
||||||
{
|
{
|
||||||
unsigned char buf[Ipcreqsz];
|
unsigned char buf[Ipcreqsz];
|
||||||
char resp[Ipcfieldmax+1];
|
char commit[Ipcfieldmax+1];
|
||||||
|
char pre[Ipcfieldmax+1];
|
||||||
|
Ipcresp resp;
|
||||||
|
|
||||||
if(im->fd < 0){
|
if(im->fd < 0){
|
||||||
|
setpreedit(im, "", 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ipcpackreset(buf, im->usepreedit);
|
||||||
|
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||||
|
readresp(im, im->usepreedit, commit, pre, &resp) < 0){
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ipcpackreset(buf, 1);
|
setpreedit(im, "", 0);
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
if(commit[0] != '\0')
|
||||||
readresp(im, resp, sizeof(resp)) < 0)
|
g_signal_emit_by_name(im, "commit", commit);
|
||||||
srvclose(im);
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
@@ -134,41 +248,45 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
|||||||
{
|
{
|
||||||
Im *im;
|
Im *im;
|
||||||
unsigned char buf[Ipcreqsz];
|
unsigned char buf[Ipcreqsz];
|
||||||
char resp[Ipcfieldmax+1];
|
char commit[Ipcfieldmax+1];
|
||||||
|
char pre[Ipcfieldmax+1];
|
||||||
uint32_t key, mod;
|
uint32_t key, mod;
|
||||||
int r;
|
Ipcresp resp;
|
||||||
GtkInputPurpose purpose;
|
|
||||||
|
|
||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
if(ev->type != GDK_KEY_PRESS)
|
if(ev->type != GDK_KEY_PRESS){
|
||||||
|
if(im->simpleactive)
|
||||||
|
return simplefilter(ctx, ev, 1);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
key = kget(ev->keyval);
|
|
||||||
if(key == 0)
|
|
||||||
return FALSE;
|
|
||||||
mod = mget(ev->state);
|
|
||||||
g_object_get(ctx, "input-purpose", &purpose, NULL);
|
|
||||||
if(purpose == GTK_INPUT_PURPOSE_PASSWORD || purpose == GTK_INPUT_PURPOSE_PIN)
|
|
||||||
return plaincommit(ctx, key, mod);
|
|
||||||
srvconnect(im);
|
|
||||||
if(im->fd < 0){
|
|
||||||
srvclose(im);
|
|
||||||
return plaincommit(ctx, key, mod);
|
|
||||||
}
|
}
|
||||||
ipcpackreq(buf, 1, mod, key);
|
if(im->simpleactive)
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0){
|
return simplefilter(ctx, ev, 0);
|
||||||
|
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);
|
srvclose(im);
|
||||||
return plaincommit(ctx, key, mod);
|
return simplefilter(ctx, ev, 0);
|
||||||
}
|
}
|
||||||
r = readresp(im, resp, sizeof(resp));
|
ipcpackreq(buf, im->usepreedit, mod, key);
|
||||||
if(r < 0){
|
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||||
|
readresp(im, im->usepreedit, commit, pre, &resp) < 0){
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
return plaincommit(ctx, key, mod);
|
return simplefilter(ctx, ev, 0);
|
||||||
}
|
}
|
||||||
if(resp[0] != '\0')
|
if(im->usepreedit && commit[0] != '\0' && im->prelen > 0)
|
||||||
g_signal_emit_by_name(ctx, "commit", resp);
|
setpreedit(im, "", 0);
|
||||||
if(r != 0)
|
if(commit[0] != '\0')
|
||||||
|
g_signal_emit_by_name(ctx, "commit", commit);
|
||||||
|
if(im->usepreedit)
|
||||||
|
setpreedit(im, pre, resp.preeditlen);
|
||||||
|
if(resp.eaten)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
return plaincommit(ctx, key, mod);
|
return simplefilter(ctx, ev, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -179,6 +297,10 @@ getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs,
|
|||||||
PangoAttribute *u;
|
PangoAttribute *u;
|
||||||
|
|
||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
|
if(im->simpleactive){
|
||||||
|
parentim->get_preedit_string(ctx, str, attrs, cursor_pos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(str)
|
if(str)
|
||||||
*str = g_strdup(im->pre);
|
*str = g_strdup(im->pre);
|
||||||
if(attrs){
|
if(attrs){
|
||||||
@@ -197,6 +319,9 @@ getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs,
|
|||||||
static void
|
static void
|
||||||
reset(GtkIMContext *ctx)
|
reset(GtkIMContext *ctx)
|
||||||
{
|
{
|
||||||
|
if(parentim->reset != NULL)
|
||||||
|
parentim->reset(ctx);
|
||||||
|
((Im*)ctx)->simpleactive = 0;
|
||||||
sendreset((Im*)ctx);
|
sendreset((Im*)ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,28 +331,136 @@ focusout(GtkIMContext *ctx)
|
|||||||
Im *im;
|
Im *im;
|
||||||
|
|
||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
|
if(parentim->focus_out != NULL)
|
||||||
|
parentim->focus_out(ctx);
|
||||||
|
im->simpleactive = 0;
|
||||||
|
/* Closing the connection releases the engine. */
|
||||||
sendreset(im);
|
sendreset(im);
|
||||||
/* Closing the context connection releases engine ownership. */
|
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
finalize(GObject *obj)
|
setusepreedit(GtkIMContext *ctx, gboolean use)
|
||||||
|
{
|
||||||
|
Im *im;
|
||||||
|
unsigned char buf[Ipcreqsz];
|
||||||
|
char commit[Ipcfieldmax+1];
|
||||||
|
char pre[Ipcfieldmax+1];
|
||||||
|
Ipcresp resp;
|
||||||
|
|
||||||
|
im = (Im*)ctx;
|
||||||
|
use = use != FALSE;
|
||||||
|
if(parentim->set_use_preedit != NULL)
|
||||||
|
parentim->set_use_preedit(ctx, use);
|
||||||
|
if(im->usepreedit == use)
|
||||||
|
return;
|
||||||
|
im->usepreedit = use;
|
||||||
|
if(!use)
|
||||||
|
setpreedit(im, "", 0);
|
||||||
|
if(im->fd < 0 || !im->ext)
|
||||||
|
return;
|
||||||
|
ipcpackcap(buf, use);
|
||||||
|
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||||
|
readresp(im, use, commit, pre, &resp) < 0 ||
|
||||||
|
!resp.eaten){
|
||||||
|
srvclose(im);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(use)
|
||||||
|
setpreedit(im, pre, resp.preeditlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
setclientwindow(GtkIMContext *ctx, GdkWindow *win)
|
||||||
|
{
|
||||||
|
Im *im;
|
||||||
|
|
||||||
|
im = (Im*)ctx;
|
||||||
|
if(win != NULL && gdk_window_is_destroyed(win))
|
||||||
|
win = NULL;
|
||||||
|
if(parentim->set_client_window != NULL)
|
||||||
|
parentim->set_client_window(ctx, win);
|
||||||
|
if(im->win == win)
|
||||||
|
return;
|
||||||
|
if(win != NULL)
|
||||||
|
g_object_ref(win);
|
||||||
|
dropwindow(im);
|
||||||
|
im->win = win;
|
||||||
|
if(sendcaret(im) < 0)
|
||||||
|
srvclose(im);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
setcursorlocation(GtkIMContext *ctx, GdkRectangle *area)
|
||||||
|
{
|
||||||
|
Im *im;
|
||||||
|
|
||||||
|
im = (Im*)ctx;
|
||||||
|
if(parentim->set_cursor_location != NULL)
|
||||||
|
parentim->set_cursor_location(ctx, area);
|
||||||
|
if(im->win != NULL && gdk_window_is_destroyed(im->win))
|
||||||
|
dropwindow(im);
|
||||||
|
if(area == NULL || im->win == NULL)
|
||||||
|
im->cursorvalid = 0;
|
||||||
|
else{
|
||||||
|
im->cursor = *area;
|
||||||
|
im->cursorvalid = 1;
|
||||||
|
}
|
||||||
|
if(sendcaret(im) < 0)
|
||||||
|
srvclose(im);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
isprivate(GtkInputPurpose purpose)
|
||||||
|
{
|
||||||
|
return purpose == GTK_INPUT_PURPOSE_PASSWORD ||
|
||||||
|
purpose == GTK_INPUT_PURPOSE_PIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
purposechanged(GObject *obj, GParamSpec *pspec, gpointer data)
|
||||||
|
{
|
||||||
|
Im *im;
|
||||||
|
GtkInputPurpose purpose;
|
||||||
|
int private;
|
||||||
|
|
||||||
|
(void)pspec;
|
||||||
|
(void)data;
|
||||||
|
im = (Im*)obj;
|
||||||
|
g_object_get(obj, "input-purpose", &purpose, NULL);
|
||||||
|
private = isprivate(purpose);
|
||||||
|
if(private == im->private)
|
||||||
|
return;
|
||||||
|
im->private = private;
|
||||||
|
if(private)
|
||||||
|
reset(GTK_IM_CONTEXT(im));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dispose(GObject *obj)
|
||||||
{
|
{
|
||||||
Im *im;
|
Im *im;
|
||||||
|
|
||||||
im = (Im*)obj;
|
im = (Im*)obj;
|
||||||
if(im->fd >= 0)
|
srvdrop(im, 0);
|
||||||
close(im->fd);
|
dropwindow(im);
|
||||||
G_OBJECT_CLASS(g_type_class_peek_parent(G_OBJECT_GET_CLASS(obj)))->finalize(obj);
|
parentobject->dispose(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
init(Im *im)
|
init(Im *im)
|
||||||
{
|
{
|
||||||
|
GtkInputPurpose purpose;
|
||||||
|
|
||||||
im->fd = -1;
|
im->fd = -1;
|
||||||
|
im->usepreedit = 1;
|
||||||
im->pre[0] = '\0';
|
im->pre[0] = '\0';
|
||||||
im->prelen = 0;
|
g_object_get(im, "input-purpose", &purpose, NULL);
|
||||||
|
im->private = isprivate(purpose);
|
||||||
|
g_signal_connect(im, "notify::input-purpose",
|
||||||
|
G_CALLBACK(purposechanged), NULL);
|
||||||
|
g_signal_connect(im, "commit", G_CALLBACK(simplecommit), im);
|
||||||
|
g_signal_connect(im, "preedit-end", G_CALLBACK(simpleend), im);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -238,11 +471,16 @@ classinit(ImClass *klass)
|
|||||||
|
|
||||||
ic = GTK_IM_CONTEXT_CLASS(klass);
|
ic = GTK_IM_CONTEXT_CLASS(klass);
|
||||||
oc = G_OBJECT_CLASS(klass);
|
oc = G_OBJECT_CLASS(klass);
|
||||||
|
parentim = g_type_class_peek_parent(klass);
|
||||||
|
parentobject = G_OBJECT_CLASS(parentim);
|
||||||
ic->filter_keypress = kpress;
|
ic->filter_keypress = kpress;
|
||||||
ic->get_preedit_string = getpreedit;
|
ic->get_preedit_string = getpreedit;
|
||||||
ic->reset = reset;
|
ic->reset = reset;
|
||||||
ic->focus_out = focusout;
|
ic->focus_out = focusout;
|
||||||
oc->finalize = finalize;
|
ic->set_use_preedit = setusepreedit;
|
||||||
|
ic->set_client_window = setclientwindow;
|
||||||
|
ic->set_cursor_location = setcursorlocation;
|
||||||
|
oc->dispose = dispose;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const GtkIMContextInfo info = {
|
static const GtkIMContextInfo info = {
|
||||||
@@ -267,7 +505,8 @@ im_module_init(GTypeModule *mod)
|
|||||||
0,
|
0,
|
||||||
(GInstanceInitFunc)init,
|
(GInstanceInitFunc)init,
|
||||||
};
|
};
|
||||||
imtype = g_type_module_register_type(mod, GTK_TYPE_IM_CONTEXT, "strans-gtk", &ti, 0);
|
imtype = g_type_module_register_type(mod, GTK_TYPE_IM_CONTEXT_SIMPLE,
|
||||||
|
"strans-gtk", &ti, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
G_MODULE_EXPORT void
|
G_MODULE_EXPORT void
|
||||||
|
|||||||
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 <errno.h>
|
||||||
|
#include <poll.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include "ipc.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
|
static void
|
||||||
putlen(unsigned char p[Ipclensz], size_t n)
|
putlen(unsigned char p[Ipclensz], size_t n)
|
||||||
{
|
{
|
||||||
@@ -20,14 +95,33 @@ getlen(const unsigned char p[Ipclensz])
|
|||||||
return p[0] | (p[1] << 8);
|
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
|
int
|
||||||
ipcpath(char *dst, size_t cap)
|
ipcpath(char *dst, size_t cap)
|
||||||
{
|
{
|
||||||
const char *dir, *sep;
|
const char *dir, *sep;
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
if(dst == NULL || cap == 0)
|
|
||||||
return -1;
|
|
||||||
dir = getenv("XDG_RUNTIME_DIR");
|
dir = getenv("XDG_RUNTIME_DIR");
|
||||||
if(dir != NULL && dir[0] == '/'){
|
if(dir != NULL && dir[0] == '/'){
|
||||||
sep = dir[strlen(dir)-1] == '/' ? "" : "/";
|
sep = dir[strlen(dir)-1] == '/' ? "" : "/";
|
||||||
@@ -40,6 +134,11 @@ ipcpath(char *dst, size_t cap)
|
|||||||
return 0;
|
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
|
int
|
||||||
ipcconnect(void)
|
ipcconnect(void)
|
||||||
{
|
{
|
||||||
@@ -52,15 +151,49 @@ ipcconnect(void)
|
|||||||
errno = ENAMETOOLONG;
|
errno = ENAMETOOLONG;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
|
||||||
if(fd < 0)
|
if(fd < 0)
|
||||||
return -1;
|
return -1;
|
||||||
if(connect(fd, (struct sockaddr*)&addr, sizeof addr) == 0)
|
if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0){
|
||||||
return fd;
|
e = errno;
|
||||||
e = errno;
|
close(fd);
|
||||||
close(fd);
|
errno = e;
|
||||||
errno = e;
|
return -1;
|
||||||
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
|
void
|
||||||
@@ -82,6 +215,32 @@ ipcpackreset(unsigned char req[Ipcreqsz], int want)
|
|||||||
req[0] = Ipcreqreset | (want ? Ipcreqwant : 0);
|
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
|
void
|
||||||
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
||||||
uint32_t *key)
|
uint32_t *key)
|
||||||
@@ -94,6 +253,40 @@ ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
|||||||
((uint32_t)req[5] << 24);
|
((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
|
int
|
||||||
ipcreqreset(const unsigned char req[Ipcreqsz])
|
ipcreqreset(const unsigned char req[Ipcreqsz])
|
||||||
{
|
{
|
||||||
@@ -109,21 +302,15 @@ ipcpackresp(unsigned char *dst, size_t cap, int eaten,
|
|||||||
|
|
||||||
if(ncommit > Ipcfieldmax || npreedit > Ipcfieldmax)
|
if(ncommit > Ipcfieldmax || npreedit > Ipcfieldmax)
|
||||||
return -1;
|
return -1;
|
||||||
if((ncommit > 0 && commit == NULL) ||
|
|
||||||
(want && npreedit > 0 && preedit == NULL))
|
|
||||||
return -1;
|
|
||||||
n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0);
|
n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0);
|
||||||
if(dst == NULL || cap < n)
|
if(cap < n)
|
||||||
return -1;
|
return -1;
|
||||||
dst[0] = eaten != 0;
|
dst[0] = eaten != 0;
|
||||||
putlen(dst + 1, ncommit);
|
putlen(dst + 1, ncommit);
|
||||||
if(ncommit > 0)
|
memcpy(dst + Ipcresphdrsz, commit, ncommit);
|
||||||
memcpy(dst + Ipcresphdrsz, commit, ncommit);
|
|
||||||
if(want){
|
if(want){
|
||||||
putlen(dst + Ipcresphdrsz + ncommit, npreedit);
|
putlen(dst + Ipcresphdrsz + ncommit, npreedit);
|
||||||
if(npreedit > 0)
|
memcpy(dst + Ipcresphdrsz + ncommit + Ipclensz, preedit, npreedit);
|
||||||
memcpy(dst + Ipcresphdrsz + ncommit + Ipclensz,
|
|
||||||
preedit, npreedit);
|
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
@@ -152,73 +339,78 @@ ipcsend(int fd, const void *buf, size_t n)
|
|||||||
{
|
{
|
||||||
const unsigned char *p;
|
const unsigned char *p;
|
||||||
ssize_t r;
|
ssize_t r;
|
||||||
|
int64_t until;
|
||||||
|
|
||||||
p = buf;
|
p = buf;
|
||||||
|
until = deadline();
|
||||||
while(n > 0){
|
while(n > 0){
|
||||||
r = send(fd, p, n, MSG_NOSIGNAL);
|
r = send(fd, p, n, MSG_NOSIGNAL|MSG_DONTWAIT);
|
||||||
if(r < 0 && errno == EINTR)
|
if(r < 0 && errno == EINTR)
|
||||||
continue;
|
continue;
|
||||||
if(r <= 0)
|
if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){
|
||||||
|
if(waitfd(fd, POLLOUT, until) < 0)
|
||||||
|
return -1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(r < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
if(r == 0){
|
||||||
|
errno = EPIPE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
p += r;
|
p += r;
|
||||||
n -= r;
|
n -= r;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Reads a length-prefixed field into dst[Ipcfieldmax+1]. */
|
||||||
static int
|
static int
|
||||||
readfield(int fd, size_t n, char *dst, size_t cap)
|
readfield(int fd, char *dst, int64_t until)
|
||||||
{
|
{
|
||||||
unsigned char discard[128];
|
unsigned char len[Ipclensz];
|
||||||
size_t keep, part;
|
size_t n;
|
||||||
|
|
||||||
if(cap > 0 && dst == NULL)
|
if(readwait(fd, len, sizeof len, until) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
keep = 0;
|
n = getlen(len);
|
||||||
if(cap > 0)
|
if(n > Ipcfieldmax){
|
||||||
keep = n >= cap ? cap - 1 : n;
|
errno = EPROTO;
|
||||||
if(keep > 0 && ipcreadn(fd, dst, keep) < 0)
|
|
||||||
return -1;
|
return -1;
|
||||||
if(cap > 0)
|
|
||||||
dst[keep] = '\0';
|
|
||||||
n -= keep;
|
|
||||||
while(n > 0){
|
|
||||||
part = n < sizeof discard ? n : sizeof discard;
|
|
||||||
if(ipcreadn(fd, discard, part) < 0)
|
|
||||||
return -1;
|
|
||||||
n -= part;
|
|
||||||
}
|
}
|
||||||
return 0;
|
if(readwait(fd, dst, n, until) < 0)
|
||||||
|
return -1;
|
||||||
|
dst[n] = '\0';
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
ipcreadresp(int fd, int want, char *commit, size_t ccap,
|
ipcreadresp(int fd, int want, char *commit, char *preedit, Ipcresp *resp)
|
||||||
char *preedit, size_t pcap, Ipcresp *resp)
|
|
||||||
{
|
{
|
||||||
unsigned char hdr[Ipcresphdrsz], npreedit[Ipclensz];
|
unsigned char eaten;
|
||||||
|
int64_t until;
|
||||||
|
int n;
|
||||||
|
|
||||||
if(resp == NULL || (ccap > 0 && commit == NULL) ||
|
commit[0] = '\0';
|
||||||
(pcap > 0 && preedit == NULL))
|
preedit[0] = '\0';
|
||||||
return -1;
|
|
||||||
if(ccap > 0)
|
|
||||||
commit[0] = '\0';
|
|
||||||
if(pcap > 0)
|
|
||||||
preedit[0] = '\0';
|
|
||||||
memset(resp, 0, sizeof *resp);
|
memset(resp, 0, sizeof *resp);
|
||||||
if(ipcreadn(fd, hdr, sizeof hdr) < 0)
|
until = deadline();
|
||||||
|
if(readwait(fd, &eaten, 1, until) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
resp->eaten = hdr[0] != 0;
|
if(eaten > 1){
|
||||||
resp->ncommit = getlen(hdr + 1);
|
errno = EPROTO;
|
||||||
if(resp->ncommit > Ipcfieldmax)
|
|
||||||
return -1;
|
return -1;
|
||||||
if(readfield(fd, resp->ncommit, commit, ccap) < 0)
|
}
|
||||||
|
resp->eaten = eaten;
|
||||||
|
n = readfield(fd, commit, until);
|
||||||
|
if(n < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
resp->commitlen = n;
|
||||||
if(!want)
|
if(!want)
|
||||||
return 0;
|
return 0;
|
||||||
if(ipcreadn(fd, npreedit, sizeof npreedit) < 0)
|
n = readfield(fd, preedit, until);
|
||||||
|
if(n < 0)
|
||||||
return -1;
|
return -1;
|
||||||
resp->npreedit = getlen(npreedit);
|
resp->preeditlen = n;
|
||||||
if(resp->npreedit > Ipcfieldmax)
|
return 0;
|
||||||
return -1;
|
|
||||||
return readfield(fd, resp->npreedit, preedit, pcap);
|
|
||||||
}
|
}
|
||||||
|
|||||||
45
ipc.h
45
ipc.h
@@ -2,21 +2,33 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Request: [flags, modifiers, key byte 0, ..., key byte 3].
|
* Key request: [flags, modifiers, key byte 0, ..., key byte 3].
|
||||||
* Flags request preedit and distinguish lifecycle reset from physical Escape.
|
* 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
|
* The server identifies the connection as the engine owner; that identity is
|
||||||
* not sent on the wire.
|
* not sent on the wire.
|
||||||
* Only Mmask modifier bits are retained.
|
* Only Mmask modifier bits are retained.
|
||||||
* Response: [eaten, commit-length-low, commit-length-high, commit...],
|
* Response: [eaten, commit-length-low, commit-length-high, commit...],
|
||||||
* followed, when requested, by
|
* followed, when requested, by
|
||||||
* [preedit-length-low, preedit-length-high, preedit...].
|
* [preedit-length-low, preedit-length-high, preedit...].
|
||||||
* Lengths are little-endian byte counts; fields are at most Ipcfieldmax bytes.
|
* 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
|
enum
|
||||||
{
|
{
|
||||||
Ipcreqwant = 1<<0,
|
Ipcreqwant = 1<<0,
|
||||||
Ipcreqreset = 1<<1,
|
Ipcreqreset = 1<<1,
|
||||||
|
Ipcext = 1<<7,
|
||||||
|
Ipcversion = 1,
|
||||||
|
Ipcopcap = 0,
|
||||||
|
Ipcopcaret = 1,
|
||||||
|
|
||||||
Kspec = 0x110000,
|
Kspec = 0x110000,
|
||||||
Kback = Kspec|0x08,
|
Kback = Kspec|0x08,
|
||||||
@@ -25,37 +37,58 @@ enum
|
|||||||
Kesc = Kspec|0x1b,
|
Kesc = Kspec|0x1b,
|
||||||
Kup = Kspec|0x52,
|
Kup = Kspec|0x52,
|
||||||
Kdown = Kspec|0x54,
|
Kdown = Kspec|0x54,
|
||||||
|
Kpgup = Kspec|0x55,
|
||||||
|
Kpgdown = Kspec|0x56,
|
||||||
Kmodfirst = Kspec|0xe1,
|
Kmodfirst = Kspec|0xe1,
|
||||||
Kmodlast = Kspec|0xee,
|
Kmodlast = Kspec|0xee,
|
||||||
|
|
||||||
|
/* X core modifier bits; GDK and IBus also flag Super at bit 26. */
|
||||||
Mshift = 1<<0,
|
Mshift = 1<<0,
|
||||||
Mctrl = 1<<2,
|
Mctrl = 1<<2,
|
||||||
Malt = 1<<3,
|
Malt = 1<<3,
|
||||||
Msuper = 1<<6,
|
Msuper = 1<<6,
|
||||||
Mmask = Mshift|Mctrl|Malt|Msuper,
|
Mmask = Mshift|Mctrl|Malt|Msuper,
|
||||||
|
Msupervirt = 1<<26,
|
||||||
|
|
||||||
Ipcreqsz = 6,
|
Ipcreqsz = 6,
|
||||||
|
Ipccaretsz = 16,
|
||||||
Ipclensz = 2,
|
Ipclensz = 2,
|
||||||
Ipcresphdrsz = 1 + Ipclensz,
|
Ipcresphdrsz = 1 + Ipclensz,
|
||||||
Ipcfieldmax = 256,
|
Ipcfieldmax = 256,
|
||||||
Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax,
|
Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax,
|
||||||
|
Ipcwaitms = 250,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Ipcunknown = -1,
|
||||||
|
Ipckey,
|
||||||
|
Ipccap,
|
||||||
|
Ipccaret,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Ipcresp Ipcresp;
|
typedef struct Ipcresp Ipcresp;
|
||||||
struct Ipcresp
|
struct Ipcresp
|
||||||
{
|
{
|
||||||
int eaten;
|
int eaten;
|
||||||
size_t ncommit;
|
/* Bytes copied to each caller buffer, excluding its trailing NUL. */
|
||||||
size_t npreedit;
|
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 ipcpackreq(unsigned char[Ipcreqsz], int, uint32_t, uint32_t);
|
||||||
void ipcpackreset(unsigned char[Ipcreqsz], int);
|
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*);
|
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 ipcreqreset(const unsigned char[Ipcreqsz]);
|
||||||
int ipcpackresp(unsigned char*, size_t, int, const char*, size_t, const char*, size_t, int);
|
int ipcpackresp(unsigned char*, size_t, int, const char*, size_t, const char*, size_t, int);
|
||||||
int ipcreadn(int, void*, size_t);
|
int ipcreadn(int, void*, size_t);
|
||||||
int ipcsend(int, const void*, size_t);
|
int ipcsend(int, const void*, size_t);
|
||||||
int ipcreadresp(int, int, char*, size_t, char*, size_t, Ipcresp*);
|
int ipcreadresp(int, int, char*, char*, Ipcresp*);
|
||||||
int ipcpath(char*, size_t);
|
int ipcpath(char*, size_t);
|
||||||
int ipcconnect(void);
|
int ipcconnect(void);
|
||||||
|
|||||||
16
ko.c
16
ko.c
@@ -189,6 +189,10 @@ issyl(Rune r)
|
|||||||
return r >= Sbase && r < Sbase + Ncho*Njung*Njong;
|
return r >= Sbase && r < Sbase + Ncho*Njung*Njong;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The pending text is at most one syllable; a key either extends it,
|
||||||
|
* commits it and starts another, or commits it and passes through.
|
||||||
|
*/
|
||||||
Emit
|
Emit
|
||||||
transko(Im *im, Rune c)
|
transko(Im *im, Rune c)
|
||||||
{
|
{
|
||||||
@@ -199,10 +203,7 @@ transko(Im *im, Rune c)
|
|||||||
memset(&e, 0, sizeof e);
|
memset(&e, 0, sizeof e);
|
||||||
jm = keytojamo(c);
|
jm = keytojamo(c);
|
||||||
if(jm == 0){
|
if(jm == 0){
|
||||||
if(im->pre.n > 0){
|
e.s = im->pre;
|
||||||
e.s = im->pre;
|
|
||||||
sclear(&im->pre);
|
|
||||||
}
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,14 +218,12 @@ transko(Im *im, Rune c)
|
|||||||
ci = Choidx(last);
|
ci = Choidx(last);
|
||||||
ji = Jungidx(jm);
|
ji = Jungidx(jm);
|
||||||
if(ci >= 0 && ji >= 0){
|
if(ci >= 0 && ji >= 0){
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&e.next, compose(ci, ji, 0));
|
sputr(&e.next, compose(ci, ji, 0));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
if(Jungidx(last) >= 0 && ji >= 0){
|
if(Jungidx(last) >= 0 && ji >= 0){
|
||||||
comb = combine(cvow, nelem(cvow), last, jm);
|
comb = combine(cvow, nelem(cvow), last, jm);
|
||||||
if(comb){
|
if(comb){
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&e.next, comb);
|
sputr(&e.next, comb);
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -239,7 +238,6 @@ transko(Im *im, Rune c)
|
|||||||
if(joi == 0){
|
if(joi == 0){
|
||||||
ni = Jongidx(jm);
|
ni = Jongidx(jm);
|
||||||
if(ni > 0){
|
if(ni > 0){
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&e.next, compose(ci, ji, ni));
|
sputr(&e.next, compose(ci, ji, ni));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -248,7 +246,6 @@ transko(Im *im, Rune c)
|
|||||||
comb = combine(cvow, nelem(cvow), jung[ji], jm);
|
comb = combine(cvow, nelem(cvow), jung[ji], jm);
|
||||||
if(comb){
|
if(comb){
|
||||||
ni = Jungidx(comb);
|
ni = Jungidx(comb);
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&e.next, compose(ci, ni, 0));
|
sputr(&e.next, compose(ci, ni, 0));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -260,7 +257,6 @@ transko(Im *im, Rune c)
|
|||||||
if(comb){
|
if(comb){
|
||||||
ni = Jongidx(comb);
|
ni = Jongidx(comb);
|
||||||
if(ni > 0){
|
if(ni > 0){
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&e.next, compose(ci, ji, ni));
|
sputr(&e.next, compose(ci, ji, ni));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -271,14 +267,12 @@ transko(Im *im, Rune c)
|
|||||||
if(splitpair(cjong, nelem(cjong), jc, &stay, &next)){
|
if(splitpair(cjong, nelem(cjong), jc, &stay, &next)){
|
||||||
si = Jongidx(stay);
|
si = Jongidx(stay);
|
||||||
ni = Choidx(next);
|
ni = Choidx(next);
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&e.s, compose(ci, ji, si));
|
sputr(&e.s, compose(ci, ji, si));
|
||||||
sputr(&e.next, compose(ni, vi, 0));
|
sputr(&e.next, compose(ni, vi, 0));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
ni = Choidx(jc);
|
ni = Choidx(jc);
|
||||||
if(ni >= 0){
|
if(ni >= 0){
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&e.s, compose(ci, ji, 0));
|
sputr(&e.s, compose(ci, ji, 0));
|
||||||
sputr(&e.next, compose(ni, vi, 0));
|
sputr(&e.next, compose(ni, vi, 0));
|
||||||
return e;
|
return e;
|
||||||
|
|||||||
31
main.c
31
main.c
@@ -3,15 +3,11 @@
|
|||||||
|
|
||||||
Channel *drawc;
|
Channel *drawc;
|
||||||
Channel *keyc;
|
Channel *keyc;
|
||||||
Channel *dictreqc;
|
|
||||||
Channel *dictresc;
|
|
||||||
char **fontfiles;
|
|
||||||
int nfontfiles;
|
|
||||||
|
|
||||||
void
|
void
|
||||||
usage(void)
|
usage(void)
|
||||||
{
|
{
|
||||||
fprint(2, "usage: strans mapdir fontfile [fontfile ...]\n");
|
fprint(2, "usage: strans mapdir\n");
|
||||||
threadexitsall("usage");
|
threadexitsall("usage");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,25 +47,20 @@ erealloc(void *p, ulong n)
|
|||||||
void
|
void
|
||||||
threadmain(int argc, char **argv)
|
threadmain(int argc, char **argv)
|
||||||
{
|
{
|
||||||
if(argc < 3 || argc > Maxfonts + 2)
|
char *display;
|
||||||
|
|
||||||
|
if(argc != 2)
|
||||||
usage();
|
usage();
|
||||||
|
|
||||||
fontfiles = argv + 2;
|
|
||||||
nfontfiles = argc - 2;
|
|
||||||
drawc = chancreate(sizeof(Drawcmd), 4);
|
drawc = chancreate(sizeof(Drawcmd), 4);
|
||||||
keyc = chancreate(sizeof(Keyreq), 0);
|
keyc = chancreate(sizeof(Keyreq), 0);
|
||||||
dictreqc = chancreate(sizeof(Dictreq), 4);
|
langinit(argv[1]);
|
||||||
dictresc = chancreate(sizeof(Dictres), 0);
|
|
||||||
mapinit(argv[1]);
|
|
||||||
dictinit(argv[1]);
|
|
||||||
srvinit();
|
srvinit();
|
||||||
if(proccreate(drawthread, nil, 16384) < 0)
|
proccreate(drawthread, nil, 16384);
|
||||||
die("can't create draw worker");
|
proccreate(srvthread, nil, 16384);
|
||||||
if(proccreate(srvthread, nil, 16384) < 0)
|
proccreate(ibusthread, nil, 32768);
|
||||||
die("can't create server worker");
|
display = getenv("DISPLAY");
|
||||||
if(proccreate(ibusthread, nil, 32768) < 0)
|
if(display != nil && display[0] != '\0')
|
||||||
die("can't create IBus worker");
|
proccreate(ximthread, nil, 32768);
|
||||||
if(threadcreate(dictthread, nil, 16384) < 0)
|
|
||||||
die("can't create dictionary worker");
|
|
||||||
imthread(nil);
|
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
|
contract and groups rows by their Hangul reading to produce the existing
|
||||||
runtime dictionary format. Candidate order follows source order. The source
|
runtime dictionary format. Candidate order follows source order. The source
|
||||||
keeps all retained pairs for review; the generated dictionary stores the first
|
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
|
The source is derived from libhangul release tag `libhangul-0.2.0`. The
|
||||||
annotated tag object is `20afc38922e3595ee3ed5b186f2ea05afe663763`, its
|
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
|
`verifymap.py` checks UTF-8, row structure, unique keys, 64-rune keys and
|
||||||
values, canonical candidate spacing, and duplicate dictionary candidates.
|
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
|
;; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
;; POSSIBILITY OF SUCH DAMAGE.
|
;; 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 ぞ
|
zo ぞ
|
||||||
ta た
|
ta た
|
||||||
da だ
|
da だ
|
||||||
|
di ぢ
|
||||||
ti ち
|
ti ち
|
||||||
chi ち
|
chi ち
|
||||||
cha ちゃ
|
cha ちゃ
|
||||||
chu ちゅ
|
chu ちゅ
|
||||||
|
che ちぇ
|
||||||
tyu ちゅ
|
tyu ちゅ
|
||||||
cho ちょ
|
cho ちょ
|
||||||
ji じ
|
ji じ
|
||||||
@@ -83,6 +85,10 @@ pyu ぴゅ
|
|||||||
pyo ぴょ
|
pyo ぴょ
|
||||||
hu ふ
|
hu ふ
|
||||||
fu ふ
|
fu ふ
|
||||||
|
fa ふぁ
|
||||||
|
fi ふぃ
|
||||||
|
fe ふぇ
|
||||||
|
fo ふぉ
|
||||||
bu ぶ
|
bu ぶ
|
||||||
vu ぶ
|
vu ぶ
|
||||||
pu ぷ
|
pu ぷ
|
||||||
@@ -124,47 +130,23 @@ xi ぃ
|
|||||||
xu ぅ
|
xu ぅ
|
||||||
xe ぇ
|
xe ぇ
|
||||||
xo ぉ
|
xo ぉ
|
||||||
kka っか
|
kk っ
|
||||||
kki っき
|
ss っ
|
||||||
kkya っきゃ
|
tt っ
|
||||||
kkyu っきゅ
|
pp っ
|
||||||
kkyo っきょ
|
cc っ
|
||||||
kku っく
|
gg っ
|
||||||
kke っけ
|
zz っ
|
||||||
kko っこ
|
jj っ
|
||||||
ssa っさ
|
dd っ
|
||||||
ssi っし
|
bb っ
|
||||||
sshi っし
|
hh っ
|
||||||
ssha っしゃ
|
ff っ
|
||||||
sshu っしゅ
|
mm っ
|
||||||
sshe っしぇ
|
yy っ
|
||||||
ssho っしょ
|
rr っ
|
||||||
ssyo っしょ
|
ww っ
|
||||||
ssu っす
|
vv っ
|
||||||
sse っせ
|
tch っ
|
||||||
sso っそ
|
|
||||||
tta った
|
|
||||||
dda っだ
|
|
||||||
tti っち
|
|
||||||
cchi っち
|
|
||||||
ccha っちゃ
|
|
||||||
cchu っちゅ
|
|
||||||
ttyu っちゅ
|
|
||||||
ccho っちょ
|
|
||||||
ttu っつ
|
|
||||||
ttsu っつ
|
|
||||||
ddu っづ
|
|
||||||
tte って
|
|
||||||
dde っで
|
|
||||||
tto っと
|
|
||||||
ddo っど
|
|
||||||
ppa っぱ
|
|
||||||
ppi っぴ
|
|
||||||
ppya っぴゃ
|
|
||||||
ppyu っぴゅ
|
|
||||||
ppyo っぴょ
|
|
||||||
ppu っぷ
|
|
||||||
ppe っぺ
|
|
||||||
ppo っぽ
|
|
||||||
. 。
|
. 。
|
||||||
, 、
|
, 、
|
||||||
|
|||||||
11
map/kata.map
11
map/kata.map
@@ -128,6 +128,17 @@ pp ッ
|
|||||||
tt ッ
|
tt ッ
|
||||||
tch ッ
|
tch ッ
|
||||||
ss ッ
|
ss ッ
|
||||||
|
gg ッ
|
||||||
|
zz ッ
|
||||||
|
jj ッ
|
||||||
|
bb ッ
|
||||||
|
hh ッ
|
||||||
|
ff ッ
|
||||||
|
mm ッ
|
||||||
|
yy ッ
|
||||||
|
rr ッ
|
||||||
|
ww ッ
|
||||||
|
vv ッ
|
||||||
di ディ
|
di ディ
|
||||||
fa ファ
|
fa ファ
|
||||||
fi フィ
|
fi フィ
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
|
|
||||||
SOURCE = Path(__file__).with_name("hanja.src")
|
SOURCE = Path(__file__).with_name("hanja.src")
|
||||||
MAXCANDIDATES = 32
|
MAXCANDIDATES = 128
|
||||||
|
|
||||||
|
|
||||||
def ishangul(s):
|
def ishangul(s):
|
||||||
|
|||||||
@@ -55,11 +55,15 @@ def vowel2():
|
|||||||
tab = [
|
tab = [
|
||||||
("oa", "oa", "a"), ("oe", "oe", "e"), ("ai", "ai", "a"),
|
("oa", "oa", "a"), ("oe", "oe", "e"), ("ai", "ai", "a"),
|
||||||
("ao", "ao", "a"), ("au", "au", "a"), ("ay", "ay", "a"),
|
("ao", "ao", "a"), ("au", "au", "a"), ("ay", "ay", "a"),
|
||||||
("eu", "eu", "e"), ("iu", "iu", "i"), ("oi", "oi", "o"),
|
("eo", "eo", "e"), ("eu", "eu", "e"), ("ia", "ia", "i"),
|
||||||
|
("iu", "iu", "i"), ("oi", "oi", "o"), ("ua", "ua", "u"),
|
||||||
("ui", "ui", "u"), ("uy", "uy", "y"),
|
("ui", "ui", "u"), ("uy", "uy", "y"),
|
||||||
|
("aau", "âu", "â"), ("aay", "ây", "â"), ("eeu", "êu", "ê"),
|
||||||
|
("ooi", "ôi", "ô"), ("owi", "ơi", "ơ"), ("uwi", "ưi", "ư"),
|
||||||
|
("uwu", "ưu", "ư"), ("uwa", "ưa", "ư"), ("uee", "uê", "ê"),
|
||||||
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
||||||
("uow", "ươ", "ơ"), ("uaa", "uâ", "â"), ("oaw", "oă", "ă"),
|
("uow", "ươ", "ơ"), ("uaa", "uâ", "â"), ("oaw", "oă", "ă"),
|
||||||
("uwa", "ưa", "a"), ("uwow", "ươ", "ơ"),
|
("uwow", "ươ", "ơ"),
|
||||||
]
|
]
|
||||||
for i, o, v in tab:
|
for i, o, v in tab:
|
||||||
emit(i, o)
|
emit(i, o)
|
||||||
@@ -68,21 +72,21 @@ def vowel2():
|
|||||||
emit("ie", "ie")
|
emit("ie", "ie")
|
||||||
emit("ye", "ye")
|
emit("ye", "ye")
|
||||||
emit("uo", "uo")
|
emit("uo", "uo")
|
||||||
emit("ua", "ua")
|
|
||||||
|
|
||||||
def vowel3():
|
def vowel3():
|
||||||
# input, output, vowel
|
# input, output, vowel
|
||||||
tab = [
|
tab = [
|
||||||
("ieeu", "iêu", "ê"), ("yeeu", "yêu", "ê"),
|
("ieeu", "iêu", "ê"), ("yeeu", "yêu", "ê"),
|
||||||
("uooi", "uôi", "ô"), ("uowi", "ươi", "ơ"),
|
("uooi", "uôi", "ô"), ("uowi", "ươi", "ơ"),
|
||||||
("oai", "oai", "a"), ("oay", "oay", "a"),
|
("uowu", "ươu", "ơ"), ("uwowi", "ươi", "ơ"),
|
||||||
("uyee", "uyê", "ê"),
|
("uwowu", "ươu", "ơ"), ("uaay", "uây", "â"),
|
||||||
|
("oai", "oai", "a"), ("oay", "oay", "a"), ("oeo", "oeo", "e"),
|
||||||
|
("uya", "uya", "y"), ("uyu", "uyu", "y"), ("uyee", "uyê", "ê"),
|
||||||
]
|
]
|
||||||
for i, o, v in tab:
|
for i, o, v in tab:
|
||||||
emit(i, o)
|
emit(i, o)
|
||||||
for t in tone:
|
for t in tone:
|
||||||
emit(i+t, o.replace(v, addtone(v, t), 1))
|
emit(i+t, o.replace(v, addtone(v, t), 1))
|
||||||
emit("uya", "uya")
|
|
||||||
|
|
||||||
def modvowels():
|
def modvowels():
|
||||||
for i, o in modvowel:
|
for i, o in modvowel:
|
||||||
@@ -132,10 +136,11 @@ def final():
|
|||||||
("aw", "ă", "ă"), ("aa", "â", "â"), ("ee", "ê", "ê"),
|
("aw", "ă", "ă"), ("aa", "â", "â"), ("ee", "ê", "ê"),
|
||||||
("oo", "ô", "ô"), ("ow", "ơ", "ơ"), ("uw", "ư", "ư"),
|
("oo", "ô", "ô"), ("ow", "ơ", "ơ"), ("uw", "ư", "ư"),
|
||||||
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
||||||
("uow", "ươ", "ơ"), ("uaa", "uâ", "â"), ("oaw", "oă", "ă"),
|
("uow", "ươ", "ơ"), ("uwow", "ươ", "ơ"), ("uyee", "uyê", "ê"),
|
||||||
|
("uaa", "uâ", "â"), ("uee", "uê", "ê"), ("oaw", "oă", "ă"),
|
||||||
("ai", "ai", "a"), ("ao", "ao", "a"), ("au", "au", "a"),
|
("ai", "ai", "a"), ("ao", "ao", "a"), ("au", "au", "a"),
|
||||||
("ay", "ay", "a"), ("oa", "oa", "a"), ("oi", "oi", "o"),
|
("ay", "ay", "a"), ("oa", "oa", "a"), ("oe", "oe", "e"),
|
||||||
("ui", "ui", "u"), ("uy", "uy", "y"),
|
("oi", "oi", "o"), ("ui", "ui", "u"), ("uy", "uy", "y"),
|
||||||
]
|
]
|
||||||
for i, o, v in tab:
|
for i, o, v in tab:
|
||||||
for c in coda:
|
for c in coda:
|
||||||
|
|||||||
768
map/telex.map
768
map/telex.map
@@ -142,6 +142,18 @@ ayx ãy
|
|||||||
Ayx Ãy
|
Ayx Ãy
|
||||||
ayj ạy
|
ayj ạy
|
||||||
Ayj Ạy
|
Ayj Ạy
|
||||||
|
eo eo
|
||||||
|
Eo Eo
|
||||||
|
eos éo
|
||||||
|
Eos Éo
|
||||||
|
eof èo
|
||||||
|
Eof Èo
|
||||||
|
eor ẻo
|
||||||
|
Eor Ẻo
|
||||||
|
eox ẽo
|
||||||
|
Eox Ẽo
|
||||||
|
eoj ẹo
|
||||||
|
Eoj Ẹo
|
||||||
eu eu
|
eu eu
|
||||||
Eu Eu
|
Eu Eu
|
||||||
eus éu
|
eus éu
|
||||||
@@ -154,6 +166,18 @@ eux ẽu
|
|||||||
Eux Ẽu
|
Eux Ẽu
|
||||||
euj ẹu
|
euj ẹu
|
||||||
Euj Ẹu
|
Euj Ẹu
|
||||||
|
ia ia
|
||||||
|
Ia Ia
|
||||||
|
ias ía
|
||||||
|
Ias Ía
|
||||||
|
iaf ìa
|
||||||
|
Iaf Ìa
|
||||||
|
iar ỉa
|
||||||
|
Iar Ỉa
|
||||||
|
iax ĩa
|
||||||
|
Iax Ĩa
|
||||||
|
iaj ịa
|
||||||
|
Iaj Ịa
|
||||||
iu iu
|
iu iu
|
||||||
Iu Iu
|
Iu Iu
|
||||||
ius íu
|
ius íu
|
||||||
@@ -178,6 +202,18 @@ oix õi
|
|||||||
Oix Õi
|
Oix Õi
|
||||||
oij ọi
|
oij ọi
|
||||||
Oij Ọi
|
Oij Ọi
|
||||||
|
ua ua
|
||||||
|
Ua Ua
|
||||||
|
uas úa
|
||||||
|
Uas Úa
|
||||||
|
uaf ùa
|
||||||
|
Uaf Ùa
|
||||||
|
uar ủa
|
||||||
|
Uar Ủa
|
||||||
|
uax ũa
|
||||||
|
Uax Ũa
|
||||||
|
uaj ụa
|
||||||
|
Uaj Ụa
|
||||||
ui ui
|
ui ui
|
||||||
Ui Ui
|
Ui Ui
|
||||||
uis úi
|
uis úi
|
||||||
@@ -202,6 +238,114 @@ uyx uỹ
|
|||||||
Uyx Uỹ
|
Uyx Uỹ
|
||||||
uyj uỵ
|
uyj uỵ
|
||||||
Uyj Uỵ
|
Uyj Uỵ
|
||||||
|
aau âu
|
||||||
|
Aau Âu
|
||||||
|
aaus ấu
|
||||||
|
Aaus Ấu
|
||||||
|
aauf ầu
|
||||||
|
Aauf Ầu
|
||||||
|
aaur ẩu
|
||||||
|
Aaur Ẩu
|
||||||
|
aaux ẫu
|
||||||
|
Aaux Ẫu
|
||||||
|
aauj ậu
|
||||||
|
Aauj Ậu
|
||||||
|
aay ây
|
||||||
|
Aay Ây
|
||||||
|
aays ấy
|
||||||
|
Aays Ấy
|
||||||
|
aayf ầy
|
||||||
|
Aayf Ầy
|
||||||
|
aayr ẩy
|
||||||
|
Aayr Ẩy
|
||||||
|
aayx ẫy
|
||||||
|
Aayx Ẫy
|
||||||
|
aayj ậy
|
||||||
|
Aayj Ậy
|
||||||
|
eeu êu
|
||||||
|
Eeu Êu
|
||||||
|
eeus ếu
|
||||||
|
Eeus Ếu
|
||||||
|
eeuf ều
|
||||||
|
Eeuf Ều
|
||||||
|
eeur ểu
|
||||||
|
Eeur Ểu
|
||||||
|
eeux ễu
|
||||||
|
Eeux Ễu
|
||||||
|
eeuj ệu
|
||||||
|
Eeuj Ệu
|
||||||
|
ooi ôi
|
||||||
|
Ooi Ôi
|
||||||
|
oois ối
|
||||||
|
Oois Ối
|
||||||
|
ooif ồi
|
||||||
|
Ooif Ồi
|
||||||
|
ooir ổi
|
||||||
|
Ooir Ổi
|
||||||
|
ooix ỗi
|
||||||
|
Ooix Ỗi
|
||||||
|
ooij ội
|
||||||
|
Ooij Ội
|
||||||
|
owi ơi
|
||||||
|
Owi Ơi
|
||||||
|
owis ới
|
||||||
|
Owis Ới
|
||||||
|
owif ời
|
||||||
|
Owif Ời
|
||||||
|
owir ởi
|
||||||
|
Owir Ởi
|
||||||
|
owix ỡi
|
||||||
|
Owix Ỡi
|
||||||
|
owij ợi
|
||||||
|
Owij Ợi
|
||||||
|
uwi ưi
|
||||||
|
Uwi Ưi
|
||||||
|
uwis ứi
|
||||||
|
Uwis Ứi
|
||||||
|
uwif ừi
|
||||||
|
Uwif Ừi
|
||||||
|
uwir ửi
|
||||||
|
Uwir Ửi
|
||||||
|
uwix ữi
|
||||||
|
Uwix Ữi
|
||||||
|
uwij ựi
|
||||||
|
Uwij Ựi
|
||||||
|
uwu ưu
|
||||||
|
Uwu Ưu
|
||||||
|
uwus ứu
|
||||||
|
Uwus Ứu
|
||||||
|
uwuf ừu
|
||||||
|
Uwuf Ừu
|
||||||
|
uwur ửu
|
||||||
|
Uwur Ửu
|
||||||
|
uwux ữu
|
||||||
|
Uwux Ữu
|
||||||
|
uwuj ựu
|
||||||
|
Uwuj Ựu
|
||||||
|
uwa ưa
|
||||||
|
Uwa Ưa
|
||||||
|
uwas ứa
|
||||||
|
Uwas Ứa
|
||||||
|
uwaf ừa
|
||||||
|
Uwaf Ừa
|
||||||
|
uwar ửa
|
||||||
|
Uwar Ửa
|
||||||
|
uwax ữa
|
||||||
|
Uwax Ữa
|
||||||
|
uwaj ựa
|
||||||
|
Uwaj Ựa
|
||||||
|
uee uê
|
||||||
|
Uee Uê
|
||||||
|
uees uế
|
||||||
|
Uees Uế
|
||||||
|
ueef uề
|
||||||
|
Ueef Uề
|
||||||
|
ueer uể
|
||||||
|
Ueer Uể
|
||||||
|
ueex uễ
|
||||||
|
Ueex Uễ
|
||||||
|
ueej uệ
|
||||||
|
Ueej Uệ
|
||||||
iee iê
|
iee iê
|
||||||
Iee Iê
|
Iee Iê
|
||||||
iees iế
|
iees iế
|
||||||
@@ -274,18 +418,6 @@ oawx oẵ
|
|||||||
Oawx Oẵ
|
Oawx Oẵ
|
||||||
oawj oặ
|
oawj oặ
|
||||||
Oawj Oặ
|
Oawj Oặ
|
||||||
uwa ưa
|
|
||||||
Uwa Ưa
|
|
||||||
uwas ưá
|
|
||||||
Uwas Ưá
|
|
||||||
uwaf ưà
|
|
||||||
Uwaf Ưà
|
|
||||||
uwar ưả
|
|
||||||
Uwar Ưả
|
|
||||||
uwax ưã
|
|
||||||
Uwax Ưã
|
|
||||||
uwaj ưạ
|
|
||||||
Uwaj Ưạ
|
|
||||||
uwow ươ
|
uwow ươ
|
||||||
Uwow Ươ
|
Uwow Ươ
|
||||||
uwows ướ
|
uwows ướ
|
||||||
@@ -304,8 +436,6 @@ ye ye
|
|||||||
Ye Ye
|
Ye Ye
|
||||||
uo uo
|
uo uo
|
||||||
Uo Uo
|
Uo Uo
|
||||||
ua ua
|
|
||||||
Ua Ua
|
|
||||||
ieeu iêu
|
ieeu iêu
|
||||||
Ieeu Iêu
|
Ieeu Iêu
|
||||||
ieeus iếu
|
ieeus iếu
|
||||||
@@ -354,6 +484,54 @@ uowix ưỡi
|
|||||||
Uowix Ưỡi
|
Uowix Ưỡi
|
||||||
uowij ượi
|
uowij ượi
|
||||||
Uowij Ượi
|
Uowij Ượi
|
||||||
|
uowu ươu
|
||||||
|
Uowu Ươu
|
||||||
|
uowus ướu
|
||||||
|
Uowus Ướu
|
||||||
|
uowuf ườu
|
||||||
|
Uowuf Ườu
|
||||||
|
uowur ưởu
|
||||||
|
Uowur Ưởu
|
||||||
|
uowux ưỡu
|
||||||
|
Uowux Ưỡu
|
||||||
|
uowuj ượu
|
||||||
|
Uowuj Ượu
|
||||||
|
uwowi ươi
|
||||||
|
Uwowi Ươi
|
||||||
|
uwowis ưới
|
||||||
|
Uwowis Ưới
|
||||||
|
uwowif ười
|
||||||
|
Uwowif Ười
|
||||||
|
uwowir ưởi
|
||||||
|
Uwowir Ưởi
|
||||||
|
uwowix ưỡi
|
||||||
|
Uwowix Ưỡi
|
||||||
|
uwowij ượi
|
||||||
|
Uwowij Ượi
|
||||||
|
uwowu ươu
|
||||||
|
Uwowu Ươu
|
||||||
|
uwowus ướu
|
||||||
|
Uwowus Ướu
|
||||||
|
uwowuf ườu
|
||||||
|
Uwowuf Ườu
|
||||||
|
uwowur ưởu
|
||||||
|
Uwowur Ưởu
|
||||||
|
uwowux ưỡu
|
||||||
|
Uwowux Ưỡu
|
||||||
|
uwowuj ượu
|
||||||
|
Uwowuj Ượu
|
||||||
|
uaay uây
|
||||||
|
Uaay Uây
|
||||||
|
uaays uấy
|
||||||
|
Uaays Uấy
|
||||||
|
uaayf uầy
|
||||||
|
Uaayf Uầy
|
||||||
|
uaayr uẩy
|
||||||
|
Uaayr Uẩy
|
||||||
|
uaayx uẫy
|
||||||
|
Uaayx Uẫy
|
||||||
|
uaayj uậy
|
||||||
|
Uaayj Uậy
|
||||||
oai oai
|
oai oai
|
||||||
Oai Oai
|
Oai Oai
|
||||||
oais oái
|
oais oái
|
||||||
@@ -378,6 +556,42 @@ oayx oãy
|
|||||||
Oayx Oãy
|
Oayx Oãy
|
||||||
oayj oạy
|
oayj oạy
|
||||||
Oayj Oạy
|
Oayj Oạy
|
||||||
|
oeo oeo
|
||||||
|
Oeo Oeo
|
||||||
|
oeos oéo
|
||||||
|
Oeos Oéo
|
||||||
|
oeof oèo
|
||||||
|
Oeof Oèo
|
||||||
|
oeor oẻo
|
||||||
|
Oeor Oẻo
|
||||||
|
oeox oẽo
|
||||||
|
Oeox Oẽo
|
||||||
|
oeoj oẹo
|
||||||
|
Oeoj Oẹo
|
||||||
|
uya uya
|
||||||
|
Uya Uya
|
||||||
|
uyas uýa
|
||||||
|
Uyas Uýa
|
||||||
|
uyaf uỳa
|
||||||
|
Uyaf Uỳa
|
||||||
|
uyar uỷa
|
||||||
|
Uyar Uỷa
|
||||||
|
uyax uỹa
|
||||||
|
Uyax Uỹa
|
||||||
|
uyaj uỵa
|
||||||
|
Uyaj Uỵa
|
||||||
|
uyu uyu
|
||||||
|
Uyu Uyu
|
||||||
|
uyus uýu
|
||||||
|
Uyus Uýu
|
||||||
|
uyuf uỳu
|
||||||
|
Uyuf Uỳu
|
||||||
|
uyur uỷu
|
||||||
|
Uyur Uỷu
|
||||||
|
uyux uỹu
|
||||||
|
Uyux Uỹu
|
||||||
|
uyuj uỵu
|
||||||
|
Uyuj Uỵu
|
||||||
uyee uyê
|
uyee uyê
|
||||||
Uyee Uyê
|
Uyee Uyê
|
||||||
uyees uyế
|
uyees uyế
|
||||||
@@ -390,8 +604,6 @@ uyeex uyễ
|
|||||||
Uyeex Uyễ
|
Uyeex Uyễ
|
||||||
uyeej uyệ
|
uyeej uyệ
|
||||||
Uyeej Uyệ
|
Uyeej Uyệ
|
||||||
uya uya
|
|
||||||
Uya Uya
|
|
||||||
aw ă
|
aw ă
|
||||||
Aw Ă
|
Aw Ă
|
||||||
aa â
|
aa â
|
||||||
@@ -2146,6 +2358,198 @@ uownhx ưỡnh
|
|||||||
Uownhx Ưỡnh
|
Uownhx Ưỡnh
|
||||||
uownhj ượnh
|
uownhj ượnh
|
||||||
Uownhj Ượnh
|
Uownhj Ượnh
|
||||||
|
uwowc ươc
|
||||||
|
Uwowc Ươc
|
||||||
|
uwowcs ước
|
||||||
|
Uwowcs Ước
|
||||||
|
uwowcf ườc
|
||||||
|
Uwowcf Ườc
|
||||||
|
uwowcr ưởc
|
||||||
|
Uwowcr Ưởc
|
||||||
|
uwowcx ưỡc
|
||||||
|
Uwowcx Ưỡc
|
||||||
|
uwowcj ược
|
||||||
|
Uwowcj Ược
|
||||||
|
uwowm ươm
|
||||||
|
Uwowm Ươm
|
||||||
|
uwowms ướm
|
||||||
|
Uwowms Ướm
|
||||||
|
uwowmf ườm
|
||||||
|
Uwowmf Ườm
|
||||||
|
uwowmr ưởm
|
||||||
|
Uwowmr Ưởm
|
||||||
|
uwowmx ưỡm
|
||||||
|
Uwowmx Ưỡm
|
||||||
|
uwowmj ượm
|
||||||
|
Uwowmj Ượm
|
||||||
|
uwown ươn
|
||||||
|
Uwown Ươn
|
||||||
|
uwowns ướn
|
||||||
|
Uwowns Ướn
|
||||||
|
uwownf ườn
|
||||||
|
Uwownf Ườn
|
||||||
|
uwownr ưởn
|
||||||
|
Uwownr Ưởn
|
||||||
|
uwownx ưỡn
|
||||||
|
Uwownx Ưỡn
|
||||||
|
uwownj ượn
|
||||||
|
Uwownj Ượn
|
||||||
|
uwowp ươp
|
||||||
|
Uwowp Ươp
|
||||||
|
uwowps ướp
|
||||||
|
Uwowps Ướp
|
||||||
|
uwowpf ườp
|
||||||
|
Uwowpf Ườp
|
||||||
|
uwowpr ưởp
|
||||||
|
Uwowpr Ưởp
|
||||||
|
uwowpx ưỡp
|
||||||
|
Uwowpx Ưỡp
|
||||||
|
uwowpj ượp
|
||||||
|
Uwowpj Ượp
|
||||||
|
uwowt ươt
|
||||||
|
Uwowt Ươt
|
||||||
|
uwowts ướt
|
||||||
|
Uwowts Ướt
|
||||||
|
uwowtf ườt
|
||||||
|
Uwowtf Ườt
|
||||||
|
uwowtr ưởt
|
||||||
|
Uwowtr Ưởt
|
||||||
|
uwowtx ưỡt
|
||||||
|
Uwowtx Ưỡt
|
||||||
|
uwowtj ượt
|
||||||
|
Uwowtj Ượt
|
||||||
|
uwowch ươch
|
||||||
|
Uwowch Ươch
|
||||||
|
uwowchs ướch
|
||||||
|
Uwowchs Ướch
|
||||||
|
uwowchf ườch
|
||||||
|
Uwowchf Ườch
|
||||||
|
uwowchr ưởch
|
||||||
|
Uwowchr Ưởch
|
||||||
|
uwowchx ưỡch
|
||||||
|
Uwowchx Ưỡch
|
||||||
|
uwowchj ượch
|
||||||
|
Uwowchj Ượch
|
||||||
|
uwowng ương
|
||||||
|
Uwowng Ương
|
||||||
|
uwowngs ướng
|
||||||
|
Uwowngs Ướng
|
||||||
|
uwowngf ường
|
||||||
|
Uwowngf Ường
|
||||||
|
uwowngr ưởng
|
||||||
|
Uwowngr Ưởng
|
||||||
|
uwowngx ưỡng
|
||||||
|
Uwowngx Ưỡng
|
||||||
|
uwowngj ượng
|
||||||
|
Uwowngj Ượng
|
||||||
|
uwownh ươnh
|
||||||
|
Uwownh Ươnh
|
||||||
|
uwownhs ướnh
|
||||||
|
Uwownhs Ướnh
|
||||||
|
uwownhf ườnh
|
||||||
|
Uwownhf Ườnh
|
||||||
|
uwownhr ưởnh
|
||||||
|
Uwownhr Ưởnh
|
||||||
|
uwownhx ưỡnh
|
||||||
|
Uwownhx Ưỡnh
|
||||||
|
uwownhj ượnh
|
||||||
|
Uwownhj Ượnh
|
||||||
|
uyeec uyêc
|
||||||
|
Uyeec Uyêc
|
||||||
|
uyeecs uyếc
|
||||||
|
Uyeecs Uyếc
|
||||||
|
uyeecf uyềc
|
||||||
|
Uyeecf Uyềc
|
||||||
|
uyeecr uyểc
|
||||||
|
Uyeecr Uyểc
|
||||||
|
uyeecx uyễc
|
||||||
|
Uyeecx Uyễc
|
||||||
|
uyeecj uyệc
|
||||||
|
Uyeecj Uyệc
|
||||||
|
uyeem uyêm
|
||||||
|
Uyeem Uyêm
|
||||||
|
uyeems uyếm
|
||||||
|
Uyeems Uyếm
|
||||||
|
uyeemf uyềm
|
||||||
|
Uyeemf Uyềm
|
||||||
|
uyeemr uyểm
|
||||||
|
Uyeemr Uyểm
|
||||||
|
uyeemx uyễm
|
||||||
|
Uyeemx Uyễm
|
||||||
|
uyeemj uyệm
|
||||||
|
Uyeemj Uyệm
|
||||||
|
uyeen uyên
|
||||||
|
Uyeen Uyên
|
||||||
|
uyeens uyến
|
||||||
|
Uyeens Uyến
|
||||||
|
uyeenf uyền
|
||||||
|
Uyeenf Uyền
|
||||||
|
uyeenr uyển
|
||||||
|
Uyeenr Uyển
|
||||||
|
uyeenx uyễn
|
||||||
|
Uyeenx Uyễn
|
||||||
|
uyeenj uyện
|
||||||
|
Uyeenj Uyện
|
||||||
|
uyeep uyêp
|
||||||
|
Uyeep Uyêp
|
||||||
|
uyeeps uyếp
|
||||||
|
Uyeeps Uyếp
|
||||||
|
uyeepf uyềp
|
||||||
|
Uyeepf Uyềp
|
||||||
|
uyeepr uyểp
|
||||||
|
Uyeepr Uyểp
|
||||||
|
uyeepx uyễp
|
||||||
|
Uyeepx Uyễp
|
||||||
|
uyeepj uyệp
|
||||||
|
Uyeepj Uyệp
|
||||||
|
uyeet uyêt
|
||||||
|
Uyeet Uyêt
|
||||||
|
uyeets uyết
|
||||||
|
Uyeets Uyết
|
||||||
|
uyeetf uyềt
|
||||||
|
Uyeetf Uyềt
|
||||||
|
uyeetr uyểt
|
||||||
|
Uyeetr Uyểt
|
||||||
|
uyeetx uyễt
|
||||||
|
Uyeetx Uyễt
|
||||||
|
uyeetj uyệt
|
||||||
|
Uyeetj Uyệt
|
||||||
|
uyeech uyêch
|
||||||
|
Uyeech Uyêch
|
||||||
|
uyeechs uyếch
|
||||||
|
Uyeechs Uyếch
|
||||||
|
uyeechf uyềch
|
||||||
|
Uyeechf Uyềch
|
||||||
|
uyeechr uyểch
|
||||||
|
Uyeechr Uyểch
|
||||||
|
uyeechx uyễch
|
||||||
|
Uyeechx Uyễch
|
||||||
|
uyeechj uyệch
|
||||||
|
Uyeechj Uyệch
|
||||||
|
uyeeng uyêng
|
||||||
|
Uyeeng Uyêng
|
||||||
|
uyeengs uyếng
|
||||||
|
Uyeengs Uyếng
|
||||||
|
uyeengf uyềng
|
||||||
|
Uyeengf Uyềng
|
||||||
|
uyeengr uyểng
|
||||||
|
Uyeengr Uyểng
|
||||||
|
uyeengx uyễng
|
||||||
|
Uyeengx Uyễng
|
||||||
|
uyeengj uyệng
|
||||||
|
Uyeengj Uyệng
|
||||||
|
uyeenh uyênh
|
||||||
|
Uyeenh Uyênh
|
||||||
|
uyeenhs uyếnh
|
||||||
|
Uyeenhs Uyếnh
|
||||||
|
uyeenhf uyềnh
|
||||||
|
Uyeenhf Uyềnh
|
||||||
|
uyeenhr uyểnh
|
||||||
|
Uyeenhr Uyểnh
|
||||||
|
uyeenhx uyễnh
|
||||||
|
Uyeenhx Uyễnh
|
||||||
|
uyeenhj uyệnh
|
||||||
|
Uyeenhj Uyệnh
|
||||||
uaac uâc
|
uaac uâc
|
||||||
Uaac Uâc
|
Uaac Uâc
|
||||||
uaacs uấc
|
uaacs uấc
|
||||||
@@ -2242,6 +2646,102 @@ uaanhx uẫnh
|
|||||||
Uaanhx Uẫnh
|
Uaanhx Uẫnh
|
||||||
uaanhj uậnh
|
uaanhj uậnh
|
||||||
Uaanhj Uậnh
|
Uaanhj Uậnh
|
||||||
|
ueec uêc
|
||||||
|
Ueec Uêc
|
||||||
|
ueecs uếc
|
||||||
|
Ueecs Uếc
|
||||||
|
ueecf uềc
|
||||||
|
Ueecf Uềc
|
||||||
|
ueecr uểc
|
||||||
|
Ueecr Uểc
|
||||||
|
ueecx uễc
|
||||||
|
Ueecx Uễc
|
||||||
|
ueecj uệc
|
||||||
|
Ueecj Uệc
|
||||||
|
ueem uêm
|
||||||
|
Ueem Uêm
|
||||||
|
ueems uếm
|
||||||
|
Ueems Uếm
|
||||||
|
ueemf uềm
|
||||||
|
Ueemf Uềm
|
||||||
|
ueemr uểm
|
||||||
|
Ueemr Uểm
|
||||||
|
ueemx uễm
|
||||||
|
Ueemx Uễm
|
||||||
|
ueemj uệm
|
||||||
|
Ueemj Uệm
|
||||||
|
ueen uên
|
||||||
|
Ueen Uên
|
||||||
|
ueens uến
|
||||||
|
Ueens Uến
|
||||||
|
ueenf uền
|
||||||
|
Ueenf Uền
|
||||||
|
ueenr uển
|
||||||
|
Ueenr Uển
|
||||||
|
ueenx uễn
|
||||||
|
Ueenx Uễn
|
||||||
|
ueenj uện
|
||||||
|
Ueenj Uện
|
||||||
|
ueep uêp
|
||||||
|
Ueep Uêp
|
||||||
|
ueeps uếp
|
||||||
|
Ueeps Uếp
|
||||||
|
ueepf uềp
|
||||||
|
Ueepf Uềp
|
||||||
|
ueepr uểp
|
||||||
|
Ueepr Uểp
|
||||||
|
ueepx uễp
|
||||||
|
Ueepx Uễp
|
||||||
|
ueepj uệp
|
||||||
|
Ueepj Uệp
|
||||||
|
ueet uêt
|
||||||
|
Ueet Uêt
|
||||||
|
ueets uết
|
||||||
|
Ueets Uết
|
||||||
|
ueetf uềt
|
||||||
|
Ueetf Uềt
|
||||||
|
ueetr uểt
|
||||||
|
Ueetr Uểt
|
||||||
|
ueetx uễt
|
||||||
|
Ueetx Uễt
|
||||||
|
ueetj uệt
|
||||||
|
Ueetj Uệt
|
||||||
|
ueech uêch
|
||||||
|
Ueech Uêch
|
||||||
|
ueechs uếch
|
||||||
|
Ueechs Uếch
|
||||||
|
ueechf uềch
|
||||||
|
Ueechf Uềch
|
||||||
|
ueechr uểch
|
||||||
|
Ueechr Uểch
|
||||||
|
ueechx uễch
|
||||||
|
Ueechx Uễch
|
||||||
|
ueechj uệch
|
||||||
|
Ueechj Uệch
|
||||||
|
ueeng uêng
|
||||||
|
Ueeng Uêng
|
||||||
|
ueengs uếng
|
||||||
|
Ueengs Uếng
|
||||||
|
ueengf uềng
|
||||||
|
Ueengf Uềng
|
||||||
|
ueengr uểng
|
||||||
|
Ueengr Uểng
|
||||||
|
ueengx uễng
|
||||||
|
Ueengx Uễng
|
||||||
|
ueengj uệng
|
||||||
|
Ueengj Uệng
|
||||||
|
ueenh uênh
|
||||||
|
Ueenh Uênh
|
||||||
|
ueenhs uếnh
|
||||||
|
Ueenhs Uếnh
|
||||||
|
ueenhf uềnh
|
||||||
|
Ueenhf Uềnh
|
||||||
|
ueenhr uểnh
|
||||||
|
Ueenhr Uểnh
|
||||||
|
ueenhx uễnh
|
||||||
|
Ueenhx Uễnh
|
||||||
|
ueenhj uệnh
|
||||||
|
Ueenhj Uệnh
|
||||||
oawc oăc
|
oawc oăc
|
||||||
Oawc Oăc
|
Oawc Oăc
|
||||||
oawcs oắc
|
oawcs oắc
|
||||||
@@ -2818,6 +3318,102 @@ oanhx oãnh
|
|||||||
Oanhx Oãnh
|
Oanhx Oãnh
|
||||||
oanhj oạnh
|
oanhj oạnh
|
||||||
Oanhj Oạnh
|
Oanhj Oạnh
|
||||||
|
oec oec
|
||||||
|
Oec Oec
|
||||||
|
oecs oéc
|
||||||
|
Oecs Oéc
|
||||||
|
oecf oèc
|
||||||
|
Oecf Oèc
|
||||||
|
oecr oẻc
|
||||||
|
Oecr Oẻc
|
||||||
|
oecx oẽc
|
||||||
|
Oecx Oẽc
|
||||||
|
oecj oẹc
|
||||||
|
Oecj Oẹc
|
||||||
|
oem oem
|
||||||
|
Oem Oem
|
||||||
|
oems oém
|
||||||
|
Oems Oém
|
||||||
|
oemf oèm
|
||||||
|
Oemf Oèm
|
||||||
|
oemr oẻm
|
||||||
|
Oemr Oẻm
|
||||||
|
oemx oẽm
|
||||||
|
Oemx Oẽm
|
||||||
|
oemj oẹm
|
||||||
|
Oemj Oẹm
|
||||||
|
oen oen
|
||||||
|
Oen Oen
|
||||||
|
oens oén
|
||||||
|
Oens Oén
|
||||||
|
oenf oèn
|
||||||
|
Oenf Oèn
|
||||||
|
oenr oẻn
|
||||||
|
Oenr Oẻn
|
||||||
|
oenx oẽn
|
||||||
|
Oenx Oẽn
|
||||||
|
oenj oẹn
|
||||||
|
Oenj Oẹn
|
||||||
|
oep oep
|
||||||
|
Oep Oep
|
||||||
|
oeps oép
|
||||||
|
Oeps Oép
|
||||||
|
oepf oèp
|
||||||
|
Oepf Oèp
|
||||||
|
oepr oẻp
|
||||||
|
Oepr Oẻp
|
||||||
|
oepx oẽp
|
||||||
|
Oepx Oẽp
|
||||||
|
oepj oẹp
|
||||||
|
Oepj Oẹp
|
||||||
|
oet oet
|
||||||
|
Oet Oet
|
||||||
|
oets oét
|
||||||
|
Oets Oét
|
||||||
|
oetf oèt
|
||||||
|
Oetf Oèt
|
||||||
|
oetr oẻt
|
||||||
|
Oetr Oẻt
|
||||||
|
oetx oẽt
|
||||||
|
Oetx Oẽt
|
||||||
|
oetj oẹt
|
||||||
|
Oetj Oẹt
|
||||||
|
oech oech
|
||||||
|
Oech Oech
|
||||||
|
oechs oéch
|
||||||
|
Oechs Oéch
|
||||||
|
oechf oèch
|
||||||
|
Oechf Oèch
|
||||||
|
oechr oẻch
|
||||||
|
Oechr Oẻch
|
||||||
|
oechx oẽch
|
||||||
|
Oechx Oẽch
|
||||||
|
oechj oẹch
|
||||||
|
Oechj Oẹch
|
||||||
|
oeng oeng
|
||||||
|
Oeng Oeng
|
||||||
|
oengs oéng
|
||||||
|
Oengs Oéng
|
||||||
|
oengf oèng
|
||||||
|
Oengf Oèng
|
||||||
|
oengr oẻng
|
||||||
|
Oengr Oẻng
|
||||||
|
oengx oẽng
|
||||||
|
Oengx Oẽng
|
||||||
|
oengj oẹng
|
||||||
|
Oengj Oẹng
|
||||||
|
oenh oenh
|
||||||
|
Oenh Oenh
|
||||||
|
oenhs oénh
|
||||||
|
Oenhs Oénh
|
||||||
|
oenhf oènh
|
||||||
|
Oenhf Oènh
|
||||||
|
oenhr oẻnh
|
||||||
|
Oenhr Oẻnh
|
||||||
|
oenhx oẽnh
|
||||||
|
Oenhx Oẽnh
|
||||||
|
oenhj oẹnh
|
||||||
|
Oenhj Oẹnh
|
||||||
oic oic
|
oic oic
|
||||||
Oic Oic
|
Oic Oic
|
||||||
oics óic
|
oics óic
|
||||||
@@ -3153,10 +3749,18 @@ quay quay
|
|||||||
Quay Quay
|
Quay Quay
|
||||||
giay giay
|
giay giay
|
||||||
Giay Giay
|
Giay Giay
|
||||||
|
queo queo
|
||||||
|
Queo Queo
|
||||||
|
gieo gieo
|
||||||
|
Gieo Gieo
|
||||||
queu queu
|
queu queu
|
||||||
Queu Queu
|
Queu Queu
|
||||||
gieu gieu
|
gieu gieu
|
||||||
Gieu Gieu
|
Gieu Gieu
|
||||||
|
quia quia
|
||||||
|
Quia Quia
|
||||||
|
giia giia
|
||||||
|
Giia Giia
|
||||||
quiu quiu
|
quiu quiu
|
||||||
Quiu Quiu
|
Quiu Quiu
|
||||||
giiu giiu
|
giiu giiu
|
||||||
@@ -3165,10 +3769,40 @@ quoi quoi
|
|||||||
Quoi Quoi
|
Quoi Quoi
|
||||||
gioi gioi
|
gioi gioi
|
||||||
Gioi Gioi
|
Gioi Gioi
|
||||||
|
giua giua
|
||||||
|
Giua Giua
|
||||||
giui giui
|
giui giui
|
||||||
Giui Giui
|
Giui Giui
|
||||||
giuy giuy
|
giuy giuy
|
||||||
Giuy Giuy
|
Giuy Giuy
|
||||||
|
quaau quâu
|
||||||
|
Quaau Quâu
|
||||||
|
giaau giâu
|
||||||
|
Giaau Giâu
|
||||||
|
quaay quây
|
||||||
|
Quaay Quây
|
||||||
|
giaay giây
|
||||||
|
Giaay Giây
|
||||||
|
queeu quêu
|
||||||
|
Queeu Quêu
|
||||||
|
gieeu giêu
|
||||||
|
Gieeu Giêu
|
||||||
|
quooi quôi
|
||||||
|
Quooi Quôi
|
||||||
|
giooi giôi
|
||||||
|
Giooi Giôi
|
||||||
|
quowi quơi
|
||||||
|
Quowi Quơi
|
||||||
|
giowi giơi
|
||||||
|
Giowi Giơi
|
||||||
|
giuwi giưi
|
||||||
|
Giuwi Giưi
|
||||||
|
giuwu giưu
|
||||||
|
Giuwu Giưu
|
||||||
|
giuwa giưa
|
||||||
|
Giuwa Giưa
|
||||||
|
giuee giuê
|
||||||
|
Giuee Giuê
|
||||||
quiee quiê
|
quiee quiê
|
||||||
Quiee Quiê
|
Quiee Quiê
|
||||||
giiee giiê
|
giiee giiê
|
||||||
@@ -3187,8 +3821,6 @@ quoaw quoă
|
|||||||
Quoaw Quoă
|
Quoaw Quoă
|
||||||
gioaw gioă
|
gioaw gioă
|
||||||
Gioaw Gioă
|
Gioaw Gioă
|
||||||
giuwa giưa
|
|
||||||
Giuwa Giưa
|
|
||||||
giuwow giươ
|
giuwow giươ
|
||||||
Giuwow Giươ
|
Giuwow Giươ
|
||||||
quie quie
|
quie quie
|
||||||
@@ -3201,8 +3833,6 @@ giye giye
|
|||||||
Giye Giye
|
Giye Giye
|
||||||
giuo giuo
|
giuo giuo
|
||||||
Giuo Giuo
|
Giuo Giuo
|
||||||
giua giua
|
|
||||||
Giua Giua
|
|
||||||
quieeu quiêu
|
quieeu quiêu
|
||||||
Quieeu Quiêu
|
Quieeu Quiêu
|
||||||
giieeu giiêu
|
giieeu giiêu
|
||||||
@@ -3215,6 +3845,14 @@ giuooi giuôi
|
|||||||
Giuooi Giuôi
|
Giuooi Giuôi
|
||||||
giuowi giươi
|
giuowi giươi
|
||||||
Giuowi Giươi
|
Giuowi Giươi
|
||||||
|
giuowu giươu
|
||||||
|
Giuowu Giươu
|
||||||
|
giuwowi giươi
|
||||||
|
Giuwowi Giươi
|
||||||
|
giuwowu giươu
|
||||||
|
Giuwowu Giươu
|
||||||
|
giuaay giuây
|
||||||
|
Giuaay Giuây
|
||||||
quoai quoai
|
quoai quoai
|
||||||
Quoai Quoai
|
Quoai Quoai
|
||||||
gioai gioai
|
gioai gioai
|
||||||
@@ -3223,10 +3861,16 @@ quoay quoay
|
|||||||
Quoay Quoay
|
Quoay Quoay
|
||||||
gioay gioay
|
gioay gioay
|
||||||
Gioay Gioay
|
Gioay Gioay
|
||||||
giuyee giuyê
|
quoeo quoeo
|
||||||
Giuyee Giuyê
|
Quoeo Quoeo
|
||||||
|
gioeo gioeo
|
||||||
|
Gioeo Gioeo
|
||||||
giuya giuya
|
giuya giuya
|
||||||
Giuya Giuya
|
Giuya Giuya
|
||||||
|
giuyu giuyu
|
||||||
|
Giuyu Giuyu
|
||||||
|
giuyee giuyê
|
||||||
|
Giuyee Giuyê
|
||||||
quaw quă
|
quaw quă
|
||||||
Quaw Quă
|
Quaw Quă
|
||||||
giaw giă
|
giaw giă
|
||||||
@@ -3719,6 +4363,38 @@ giuowng giương
|
|||||||
Giuowng Giương
|
Giuowng Giương
|
||||||
giuownh giươnh
|
giuownh giươnh
|
||||||
Giuownh Giươnh
|
Giuownh Giươnh
|
||||||
|
giuwowc giươc
|
||||||
|
Giuwowc Giươc
|
||||||
|
giuwowm giươm
|
||||||
|
Giuwowm Giươm
|
||||||
|
giuwown giươn
|
||||||
|
Giuwown Giươn
|
||||||
|
giuwowp giươp
|
||||||
|
Giuwowp Giươp
|
||||||
|
giuwowt giươt
|
||||||
|
Giuwowt Giươt
|
||||||
|
giuwowch giươch
|
||||||
|
Giuwowch Giươch
|
||||||
|
giuwowng giương
|
||||||
|
Giuwowng Giương
|
||||||
|
giuwownh giươnh
|
||||||
|
Giuwownh Giươnh
|
||||||
|
giuyeec giuyêc
|
||||||
|
Giuyeec Giuyêc
|
||||||
|
giuyeem giuyêm
|
||||||
|
Giuyeem Giuyêm
|
||||||
|
giuyeen giuyên
|
||||||
|
Giuyeen Giuyên
|
||||||
|
giuyeep giuyêp
|
||||||
|
Giuyeep Giuyêp
|
||||||
|
giuyeet giuyêt
|
||||||
|
Giuyeet Giuyêt
|
||||||
|
giuyeech giuyêch
|
||||||
|
Giuyeech Giuyêch
|
||||||
|
giuyeeng giuyêng
|
||||||
|
Giuyeeng Giuyêng
|
||||||
|
giuyeenh giuyênh
|
||||||
|
Giuyeenh Giuyênh
|
||||||
giuaac giuâc
|
giuaac giuâc
|
||||||
Giuaac Giuâc
|
Giuaac Giuâc
|
||||||
giuaam giuâm
|
giuaam giuâm
|
||||||
@@ -3735,6 +4411,22 @@ giuaang giuâng
|
|||||||
Giuaang Giuâng
|
Giuaang Giuâng
|
||||||
giuaanh giuânh
|
giuaanh giuânh
|
||||||
Giuaanh Giuânh
|
Giuaanh Giuânh
|
||||||
|
giueec giuêc
|
||||||
|
Giueec Giuêc
|
||||||
|
giueem giuêm
|
||||||
|
Giueem Giuêm
|
||||||
|
giueen giuên
|
||||||
|
Giueen Giuên
|
||||||
|
giueep giuêp
|
||||||
|
Giueep Giuêp
|
||||||
|
giueet giuêt
|
||||||
|
Giueet Giuêt
|
||||||
|
giueech giuêch
|
||||||
|
Giueech Giuêch
|
||||||
|
giueeng giuêng
|
||||||
|
Giueeng Giuêng
|
||||||
|
giueenh giuênh
|
||||||
|
Giueenh Giuênh
|
||||||
quoawc quoăc
|
quoawc quoăc
|
||||||
Quoawc Quoăc
|
Quoawc Quoăc
|
||||||
gioawc gioăc
|
gioawc gioăc
|
||||||
@@ -3927,6 +4619,38 @@ quoanh quoanh
|
|||||||
Quoanh Quoanh
|
Quoanh Quoanh
|
||||||
gioanh gioanh
|
gioanh gioanh
|
||||||
Gioanh Gioanh
|
Gioanh Gioanh
|
||||||
|
quoec quoec
|
||||||
|
Quoec Quoec
|
||||||
|
gioec gioec
|
||||||
|
Gioec Gioec
|
||||||
|
quoem quoem
|
||||||
|
Quoem Quoem
|
||||||
|
gioem gioem
|
||||||
|
Gioem Gioem
|
||||||
|
quoen quoen
|
||||||
|
Quoen Quoen
|
||||||
|
gioen gioen
|
||||||
|
Gioen Gioen
|
||||||
|
quoep quoep
|
||||||
|
Quoep Quoep
|
||||||
|
gioep gioep
|
||||||
|
Gioep Gioep
|
||||||
|
quoet quoet
|
||||||
|
Quoet Quoet
|
||||||
|
gioet gioet
|
||||||
|
Gioet Gioet
|
||||||
|
quoech quoech
|
||||||
|
Quoech Quoech
|
||||||
|
gioech gioech
|
||||||
|
Gioech Gioech
|
||||||
|
quoeng quoeng
|
||||||
|
Quoeng Quoeng
|
||||||
|
gioeng gioeng
|
||||||
|
Gioeng Gioeng
|
||||||
|
quoenh quoenh
|
||||||
|
Quoenh Quoenh
|
||||||
|
gioenh gioenh
|
||||||
|
Gioenh Gioenh
|
||||||
quoic quoic
|
quoic quoic
|
||||||
Quoic Quoic
|
Quoic Quoic
|
||||||
gioic gioic
|
gioic gioic
|
||||||
|
|||||||
268
popup_layout.c
268
popup_layout.c
@@ -1,33 +1,269 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
int
|
enum {
|
||||||
popupcells(Rune *r, int n)
|
Asciitofull = 0xFEE0,
|
||||||
{
|
};
|
||||||
int i, w;
|
|
||||||
|
int popupscale = 1;
|
||||||
|
|
||||||
|
static void
|
||||||
|
fill(u32int *buf, int n, u32int color)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
w = 0;
|
|
||||||
for(i = 0; i < n; i++)
|
for(i = 0; i < n; i++)
|
||||||
if(r[i] != 0xfe0e && r[i] != 0xfe0f)
|
buf[i] = color;
|
||||||
w++;
|
}
|
||||||
return w;
|
|
||||||
|
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
|
void
|
||||||
popupposition(Caret *caret, int pointerx, int pointery, int screenw,
|
popuparea(Area *mon, int nmon, Area *work, int x, int y, Area *out)
|
||||||
int screenh, int w, int h, int *x, int *y)
|
|
||||||
{
|
{
|
||||||
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){
|
if(caret->valid){
|
||||||
px = caret->x;
|
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{
|
}else{
|
||||||
px = (vlong)pointerx + 10;
|
px = (vlong)pointerx + 10;
|
||||||
py = (vlong)pointery + 10;
|
py = (vlong)pointery + 10;
|
||||||
|
if(py + h > bottom)
|
||||||
|
py = (vlong)pointery - 10 - h;
|
||||||
}
|
}
|
||||||
xmax = max((vlong)screenw - w, 0);
|
xmax = max(right - w, area->x);
|
||||||
ymax = max((vlong)screenh - h, 0);
|
ymax = max(bottom - h, area->y);
|
||||||
*x = max(0, min(px, xmax));
|
*x = max((vlong)area->x, min(px, xmax));
|
||||||
*y = max(0, min(py, ymax));
|
*y = max((vlong)area->y, min(py, ymax));
|
||||||
}
|
}
|
||||||
|
|||||||
33
run.sh
33
run.sh
@@ -2,48 +2,23 @@
|
|||||||
|
|
||||||
cd "$(dirname "$0")" || exit 1
|
cd "$(dirname "$0")" || exit 1
|
||||||
|
|
||||||
if test "$#" -eq 0; then
|
if test "$#" -ne 0; then
|
||||||
set --
|
echo "usage: run.sh" >&2
|
||||||
for font in \
|
exit 1
|
||||||
/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
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! test -x ./strans; then
|
if ! test -x ./strans; then
|
||||||
echo "run.sh: ./strans is not executable; run make first" >&2
|
echo "run.sh: ./strans is not executable; run make first" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
if ! command -v pkill >/dev/null 2>&1; then
|
||||||
echo "run.sh: pkill is required" >&2
|
echo "run.sh: pkill is required" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
uid=$(id -u) || exit 1
|
uid=$(id -u) || exit 1
|
||||||
pkill -TERM -u "$uid" -x strans-xim 2>/dev/null || :
|
|
||||||
pkill -TERM -u "$uid" -x strans 2>/dev/null || :
|
pkill -TERM -u "$uid" -x strans 2>/dev/null || :
|
||||||
sleep 0.1
|
sleep 0.1
|
||||||
pkill -KILL -u "$uid" -x strans-xim 2>/dev/null || :
|
|
||||||
pkill -KILL -u "$uid" -x strans 2>/dev/null || :
|
pkill -KILL -u "$uid" -x strans 2>/dev/null || :
|
||||||
|
|
||||||
./strans map "$@" </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
|
|
||||||
|
|||||||
122
srv.c
122
srv.c
@@ -5,56 +5,108 @@
|
|||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
|
|
||||||
static char adir[256];
|
static char adir[256];
|
||||||
|
static char sockpath[sizeof(((struct sockaddr_un*)0)->sun_path)];
|
||||||
|
static dev_t sockdev;
|
||||||
|
static ino_t sockino;
|
||||||
static Channel *clientc;
|
static Channel *clientc;
|
||||||
|
|
||||||
static int
|
/* Removes the socket only while it is still the one we announced. */
|
||||||
srvreadkey(int fd, Keyreq *kr)
|
static void
|
||||||
|
srvunlink(void)
|
||||||
{
|
{
|
||||||
uchar req[Ipcreqsz];
|
struct stat st;
|
||||||
u32int ks, mod;
|
|
||||||
int want;
|
|
||||||
|
|
||||||
if(ipcreadn(fd, req, sizeof req) < 0)
|
if(sockpath[0] != '\0' && lstat(sockpath, &st) == 0 &&
|
||||||
return -1;
|
st.st_dev == sockdev && st.st_ino == sockino)
|
||||||
ipcunpackreq(req, &want, &mod, &ks);
|
unlink(sockpath);
|
||||||
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
|
sockpath[0] = '\0';
|
||||||
kr->ks = ks;
|
|
||||||
kr->mod = mod;
|
|
||||||
memset(&kr->caret, 0, sizeof kr->caret);
|
|
||||||
return want;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
static void
|
||||||
clientthread(void *arg)
|
clientthread(void *arg)
|
||||||
{
|
{
|
||||||
Channel *reply;
|
|
||||||
int fd;
|
|
||||||
Keyreq kr;
|
Keyreq kr;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
uchar out[Ipcmaxresp];
|
uchar out[Ipcmaxresp], token;
|
||||||
char commit[Maxutf], preedit[Maxutf];
|
char commit[Maxutf], preedit[Maxutf];
|
||||||
int n, ncommit, npreedit, want;
|
int fd, n, ncommit, npreedit, want;
|
||||||
uchar token;
|
|
||||||
|
|
||||||
fd = (int)(uintptr)arg;
|
fd = (int)(uintptr)arg;
|
||||||
threadsetname("client %d", fd);
|
threadsetname("client %d", fd);
|
||||||
reply = chancreate(sizeof(Keyres), 0);
|
memset(&kr, 0, sizeof kr);
|
||||||
kr.reply = reply;
|
kr.reply = chancreate(sizeof(Keyres), 0);
|
||||||
kr.owner = &fd;
|
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);
|
chansend(keyc, &kr);
|
||||||
chanrecv(reply, &res);
|
chanrecv(kr.reply, &res);
|
||||||
|
if(kr.op == Keycaret)
|
||||||
|
continue;
|
||||||
ncommit = stoutf(&res.commit, commit, sizeof commit);
|
ncommit = stoutf(&res.commit, commit, sizeof commit);
|
||||||
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
|
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
|
||||||
n = ipcpackresp(out, sizeof out, res.eaten,
|
/* A capability reply is always eaten: it marks the extension. */
|
||||||
|
n = ipcpackresp(out, sizeof out, kr.op == Keycap || res.eaten,
|
||||||
commit, ncommit, preedit, npreedit, want);
|
commit, ncommit, preedit, npreedit, want);
|
||||||
if(n < 0 || ipcsend(fd, out, n) < 0)
|
if(n < 0 || ipcsend(fd, out, n) < 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
kr.op = Keyrelease;
|
kr.op = Keyrelease;
|
||||||
|
kr.ks = 0;
|
||||||
|
kr.mod = 0;
|
||||||
chansend(keyc, &kr);
|
chansend(keyc, &kr);
|
||||||
chanrecv(reply, &res);
|
chanrecv(kr.reply, &res);
|
||||||
chanfree(reply);
|
chanfree(kr.reply);
|
||||||
close(fd);
|
close(fd);
|
||||||
chanrecv(clientc, &token);
|
chanrecv(clientc, &token);
|
||||||
}
|
}
|
||||||
@@ -62,18 +114,23 @@ clientthread(void *arg)
|
|||||||
void
|
void
|
||||||
srvinit(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");
|
die("IPC path is too long");
|
||||||
addr = smprint("unix!%s", path);
|
addr = smprint("unix!%s", sockpath);
|
||||||
if(addr == nil)
|
if(addr == nil)
|
||||||
die("out of memory");
|
die("out of memory");
|
||||||
if(announce(addr, adir) < 0)
|
if(announce(addr, adir) < 0)
|
||||||
die("IPC endpoint is already in use: %r");
|
die("IPC endpoint is already in use: %r");
|
||||||
free(addr);
|
free(addr);
|
||||||
if(chmod(path, 0600) < 0)
|
if(chmod(sockpath, 0600) < 0 || lstat(sockpath, &st) < 0)
|
||||||
die("can't protect IPC endpoint: %s", path);
|
die("can't protect IPC endpoint: %s", sockpath);
|
||||||
|
sockdev = st.st_dev;
|
||||||
|
sockino = st.st_ino;
|
||||||
|
atexit(srvunlink);
|
||||||
|
threadnotify(srvnote, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -94,9 +151,6 @@ srvthread(void*)
|
|||||||
close(fd);
|
close(fd);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(proccreate(clientthread, (void*)(uintptr)fd, 8192) < 0){
|
proccreate(clientthread, (void*)(uintptr)fd, 8192);
|
||||||
chanrecv(clientc, &token);
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
str.c
26
str.c
@@ -1,24 +1,27 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
void
|
/* Fills s from n bytes of UTF-8; whole, valid, and at most Maxrunes. */
|
||||||
|
int
|
||||||
sinit(Str *s, char *src, int n)
|
sinit(Str *s, char *src, int n)
|
||||||
{
|
{
|
||||||
|
Str tmp = {0};
|
||||||
|
Rune r;
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
s->n = 0;
|
s->n = 0;
|
||||||
if(n < 0 || (n > 0 && src == nil))
|
while(n > 0){
|
||||||
return;
|
if(tmp.n >= Maxrunes || !fullrune(src, n))
|
||||||
while(n > 0 && s->n < Maxrunes){
|
return 0;
|
||||||
if(!fullrune(src, n))
|
len = chartorune(&r, src);
|
||||||
break;
|
if((r == Runeerror && len == 1) || (r >= 0xd800 && r <= 0xdfff))
|
||||||
len = chartorune(&s->r[s->n], src);
|
return 0;
|
||||||
if(len > n)
|
tmp.r[tmp.n++] = r;
|
||||||
break;
|
|
||||||
s->n++;
|
|
||||||
src += len;
|
src += len;
|
||||||
n -= len;
|
n -= len;
|
||||||
}
|
}
|
||||||
|
*s = tmp;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -66,14 +69,13 @@ scmp(Str *a, Str *b)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* UTF-8 of s into buf[sz], NUL-terminated, whole runes only. */
|
||||||
int
|
int
|
||||||
stoutf(Str *s, char *buf, int sz)
|
stoutf(Str *s, char *buf, int sz)
|
||||||
{
|
{
|
||||||
char tmp[UTFmax];
|
char tmp[UTFmax];
|
||||||
int i, n, len;
|
int i, n, len;
|
||||||
|
|
||||||
if(sz <= 0)
|
|
||||||
return 0;
|
|
||||||
n = 0;
|
n = 0;
|
||||||
for(i = 0; i < s->n; i++){
|
for(i = 0; i < s->n; i++){
|
||||||
len = runetochar(tmp, &s->r[i]);
|
len = runetochar(tmp, &s->r[i]);
|
||||||
|
|||||||
151
tests/Makefile
151
tests/Makefile
@@ -1,71 +1,144 @@
|
|||||||
CC = 9c
|
CC = 9c
|
||||||
LD = 9l
|
LD = 9l
|
||||||
HOSTCC = cc
|
HOSTCC = cc
|
||||||
CFLAGS = -std=c99 -Wall -Wextra -O2 -g -I.. -I../cutest
|
PKG_CONFIG ?= pkg-config
|
||||||
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
|
CFLAGS ?= -O2 -g
|
||||||
FT_LIBS = $(shell pkg-config --libs freetype2)
|
UNIT_CPPFLAGS = -I.. -I../cutest
|
||||||
DBUS_CFLAGS = $(shell pkg-config --cflags dbus-1)
|
UNIT_CFLAGS = -std=c99 -Wall -Wextra
|
||||||
DBUS_LIBS = $(shell pkg-config --libs dbus-1)
|
HOST_CFLAGS = -std=c99 -Wall -Wextra
|
||||||
IBUS_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
|
TEXT_CFLAGS = $(shell $(PKG_CONFIG) --cflags pangocairo cairo fontconfig)
|
||||||
IBUS_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
|
TEXT_LIBS = $(shell $(PKG_CONFIG) --libs pangocairo cairo fontconfig)
|
||||||
LIBS = -lthread -lbio $(FT_LIBS) $(IBUS_LIBS)
|
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
|
PROG = unit_test
|
||||||
LIVE = ibus_live_test ipc_live_test daemon_collision_test daemon_failure_test \
|
STRESS = stress_test
|
||||||
daemon_restart_test
|
LIVESRC = live.c live.h
|
||||||
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
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 \
|
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)
|
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
|
font.c
|
||||||
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
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)
|
check test: $(PROG)
|
||||||
./$(PROG) $(TESTARGS)
|
./$(PROG) $(UNITARGS)
|
||||||
./ibus_live_test ../strans ../map
|
|
||||||
|
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
|
./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_collision_test ../strans ../map
|
||||||
./daemon_failure_test ../strans
|
./daemon_failure_test ../strans
|
||||||
./daemon_restart_test ../strans ../map
|
./daemon_restart_test ../strans ../map
|
||||||
|
|
||||||
$(PROG): $(OBJS)
|
$(PROG): unit_test.o $(COMMONOBJ)
|
||||||
$(LD) -o $@ $(OBJS) $(LIBS)
|
$(LD) $(LDFLAGS) -o $@ unit_test.o $(COMMONOBJ) $(UNIT_LDLIBS) \
|
||||||
|
$(LDLIBS)
|
||||||
|
|
||||||
ibus_live_test: ibus_live_test.c
|
$(STRESS): stress_test.o $(COMMONOBJ)
|
||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g $(DBUS_CFLAGS) -o $@ $< $(DBUS_LIBS)
|
$(LD) $(LDFLAGS) -o $@ stress_test.o $(COMMONOBJ) $(UNIT_LDLIBS) \
|
||||||
|
$(LDLIBS)
|
||||||
|
|
||||||
ipc_live_test: ipc_live_test.c ../ipc.c ../ipc.h
|
stress_test.o: unit_test.c
|
||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. -o $@ ipc_live_test.c ../ipc.c
|
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -DSTRESS \
|
||||||
|
-c -o $@ $<
|
||||||
|
|
||||||
daemon_collision_test: daemon_collision_test.c ../ipc.c ../ipc.h
|
ibus_live_test: ibus_live_test.c $(LIVESRC) $(LIVEBUSSRC) ../ipc.c ../ipc.h
|
||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(DBUS_CFLAGS) -o $@ \
|
$(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
||||||
daemon_collision_test.c ../ipc.c $(DBUS_LIBS)
|
$(LDFLAGS) -o $@ ibus_live_test.c live.c livebus.c ../ipc.c \
|
||||||
|
$(DBUS_LIBS) $(LDLIBS)
|
||||||
|
|
||||||
daemon_failure_test: daemon_failure_test.c
|
ibus_client_smoke: ibus_client_smoke.c
|
||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -o $@ $<
|
$(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
|
gtk_live_test: gtk_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(DBUS_CFLAGS) -o $@ \
|
$(HOSTCC) $(CPPFLAGS) -I.. $(GTK_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
||||||
daemon_restart_test.c ../ipc.c $(DBUS_LIBS)
|
$(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
|
engine_test.o: ../strans.c
|
||||||
ibus_test.o: CFLAGS += $(IBUS_CFLAGS)
|
ibus_test.o: UNIT_CPPFLAGS += $(IBUS_CFLAGS)
|
||||||
ibus_test.o: ../ibus.c
|
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
|
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
|
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJS) $(PROG) $(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
|
#define _GNU_SOURCE
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <signal.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/inotify.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "live.h"
|
||||||
|
|
||||||
enum
|
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
|
static int
|
||||||
fail(char *fmt, ...)
|
waitfailed(Daemon *d, char *badmap)
|
||||||
{
|
{
|
||||||
va_list ap;
|
struct pollfd pfd;
|
||||||
|
int n, status;
|
||||||
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;
|
|
||||||
int64_t deadline;
|
int64_t deadline;
|
||||||
|
|
||||||
pid = t->child;
|
status = 0;
|
||||||
ok = 1;
|
deadline = nowms() + Failtimeout;
|
||||||
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;
|
|
||||||
for(;;){
|
for(;;){
|
||||||
do
|
n = waitpid(d->pid, &status, WNOHANG);
|
||||||
n = waitpid(pid, &status, WNOHANG);
|
if(n == d->pid){
|
||||||
while(n < 0 && errno == EINTR);
|
d->pid = -1;
|
||||||
if(n == pid){
|
readerrors(d);
|
||||||
t->child = -1;
|
|
||||||
readerrors(t);
|
|
||||||
if(!drainnotify(t))
|
|
||||||
ok = 0;
|
|
||||||
break;
|
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)
|
if(n < 0 && errno == EINTR)
|
||||||
continue;
|
continue;
|
||||||
if(n < 0){
|
if(n < 0){
|
||||||
fail("poll failed daemon: %s", strerror(errno));
|
if(errno == ECHILD)
|
||||||
killowned(t, &status);
|
d->pid = -1;
|
||||||
readerrors(t);
|
return fail("waitpid: %s", strerror(errno));
|
||||||
drainnotify(t);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
if(pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL)){
|
n = leftms(deadline);
|
||||||
fail("endpoint watch became unusable: %#x", pfd[0].revents);
|
if(n == 0)
|
||||||
killowned(t, &status);
|
return fail("daemon did not exit after map initialization failure");
|
||||||
readerrors(t);
|
pfd.fd = d->errfd;
|
||||||
drainnotify(t);
|
pfd.events = POLLIN|POLLHUP;
|
||||||
return 0;
|
pfd.revents = 0;
|
||||||
}
|
n = poll(&pfd, 1, n);
|
||||||
if(pfd[1].revents & (POLLERR|POLLNVAL)){
|
if(n < 0 && errno == EINTR)
|
||||||
fail("daemon stderr pipe became unusable: %#x",
|
continue;
|
||||||
pfd[1].revents);
|
if(n < 0)
|
||||||
killowned(t, &status);
|
return fail("poll daemon: %s", strerror(errno));
|
||||||
readerrors(t);
|
if(n > 0)
|
||||||
drainnotify(t);
|
readerrors(d);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if((pfd[0].revents & POLLIN) && !drainnotify(t))
|
|
||||||
ok = 0;
|
|
||||||
if(pfd[1].revents & (POLLIN|POLLHUP))
|
|
||||||
readerrors(t);
|
|
||||||
}
|
}
|
||||||
if(!WIFEXITED(status) || WEXITSTATUS(status) != 1)
|
if(!WIFEXITED(status) || WEXITSTATUS(status) == 0)
|
||||||
return fail("daemon map failure had wait status %#x", status);
|
return fail("daemon map failure had wait status %#x", status);
|
||||||
if(strcmp(t->err, t->expected) != 0)
|
if(strstr(d->err, "can't open") == NULL || strstr(d->err, badmap) == NULL)
|
||||||
return fail("daemon did not report the expected map initialization failure");
|
return fail("daemon did not report the missing map directory");
|
||||||
return ok && t->endpointclean;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
dirempty(char *path, char *which)
|
emptydir(char *path)
|
||||||
{
|
{
|
||||||
DIR *dir;
|
DIR *dir;
|
||||||
struct dirent *de;
|
struct dirent *de;
|
||||||
int count, ok;
|
int empty;
|
||||||
|
|
||||||
dir = opendir(path);
|
dir = opendir(path);
|
||||||
if(dir == NULL)
|
if(dir == NULL)
|
||||||
return fail("open %s directory %s: %s", which, path, strerror(errno));
|
return fail("open %s: %s", path, strerror(errno));
|
||||||
count = 0;
|
empty = 1;
|
||||||
ok = 1;
|
|
||||||
errno = 0;
|
errno = 0;
|
||||||
while((de = readdir(dir)) != NULL){
|
while((de = readdir(dir)) != NULL)
|
||||||
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
if(strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0){
|
||||||
continue;
|
fail("unexpected endpoint %s/%s", path, de->d_name);
|
||||||
count++;
|
empty = 0;
|
||||||
fail("unexpected %s directory entry %s", which, de->d_name);
|
}
|
||||||
ok = 0;
|
|
||||||
errno = 0;
|
|
||||||
}
|
|
||||||
if(errno != 0){
|
if(errno != 0){
|
||||||
fail("read %s directory %s: %s", which, path, strerror(errno));
|
fail("read %s: %s", path, strerror(errno));
|
||||||
ok = 0;
|
empty = 0;
|
||||||
}
|
}
|
||||||
if(closedir(dir) < 0){
|
if(closedir(dir) < 0){
|
||||||
fail("close %s directory %s: %s", which, path, strerror(errno));
|
fail("close %s: %s", path, strerror(errno));
|
||||||
ok = 0;
|
empty = 0;
|
||||||
}
|
}
|
||||||
if(count != 0)
|
return empty;
|
||||||
ok = 0;
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
checkendpoints(Test *t)
|
checkendpoints(Live *l)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
int ok;
|
|
||||||
|
|
||||||
ok = 1;
|
if(lstat(l->socket, &st) == 0)
|
||||||
errno = 0;
|
return fail("IPC endpoint exists after failed startup: %s", l->socket);
|
||||||
if(lstat(t->socket, &st) == 0){
|
if(errno != ENOENT)
|
||||||
fail("IPC endpoint exists after failed startup: %s", t->socket);
|
return fail("lstat IPC endpoint: %s", strerror(errno));
|
||||||
ok = 0;
|
return emptydir(l->runtime) && emptydir(l->bus);
|
||||||
}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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
Test test;
|
Daemon daemon;
|
||||||
|
Live live;
|
||||||
|
char badmap[320];
|
||||||
int ok;
|
int ok;
|
||||||
|
|
||||||
|
testname = "daemon_failure_test";
|
||||||
if(argc != 2){
|
if(argc != 2){
|
||||||
fprintf(stderr, "usage: daemon_failure_test strans\n");
|
fprintf(stderr, "usage: daemon_failure_test strans\n");
|
||||||
return 2;
|
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)
|
if(ok)
|
||||||
ok = startchild(&test, argv[1]);
|
ok = startdaemon(&live, &daemon, argv[1], badmap);
|
||||||
if(ok){
|
if(ok)
|
||||||
ok = waitfailed(&test);
|
ok = waitfailed(&daemon, badmap) && checkendpoints(&live);
|
||||||
if(test.child <= 0 && !checkendpoints(&test))
|
if(!killdaemon(&daemon, NULL))
|
||||||
ok = 0;
|
ok = 0;
|
||||||
}
|
if(!closeerrors(&daemon))
|
||||||
if(!cleanup(&test))
|
ok = 0;
|
||||||
|
if(!liveclean(&live))
|
||||||
ok = 0;
|
ok = 0;
|
||||||
if(!ok){
|
if(!ok){
|
||||||
showerrors(&test);
|
showerrors(&daemon);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
printf("failed daemon startup published no endpoints: ok\n");
|
printf("failed daemon startup leaves no endpoints: ok\n");
|
||||||
return 0;
|
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"
|
#include "test.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
dictionary_candidates(struct ct *t)
|
dictionary_candidates(struct ct *t)
|
||||||
{
|
{
|
||||||
char many[512], item[8];
|
char many[8*(Maxkouho+1)], item[8];
|
||||||
char *p;
|
char *p;
|
||||||
Dictreq req;
|
Str kouho[Maxkouho], key;
|
||||||
Dictres res;
|
Trie *saved;
|
||||||
Hmap *saved;
|
|
||||||
Lang *lang;
|
Lang *lang;
|
||||||
Str key;
|
int i, n;
|
||||||
int i;
|
|
||||||
|
|
||||||
lang = getlang(LangJP);
|
lang = getlang(LangJP);
|
||||||
saved = lang->dict;
|
saved = lang->dict;
|
||||||
lang->dict = hmapalloc(1);
|
lang->dict = trienew();
|
||||||
key = mkstr("かな");
|
trieput(lang->dict, "かな", strlen("かな"), " 候補1 かな 候補2 ",
|
||||||
hmapset(&lang->dict, &key, " 候補1 かな 候補2 ",
|
|
||||||
strlen(" 候補1 かな 候補2 "));
|
strlen(" 候補1 かな 候補2 "));
|
||||||
memset(&req, 0, sizeof req);
|
key = mkstr("かな");
|
||||||
req.key = key;
|
n = dictlookup(lang, &key, kouho, Maxkouho);
|
||||||
req.pre = mkstr("preedit-one");
|
if(CT_EQ_INT(t, 2, n)){
|
||||||
req.lang = LangJP;
|
checkstr(t, "candidate 1", "候補1", &kouho[0]);
|
||||||
dictlookup(&req, &res);
|
checkstr(t, "candidate 2", "候補2", &kouho[1]);
|
||||||
if(!CT_EQ_INT(t, 2, res.nkouho))
|
}
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_INT(t, LangJP, res.lang);
|
|
||||||
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
|
|
||||||
checkstr(t, "candidate 1", "候補1", &res.kouho[0]);
|
|
||||||
checkstr(t, "candidate 2", "候補2", &res.kouho[1]);
|
|
||||||
key = mkstr("key");
|
|
||||||
p = many;
|
p = many;
|
||||||
for(i = 0; i < Maxkouho+1; i++){
|
for(i = 0; i < Maxkouho+1; i++){
|
||||||
snprint(item, sizeof item, "c%02d", i);
|
snprint(item, sizeof item, "c%02d", i);
|
||||||
@@ -39,78 +32,52 @@ dictionary_candidates(struct ct *t)
|
|||||||
p += strlen(item);
|
p += strlen(item);
|
||||||
}
|
}
|
||||||
*p = '\0';
|
*p = '\0';
|
||||||
hmapset(&lang->dict, &key, many, strlen(many));
|
trieput(lang->dict, "key", 3, many, strlen(many));
|
||||||
req.key = key;
|
key = mkstr("key");
|
||||||
req.pre = mkstr("preedit-two");
|
n = dictlookup(lang, &key, kouho, Maxkouho);
|
||||||
dictlookup(&req, &res);
|
if(CT_EQ_INT(t, Maxkouho, n)){
|
||||||
if(!CT_EQ_INT(t, Maxkouho, res.nkouho))
|
checkstr(t, "first capped candidate", "c00", &kouho[0]);
|
||||||
goto cleanup;
|
snprint(item, sizeof item, "c%02d", Maxkouho-1);
|
||||||
CT_EQ_INT(t, LangJP, res.lang);
|
checkstr(t, "last capped candidate", item, &kouho[Maxkouho-1]);
|
||||||
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
|
}
|
||||||
checkstr(t, "first capped candidate", "c00", &res.kouho[0]);
|
CT_EQ_INT(t, 3, dictlookup(lang, &key, kouho, 3));
|
||||||
checkstr(t, "last capped candidate", "c31", &res.kouho[31]);
|
trieclose(lang->dict);
|
||||||
cleanup:
|
|
||||||
hmapfree(lang->dict);
|
|
||||||
lang->dict = saved;
|
lang->dict = saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
dictionary_misses_clear_result(struct ct *t)
|
dictionary_misses(struct ct *t)
|
||||||
{
|
{
|
||||||
static const struct {
|
Str kouho[Maxkouho], key;
|
||||||
char *name;
|
Trie *saved;
|
||||||
char *key;
|
|
||||||
char *pre;
|
|
||||||
} cases[] = {
|
|
||||||
{ "empty key", "", "empty-preedit" },
|
|
||||||
{ "missing key", "missing", "missing-preedit" },
|
|
||||||
};
|
|
||||||
Dictreq req;
|
|
||||||
Dictres res;
|
|
||||||
Hmap *saved;
|
|
||||||
Lang *lang;
|
Lang *lang;
|
||||||
int i;
|
|
||||||
|
|
||||||
lang = getlang(LangJP);
|
lang = getlang(LangJP);
|
||||||
saved = lang->dict;
|
saved = lang->dict;
|
||||||
lang->dict = hmapalloc(1);
|
lang->dict = trienew();
|
||||||
for(i = 0; i < nelem(cases); i++){
|
key = mkstr("");
|
||||||
memset(&res, 0xa5, sizeof res);
|
CT_EQ_INT(t, 0, dictlookup(lang, &key, kouho, Maxkouho));
|
||||||
req.key = mkstr(cases[i].key);
|
key = mkstr("missing");
|
||||||
req.pre = mkstr(cases[i].pre);
|
CT_EQ_INT(t, 0, dictlookup(lang, &key, kouho, Maxkouho));
|
||||||
req.lang = LangJP;
|
CT_EQ_INT(t, 0, dictlookup(getlang(LangKO), &key, kouho, Maxkouho));
|
||||||
dictlookup(&req, &res);
|
trieclose(lang->dict);
|
||||||
if(res.nkouho != 0)
|
|
||||||
CT_ERRORF(t, "%s: want 0 candidates, got %d",
|
|
||||||
cases[i].name, res.nkouho);
|
|
||||||
CT_EQ_INT(t, LangJP, res.lang);
|
|
||||||
checkstr(t, cases[i].name, cases[i].pre, &res.key);
|
|
||||||
}
|
|
||||||
hmapfree(lang->dict);
|
|
||||||
lang->dict = saved;
|
lang->dict = saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
dictionary_emoji_identity(struct ct *t)
|
dictionary_emoji_identity(struct ct *t)
|
||||||
{
|
{
|
||||||
Dictreq req;
|
Str kouho[Maxkouho], key;
|
||||||
Dictres res;
|
Trie *saved;
|
||||||
Hmap *saved;
|
|
||||||
Lang *lang;
|
Lang *lang;
|
||||||
Str key;
|
|
||||||
|
|
||||||
lang = getlang(LangEMOJI);
|
lang = getlang(LangEMOJI);
|
||||||
saved = lang->dict;
|
saved = lang->dict;
|
||||||
lang->dict = hmapalloc(2);
|
lang->dict = trienew();
|
||||||
|
trieput(lang->dict, "é", strlen("é"), "é", strlen("é"));
|
||||||
key = mkstr("é");
|
key = mkstr("é");
|
||||||
hmapset(&lang->dict, &key, "é", strlen("é"));
|
if(CT_EQ_INT(t, 1, dictlookup(lang, &key, kouho, Maxkouho)))
|
||||||
memset(&req, 0, sizeof req);
|
checkstr(t, "emoji identity candidate", "é", &kouho[0]);
|
||||||
req.key = key;
|
trieclose(lang->dict);
|
||||||
req.pre = key;
|
|
||||||
req.lang = LangEMOJI;
|
|
||||||
dictlookup(&req, &res);
|
|
||||||
if(CT_EQ_INT(t, 1, res.nkouho))
|
|
||||||
checkstr(t, "emoji identity candidate", "é", &res.kouho[0]);
|
|
||||||
hmapfree(lang->dict);
|
|
||||||
lang->dict = saved;
|
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"
|
#include "test.h"
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Guard = 8,
|
Guard = 8,
|
||||||
Testw = 3 * Fontsz,
|
Rgbmask = 0xffffff,
|
||||||
Testh = 3 * Fontsz,
|
|
||||||
Testn = Testw * Testh,
|
|
||||||
Missingn = Fontsz * Fontsz,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define Testw (6 * Fontsz)
|
||||||
|
#define Testh (3 * Fontsz)
|
||||||
|
#define Testn (Testw * Testh)
|
||||||
|
|
||||||
static u32int guardcolor = 0x5a5a5a5a;
|
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
|
static void
|
||||||
fillpixels(u32int *p, int n, u32int color)
|
fillpixels(u32int *p, int n, u32int color)
|
||||||
@@ -29,38 +24,84 @@ fillpixels(u32int *p, int n, u32int color)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
hasink(u32int *p, int n)
|
hasink(u32int *p, int n, u32int bg)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
bg &= Rgbmask;
|
||||||
for(i = 0; i < n; i++)
|
for(i = 0; i < n; i++)
|
||||||
if(p[i] != Colbg)
|
if((p[i] & Rgbmask) != bg)
|
||||||
return 1;
|
return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
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);
|
bg &= Rgbmask;
|
||||||
putfont(buf, Fontsz, Fontsz, 0, 0, r);
|
first = 0;
|
||||||
return hasink(buf, nelem(buf));
|
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
|
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;
|
int x, y;
|
||||||
|
|
||||||
|
bg &= Rgbmask;
|
||||||
*minx = Testw;
|
*minx = Testw;
|
||||||
*miny = Testh;
|
*miny = Testh;
|
||||||
*maxx = -1;
|
*maxx = -1;
|
||||||
*maxy = -1;
|
*maxy = -1;
|
||||||
for(y = 0; y < Testh; y++){
|
for(y = 0; y < Testh; y++){
|
||||||
for(x = 0; x < Testw; x++){
|
for(x = 0; x < Testw; x++){
|
||||||
if(p[y * Testw + x] == Colbg)
|
if((p[y * Testw + x] & Rgbmask) == bg)
|
||||||
continue;
|
continue;
|
||||||
*minx = min(*minx, x);
|
*minx = min(*minx, x);
|
||||||
*miny = min(*miny, y);
|
*miny = min(*miny, y);
|
||||||
@@ -72,86 +113,150 @@ inkbounds(u32int *p, int *minx, int *miny, int *maxx, int *maxy)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
checkclip(struct ct *t, u32int *ref, int dx, int dy)
|
checktext(struct ct *t, u32int *buf, char *utf)
|
||||||
{
|
{
|
||||||
u32int *buf, *mem, *want;
|
Str s;
|
||||||
int i, sx, sy, x, y;
|
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;
|
buf = mem + Guard;
|
||||||
fillpixels(buf, Testn, Colbg);
|
fillpixels(buf, Testn, Colbg);
|
||||||
fillpixels(want, Testn, Colbg);
|
textdraw(buf, Testw, Testh, x, y, Testw, Colfg, s);
|
||||||
for(y = 0; y < Testh; y++){
|
CT_CHECK(t, hasink(buf, Testn, Colbg));
|
||||||
for(x = 0; x < Testw; x++){
|
checkoutside(t, buf, Colbg, y);
|
||||||
sx = x - dx;
|
checkguards(t, mem);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
font_render(struct ct *t)
|
font_render(struct ct *t)
|
||||||
{
|
{
|
||||||
u32int *buf, *missing, *ref;
|
static char *plain[] = { "A", "가", "あ", "漢", "☆", "𠀋" };
|
||||||
int i, minx, miny, maxx, maxy;
|
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]);
|
mem = emalloc((Testn + 2 * Guard) * sizeof mem[0]);
|
||||||
fillpixels(missing, Missingn + 2 * Guard, guardcolor);
|
fillpixels(mem, Testn + 2 * Guard, guardcolor);
|
||||||
buf = missing + Guard;
|
buf = mem + Guard;
|
||||||
fillpixels(buf, Missingn, Colbg);
|
fillpixels(buf, Testn, Colbg);
|
||||||
CT_CHECK(t, !fontinit(missingfonts, nelem(missingfonts)));
|
a = mkstr("A");
|
||||||
putfont(buf, Fontsz, Fontsz, 0, 0, 'A');
|
if(!CT_CHECK(t, textinit())){
|
||||||
for(i = 0; i < Missingn; i++)
|
free(mem);
|
||||||
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);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CT_CHECK(t, renders('A'));
|
for(i = 0; i < nelem(plain); i++)
|
||||||
CT_CHECK(t, renders(0xac00));
|
checktext(t, buf, plain[i]);
|
||||||
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);
|
|
||||||
|
|
||||||
ref = emalloc(Testn * sizeof ref[0]);
|
checkcolor(t, buf, "😀");
|
||||||
fillpixels(ref, Testn, Colbg);
|
checkcolor(t, buf, "❤️");
|
||||||
putfont(ref, Testw, Testh, Fontsz, Fontsz, 'A');
|
checkcolor(t, buf, "☕️");
|
||||||
if(!CT_CHECK(t, inkbounds(ref, &minx, &miny, &maxx, &maxy))){
|
checkshape(t, buf, "👩💻");
|
||||||
free(ref);
|
checkshape(t, buf, "👍🏽");
|
||||||
return;
|
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);
|
CT_EQ_INT(t, natural, textwidth(&longrow));
|
||||||
checkclip(t, ref, Testw - maxx, 0);
|
|
||||||
checkclip(t, ref, 0, -miny - 1);
|
fillpixels(buf, Testn, Colsel);
|
||||||
checkclip(t, ref, 0, Testh - maxy);
|
textdraw(buf, Testw, Testh, 2*Fontsz, Fontsz, 2*Fontsz, Colselfg,
|
||||||
free(ref);
|
&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 "ibus.c"
|
||||||
#include "../cutest/cutest.h"
|
#include "test.h"
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
void testengineinit(int);
|
|
||||||
void testenginehandle(Keyreq*);
|
|
||||||
void* testengineowner(void);
|
|
||||||
void testenginepreedit(Str*);
|
|
||||||
void testenginecaret(Caret*);
|
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
@@ -18,20 +13,17 @@ typedef struct Ibusfix Ibusfix;
|
|||||||
struct Ibusfix
|
struct Ibusfix
|
||||||
{
|
{
|
||||||
Channel *oldreply;
|
Channel *oldreply;
|
||||||
Channel *trace;
|
Pump pump;
|
||||||
Channel *stop;
|
|
||||||
Channel *done;
|
|
||||||
DBusConnection *c1;
|
DBusConnection *c1;
|
||||||
DBusConnection *c2;
|
DBusConnection *c2;
|
||||||
int pumpactive;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
ibus_machine_id_fallback(struct ct *t)
|
ibus_machine_id_fallback(struct ct *t)
|
||||||
{
|
{
|
||||||
char root[] = "/tmp/strans-machine-id.XXXXXX";
|
char root[] = "/tmp/strans-machine-id.XXXXXX";
|
||||||
char primary[128], fallback[128], got[64];
|
char primary[128], fallback[128], override[128], got[512];
|
||||||
char *path[2];
|
char *path[2], *old, *saved;
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
int madeprimary, madefallback;
|
int madeprimary, madefallback;
|
||||||
|
|
||||||
@@ -61,6 +53,24 @@ ibus_machine_id_fallback(struct ct *t)
|
|||||||
path[1] = fallback;
|
path[1] = fallback;
|
||||||
machineidfiles(got, sizeof got, path, nelem(path));
|
machineidfiles(got, sizeof got, path, nelem(path));
|
||||||
CT_EQ_STR(t, "fallback-machine-id", got);
|
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:
|
cleanup:
|
||||||
if(madefallback)
|
if(madefallback)
|
||||||
CT_CHECK(t, unlink(fallback) == 0);
|
CT_CHECK(t, unlink(fallback) == 0);
|
||||||
@@ -85,6 +95,8 @@ ibus_startup_requires_ownership(struct ct *t)
|
|||||||
saved = old == nil ? nil : strdup(old);
|
saved = old == nil ? nil : strdup(old);
|
||||||
if(old != nil && !CT_CHECK(t, saved != nil))
|
if(old != nil && !CT_CHECK(t, saved != nil))
|
||||||
return;
|
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))
|
if(!CT_CHECK(t, mkdtemp(root) != nil))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
made = 1;
|
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
|
static int
|
||||||
ibusbegin(struct ct *t, Ibusfix *f)
|
ibusbegin(struct ct *t, Ibusfix *f)
|
||||||
{
|
{
|
||||||
@@ -181,41 +167,32 @@ ibusbegin(struct ct *t, Ibusfix *f)
|
|||||||
memset(contexts, 0, sizeof contexts);
|
memset(contexts, 0, sizeof contexts);
|
||||||
memset(conns, 0, sizeof conns);
|
memset(conns, 0, sizeof conns);
|
||||||
nconns = 0;
|
nconns = 0;
|
||||||
|
preowner = nil;
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
while(channbrecv(drawc, &dc) > 0)
|
||||||
;
|
;
|
||||||
testengineinit(LangEN);
|
testengineinit(LangEN);
|
||||||
f->oldreply = replyc;
|
f->oldreply = replyc;
|
||||||
replyc = chancreate(sizeof(Keyres), 0);
|
replyc = chancreate(sizeof(Keyres), 0);
|
||||||
f->trace = chancreate(sizeof(Keyreq), Maxcontexts);
|
pumpstart(&f->pump, Maxcontexts);
|
||||||
f->stop = chancreate(sizeof(uchar), 0);
|
|
||||||
f->done = chancreate(sizeof(uchar), 0);
|
|
||||||
f->pumpactive = threadcreate(enginepump, f, 8192) >= 0;
|
|
||||||
f->c1 = malloc(1);
|
f->c1 = malloc(1);
|
||||||
f->c2 = 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
|
static void
|
||||||
ibusend(Ibusfix *f)
|
ibusend(Ibusfix *f)
|
||||||
{
|
{
|
||||||
Drawcmd dc;
|
Drawcmd dc;
|
||||||
Keyreq req;
|
|
||||||
uchar token;
|
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if(f->pumpactive){
|
for(i = 0; i < nelem(contexts); i++)
|
||||||
for(i = 0; i < nelem(contexts); i++)
|
if(contexts[i].conn != nil)
|
||||||
if(contexts[i].conn != nil)
|
dropcontext(&contexts[i]);
|
||||||
dropcontext(&contexts[i]);
|
pumpstop(&f->pump);
|
||||||
while(channbrecv(f->trace, &req) > 0)
|
|
||||||
;
|
|
||||||
token = 0;
|
|
||||||
chansend(f->stop, &token);
|
|
||||||
chanrecv(f->done, &token);
|
|
||||||
}
|
|
||||||
memset(contexts, 0, sizeof contexts);
|
memset(contexts, 0, sizeof contexts);
|
||||||
memset(conns, 0, sizeof conns);
|
memset(conns, 0, sizeof conns);
|
||||||
nconns = 0;
|
nconns = 0;
|
||||||
|
preowner = nil;
|
||||||
testengineinit(LangEN);
|
testengineinit(LangEN);
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
while(channbrecv(drawc, &dc) > 0)
|
||||||
;
|
;
|
||||||
@@ -223,9 +200,6 @@ ibusend(Ibusfix *f)
|
|||||||
free(f->c2);
|
free(f->c2);
|
||||||
chanfree(replyc);
|
chanfree(replyc);
|
||||||
replyc = f->oldreply;
|
replyc = f->oldreply;
|
||||||
chanfree(f->trace);
|
|
||||||
chanfree(f->stop);
|
|
||||||
chanfree(f->done);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Keyreq
|
static Keyreq
|
||||||
@@ -234,10 +208,12 @@ nexttrace(struct ct *t, Ibusfix *f, int op, Ictx *ctx)
|
|||||||
Keyreq req;
|
Keyreq req;
|
||||||
|
|
||||||
memset(&req, 0, sizeof 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;
|
return req;
|
||||||
CT_EQ_INT(t, op, req.op);
|
CT_EQ_INT(t, op, req.op);
|
||||||
CT_EQ_PTR(t, ctx, req.owner);
|
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);
|
CT_EQ_PTR(t, replyc, req.reply);
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
@@ -247,27 +223,7 @@ notrace(struct ct *t, Ibusfix *f)
|
|||||||
{
|
{
|
||||||
Keyreq req;
|
Keyreq req;
|
||||||
|
|
||||||
CT_CHECK(t, channbrecv(f->trace, &req) <= 0);
|
CT_CHECK(t, channbrecv(f->pump.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Keyres
|
static Keyres
|
||||||
@@ -281,6 +237,108 @@ contextkey(struct ct *t, Ibusfix *f, Ictx *ctx, u32int sym, u32int state)
|
|||||||
return res;
|
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
|
void
|
||||||
ibus_context_lifecycle(struct ct *t)
|
ibus_context_lifecycle(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -306,7 +364,7 @@ ibus_context_lifecycle(struct ct *t)
|
|||||||
notrace(t, &f);
|
notrace(t, &f);
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
|
|
||||||
setcursor(a, 10, 20, 14);
|
setcursor(a, -10, -20, 14);
|
||||||
notrace(t, &f);
|
notrace(t, &f);
|
||||||
CT_CHECK(t, a->caret.valid);
|
CT_CHECK(t, a->caret.valid);
|
||||||
a->focused = 1;
|
a->focused = 1;
|
||||||
@@ -321,23 +379,23 @@ ibus_context_lifecycle(struct ct *t)
|
|||||||
req = nexttrace(t, &f, Keypress, a);
|
req = nexttrace(t, &f, Keypress, a);
|
||||||
CT_CHECK(t, !res.eaten);
|
CT_CHECK(t, !res.eaten);
|
||||||
CT_CHECK(t, req.caret.valid);
|
CT_CHECK(t, req.caret.valid);
|
||||||
CT_EQ_INT(t, 10, req.caret.x);
|
CT_EQ_INT(t, -10, req.caret.x);
|
||||||
CT_EQ_INT(t, 20, req.caret.y);
|
CT_EQ_INT(t, -20, req.caret.y);
|
||||||
CT_EQ_INT(t, 14, req.caret.h);
|
CT_EQ_INT(t, 14, req.caret.h);
|
||||||
CT_EQ_PTR(t, a, testengineowner());
|
CT_EQ_PTR(t, a, testengineowner());
|
||||||
testenginecaret(&at);
|
testenginecaret(&at);
|
||||||
CT_CHECK(t, at.valid);
|
CT_CHECK(t, at.valid);
|
||||||
CT_EQ_INT(t, 10, at.x);
|
CT_EQ_INT(t, -10, at.x);
|
||||||
CT_EQ_INT(t, 20, at.y);
|
CT_EQ_INT(t, -20, at.y);
|
||||||
res = contextkey(t, &f, a, 'n', Testctrlmask);
|
res = contextkey(t, &f, a, 'n', Testctrlmask);
|
||||||
CT_CHECK(t, res.eaten);
|
CT_CHECK(t, res.eaten);
|
||||||
contextkey(t, &f, a, 'k', 0);
|
contextkey(t, &f, a, 'k', 0);
|
||||||
res = contextkey(t, &f, a, 'a', 0);
|
res = contextkey(t, &f, a, 'a', 0);
|
||||||
checkpreedit(t, "か", &res);
|
checkstr(t, "preedit", "か", &res.preedit);
|
||||||
|
|
||||||
b->focused = 1;
|
b->focused = 1;
|
||||||
res = contextkey(t, &f, b, 'n', 0);
|
res = contextkey(t, &f, b, 'n', 0);
|
||||||
checkpreedit(t, "ん", &res);
|
checkstr(t, "preedit", "ん", &res.preedit);
|
||||||
CT_EQ_PTR(t, b, testengineowner());
|
CT_EQ_PTR(t, b, testengineowner());
|
||||||
|
|
||||||
setcursor(b, 50, 60, 12);
|
setcursor(b, 50, 60, 12);
|
||||||
@@ -351,7 +409,7 @@ ibus_context_lifecycle(struct ct *t)
|
|||||||
memset(&res, 0, sizeof res);
|
memset(&res, 0, sizeof res);
|
||||||
sendrequest(a, Keyreset, 0, 0, &res);
|
sendrequest(a, Keyreset, 0, 0, &res);
|
||||||
nexttrace(t, &f, Keyreset, a);
|
nexttrace(t, &f, Keyreset, a);
|
||||||
checkpreedit(t, "", &res);
|
checkstr(t, "preedit", "", &res.preedit);
|
||||||
checkenginepreedit(t, "ん");
|
checkenginepreedit(t, "ん");
|
||||||
CT_EQ_PTR(t, b, testengineowner());
|
CT_EQ_PTR(t, b, testengineowner());
|
||||||
|
|
||||||
@@ -367,11 +425,7 @@ ibus_context_lifecycle(struct ct *t)
|
|||||||
CT_CHECK(t, !a->caret.valid);
|
CT_CHECK(t, !a->caret.valid);
|
||||||
checkenginepreedit(t, "ん");
|
checkenginepreedit(t, "ん");
|
||||||
CT_EQ_PTR(t, b, testengineowner());
|
CT_EQ_PTR(t, b, testengineowner());
|
||||||
releasecontext(a);
|
checkdrop(t, &f, a, "/context/one");
|
||||||
notrace(t, &f);
|
|
||||||
dropcontext(a);
|
|
||||||
notrace(t, &f);
|
|
||||||
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/one"));
|
|
||||||
|
|
||||||
reused = newcontext(f.c1, "/context/stale-destroy");
|
reused = newcontext(f.c1, "/context/stale-destroy");
|
||||||
CT_EQ_PTR(t, a, reused);
|
CT_EQ_PTR(t, a, reused);
|
||||||
@@ -419,17 +473,17 @@ ibus_active_release_lifecycle(struct ct *t)
|
|||||||
ctx->focused = 1;
|
ctx->focused = 1;
|
||||||
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
||||||
res = contextkey(t, &f, ctx, 'n', 0);
|
res = contextkey(t, &f, ctx, 'n', 0);
|
||||||
checkpreedit(t, "ん", &res);
|
checkstr(t, "preedit", "ん", &res.preedit);
|
||||||
|
|
||||||
memset(&res, 0, sizeof res);
|
memset(&res, 0, sizeof res);
|
||||||
sendrequest(ctx, Keyreset, 0, 0, &res);
|
sendrequest(ctx, Keyreset, 0, 0, &res);
|
||||||
nexttrace(t, &f, Keyreset, ctx);
|
nexttrace(t, &f, Keyreset, ctx);
|
||||||
checkpreedit(t, "", &res);
|
checkstr(t, "preedit", "", &res.preedit);
|
||||||
checkenginepreedit(t, "");
|
checkenginepreedit(t, "");
|
||||||
CT_CHECK(t, ctx->focused);
|
CT_CHECK(t, ctx->focused);
|
||||||
CT_EQ_PTR(t, ctx, testengineowner());
|
CT_EQ_PTR(t, ctx, testengineowner());
|
||||||
res = contextkey(t, &f, ctx, 'k', 0);
|
res = contextkey(t, &f, ctx, 'k', 0);
|
||||||
checkpreedit(t, "k", &res);
|
checkstr(t, "preedit", "k", &res.preedit);
|
||||||
CT_EQ_PTR(t, ctx, testengineowner());
|
CT_EQ_PTR(t, ctx, testengineowner());
|
||||||
|
|
||||||
releasecontext(ctx);
|
releasecontext(ctx);
|
||||||
@@ -437,11 +491,7 @@ ibus_active_release_lifecycle(struct ct *t)
|
|||||||
CT_CHECK(t, !ctx->focused);
|
CT_CHECK(t, !ctx->focused);
|
||||||
checkenginepreedit(t, "");
|
checkenginepreedit(t, "");
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
releasecontext(ctx);
|
checkdrop(t, &f, ctx, "/context/reset");
|
||||||
notrace(t, &f);
|
|
||||||
dropcontext(ctx);
|
|
||||||
notrace(t, &f);
|
|
||||||
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/reset"));
|
|
||||||
|
|
||||||
reused = newcontext(f.c1, "/context/destroy");
|
reused = newcontext(f.c1, "/context/destroy");
|
||||||
CT_EQ_PTR(t, ctx, reused);
|
CT_EQ_PTR(t, ctx, reused);
|
||||||
|
|||||||
@@ -1,52 +1,25 @@
|
|||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#include <dirent.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <signal.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/inotify.h>
|
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/un.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "../ipc.h"
|
#include "ipc.h"
|
||||||
|
#include "live.h"
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Maxclients = 64,
|
Maxclients = 64,
|
||||||
Calltimeout = 4000,
|
Cappreedit = 1,
|
||||||
Starttimeout = 8000,
|
|
||||||
Stoptimeout = 3000,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Daemon Daemon;
|
|
||||||
typedef struct Response Response;
|
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
|
struct Response
|
||||||
{
|
{
|
||||||
int eaten;
|
int eaten;
|
||||||
@@ -54,485 +27,6 @@ struct Response
|
|||||||
char preedit[Ipcfieldmax+1];
|
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
|
static size_t
|
||||||
getlen(unsigned char p[Ipclensz])
|
getlen(unsigned char p[Ipclensz])
|
||||||
{
|
{
|
||||||
@@ -616,6 +110,15 @@ sendreset(int fd, int want)
|
|||||||
return ipcsend(fd, req, sizeof req) == 0;
|
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
|
static int
|
||||||
expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
||||||
char *where)
|
char *where)
|
||||||
@@ -632,11 +135,12 @@ expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
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))
|
if(!sendreset(fd, want))
|
||||||
return fail("send %s: %s", where, strerror(errno));
|
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
|
static int
|
||||||
@@ -654,7 +158,7 @@ tryclient(char *path, int64_t deadline, int *client)
|
|||||||
Response res;
|
Response res;
|
||||||
int fd, rv;
|
int fd, rv;
|
||||||
|
|
||||||
fd = connectuntil(path, deadline);
|
fd = connectsocket(path, deadline);
|
||||||
if(fd < 0)
|
if(fd < 0)
|
||||||
return errno == ETIMEDOUT ? -1 : 0;
|
return errno == ETIMEDOUT ? -1 : 0;
|
||||||
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
||||||
@@ -701,7 +205,7 @@ overflowrejected(char *path)
|
|||||||
int64_t deadline;
|
int64_t deadline;
|
||||||
|
|
||||||
deadline = nowms() + Calltimeout;
|
deadline = nowms() + Calltimeout;
|
||||||
fd = connectuntil(path, deadline);
|
fd = connectsocket(path, deadline);
|
||||||
if(fd < 0)
|
if(fd < 0)
|
||||||
return fail("overflow connect: %s", strerror(errno));
|
return fail("overflow connect: %s", strerror(errno));
|
||||||
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
||||||
@@ -742,7 +246,25 @@ overflowrejected(char *path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
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 client[Maxclients];
|
||||||
int i, ok;
|
int i, ok;
|
||||||
@@ -750,25 +272,16 @@ runlistener(Daemon *d)
|
|||||||
for(i = 0; i < Maxclients; i++)
|
for(i = 0; i < Maxclients; i++)
|
||||||
client[i] = -1;
|
client[i] = -1;
|
||||||
ok = 0;
|
ok = 0;
|
||||||
client[0] = connectuntil(d->socket, nowms() + Calltimeout);
|
client[0] = connectsocket(path, nowms() + Calltimeout);
|
||||||
if(client[0] < 0){
|
if(client[0] < 0){
|
||||||
fail("connect first client: %s", strerror(errno));
|
fail("connect first client: %s", strerror(errno));
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
if(!requestkey(client[0], 1, Mctrl, 'n', 1, "", "", "select Japanese") ||
|
if(!requestkey(client[0], 1, Mctrl, 'n', 1, "", "",
|
||||||
!requestkey(client[0], 1, 0, 'k', 1, "", "k", "GTK preedit") ||
|
"select Japanese"))
|
||||||
!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"))
|
|
||||||
goto out;
|
goto out;
|
||||||
for(i = 1; i < Maxclients; i++){
|
for(i = 1; i < Maxclients; i++){
|
||||||
client[i] = connectuntil(d->socket, nowms() + Calltimeout);
|
client[i] = connectsocket(path, nowms() + Calltimeout);
|
||||||
if(client[i] < 0){
|
if(client[i] < 0){
|
||||||
fail("connect capacity client %d: %s", i, strerror(errno));
|
fail("connect capacity client %d: %s", i, strerror(errno));
|
||||||
goto out;
|
goto out;
|
||||||
@@ -777,15 +290,15 @@ runlistener(Daemon *d)
|
|||||||
"capacity modifier"))
|
"capacity modifier"))
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
if(!overflowrejected(d->socket))
|
if(!overflowrejected(path))
|
||||||
goto out;
|
goto out;
|
||||||
close(client[Maxclients-1]);
|
close(client[Maxclients-1]);
|
||||||
client[Maxclients-1] = -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;
|
goto out;
|
||||||
close(client[0]);
|
close(client[0]);
|
||||||
client[0] = -1;
|
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, "", "あ",
|
!requestkey(client[0], 1, 0, 'a', 1, "", "あ",
|
||||||
"post-disconnect composition"))
|
"post-disconnect composition"))
|
||||||
goto out;
|
goto out;
|
||||||
@@ -801,21 +314,35 @@ int
|
|||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
Daemon daemon;
|
Daemon daemon;
|
||||||
int ok;
|
Live live;
|
||||||
|
int capacity, ok;
|
||||||
|
|
||||||
if(argc != 3){
|
testname = "ipc_live_test";
|
||||||
fprintf(stderr, "usage: ipc_live_test strans mapdir\n");
|
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;
|
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)
|
if(ok)
|
||||||
ok = runlistener(&daemon);
|
ok = capacity ? runcapacity(live.socket) : runsmoke(live.socket);
|
||||||
if(!ok)
|
if(!ok)
|
||||||
showerrors(&daemon);
|
showerrors(&daemon);
|
||||||
if(!stopdaemon(&daemon))
|
if(!stopdaemon(&daemon))
|
||||||
ok = 0;
|
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)
|
if(!ok)
|
||||||
return 1;
|
return 1;
|
||||||
printf("ipc live listener: ok\n");
|
printf("ipc %s: ok\n", capacity ? "connection capacity" : "live smoke");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
286
tests/ipc_test.c
286
tests/ipc_test.c
@@ -1,10 +1,33 @@
|
|||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
|
#undef accept
|
||||||
|
#undef listen
|
||||||
|
#undef send
|
||||||
|
|
||||||
|
static int64_t
|
||||||
|
nowms(void)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
||||||
|
return 0;
|
||||||
|
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ipcclientdeadlines(struct ct*);
|
||||||
|
static void ipcconnectcloexec(struct ct*);
|
||||||
|
|
||||||
void
|
void
|
||||||
ipc_masks_modifiers(struct ct *t)
|
ipc_masks_modifiers(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -25,6 +48,103 @@ ipc_masks_modifiers(struct ct *t)
|
|||||||
CT_EQ_UINT(t, 0, mod);
|
CT_EQ_UINT(t, 0, mod);
|
||||||
CT_EQ_UINT(t, 0, key);
|
CT_EQ_UINT(t, 0, key);
|
||||||
CT_CHECK(t, ipcreqreset(buf));
|
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
|
void
|
||||||
@@ -45,7 +165,6 @@ ipc_runtime_path(struct ct *t)
|
|||||||
snprint(fallback, sizeof fallback, "/tmp/strans.%d", getuid());
|
snprint(fallback, sizeof fallback, "/tmp/strans.%d", getuid());
|
||||||
CT_EQ_STR(t, fallback, buf);
|
CT_EQ_STR(t, fallback, buf);
|
||||||
CT_EQ_INT(t, -1, ipcpath(buf, 4));
|
CT_EQ_INT(t, -1, ipcpath(buf, 4));
|
||||||
CT_EQ_INT(t, -1, ipcpath(nil, sizeof buf));
|
|
||||||
if(saved != nil){
|
if(saved != nil){
|
||||||
setenv("XDG_RUNTIME_DIR", saved, 1);
|
setenv("XDG_RUNTIME_DIR", saved, 1);
|
||||||
free(saved);
|
free(saved);
|
||||||
@@ -83,22 +202,18 @@ ipc_response_pack_boundaries(struct ct *t)
|
|||||||
CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1,
|
CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1,
|
||||||
(char*)field, sizeof field, (char*)field, sizeof field, 1));
|
(char*)field, sizeof field, (char*)field, sizeof field, 1));
|
||||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
||||||
nil, 1, nil, 0, 0));
|
(char*)field, Ipcfieldmax+1, "", 0, 0));
|
||||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
|
||||||
nil, 0, nil, 1, 1));
|
|
||||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
|
||||||
(char*)field, Ipcfieldmax+1, nil, 0, 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ipc_response_empty_and_preedit(struct ct *t)
|
ipc_response_empty_and_preedit(struct ct *t)
|
||||||
{
|
{
|
||||||
uchar first[Ipcmaxresp], second[Ipcmaxresp];
|
uchar first[Ipcmaxresp], second[Ipcmaxresp];
|
||||||
char commit[16], preedit[16];
|
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
int fd[2], nfirst, nsecond;
|
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,
|
nsecond = ipcpackresp(second, sizeof second, 1,
|
||||||
"go", 2, "kana", 4, 1);
|
"go", 2, "kana", 4, 1);
|
||||||
if(!CT_CHECK(t, nfirst > 0 && nsecond > 0))
|
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)) &&
|
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, ipcsend(fd[0], second, nsecond)) &&
|
||||||
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, sizeof commit,
|
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, preedit, &resp))){
|
||||||
nil, 0, &resp))){
|
|
||||||
CT_EQ_INT(t, 0, resp.eaten);
|
CT_EQ_INT(t, 0, resp.eaten);
|
||||||
CT_EQ_SIZE(t, 0, resp.ncommit);
|
CT_EQ_SIZE(t, 0, resp.commitlen);
|
||||||
CT_EQ_SIZE(t, 0, resp.npreedit);
|
CT_EQ_SIZE(t, 0, resp.preeditlen);
|
||||||
CT_EQ_STR(t, "", commit);
|
CT_EQ_STR(t, "", commit);
|
||||||
}
|
}
|
||||||
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, sizeof commit,
|
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit, &resp))){
|
||||||
preedit, sizeof preedit, &resp))){
|
|
||||||
CT_EQ_INT(t, 1, resp.eaten);
|
CT_EQ_INT(t, 1, resp.eaten);
|
||||||
CT_EQ_SIZE(t, 2, resp.ncommit);
|
CT_EQ_SIZE(t, 2, resp.commitlen);
|
||||||
CT_EQ_SIZE(t, 4, resp.npreedit);
|
CT_EQ_SIZE(t, 4, resp.preeditlen);
|
||||||
CT_EQ_STR(t, "go", commit);
|
CT_EQ_STR(t, "go", commit);
|
||||||
CT_EQ_STR(t, "kana", preedit);
|
CT_EQ_STR(t, "kana", preedit);
|
||||||
}
|
}
|
||||||
@@ -132,14 +245,14 @@ void
|
|||||||
ipc_response_max_and_drain(struct ct *t)
|
ipc_response_max_and_drain(struct ct *t)
|
||||||
{
|
{
|
||||||
uchar first[Ipcmaxresp], second[Ipcmaxresp], field[Ipcfieldmax];
|
uchar first[Ipcmaxresp], second[Ipcmaxresp], field[Ipcfieldmax];
|
||||||
char commit[8], preedit[8];
|
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
int fd[2], nfirst, nsecond;
|
int fd[2], nfirst, nsecond;
|
||||||
|
|
||||||
memset(field, 'x', sizeof field);
|
memset(field, 'x', sizeof field);
|
||||||
nfirst = ipcpackresp(first, sizeof first, 1,
|
nfirst = ipcpackresp(first, sizeof first, 1,
|
||||||
(char*)field, sizeof field, (char*)field, sizeof field, 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))
|
if(!CT_CHECK(t, nfirst == Ipcmaxresp && nsecond > 0))
|
||||||
return;
|
return;
|
||||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
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)) &&
|
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, ipcsend(fd[0], second, nsecond)) &&
|
||||||
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, sizeof commit,
|
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit, &resp))){
|
||||||
preedit, sizeof preedit, &resp))){
|
CT_EQ_SIZE(t, Ipcfieldmax, resp.commitlen);
|
||||||
CT_EQ_SIZE(t, Ipcfieldmax, resp.ncommit);
|
CT_EQ_SIZE(t, Ipcfieldmax, resp.preeditlen);
|
||||||
CT_EQ_SIZE(t, Ipcfieldmax, resp.npreedit);
|
CT_EQ_MEM(t, field, commit, Ipcfieldmax);
|
||||||
CT_EQ_STR(t, "xxxxxxx", commit);
|
CT_EQ_MEM(t, field, preedit, Ipcfieldmax);
|
||||||
CT_EQ_STR(t, "xxxxxxx", preedit);
|
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,
|
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, preedit, &resp)))
|
||||||
nil, 0, &resp)))
|
|
||||||
CT_EQ_STR(t, "ok", commit);
|
CT_EQ_STR(t, "ok", commit);
|
||||||
close(fd[0]);
|
close(fd[0]);
|
||||||
close(fd[1]);
|
close(fd[1]);
|
||||||
@@ -166,7 +279,7 @@ void
|
|||||||
ipc_response_fragmented_and_truncated(struct ct *t)
|
ipc_response_fragmented_and_truncated(struct ct *t)
|
||||||
{
|
{
|
||||||
uchar frame[Ipcmaxresp];
|
uchar frame[Ipcmaxresp];
|
||||||
char commit[8], preedit[8];
|
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
int fd[2], i, n;
|
int fd[2], i, n;
|
||||||
|
|
||||||
@@ -182,8 +295,8 @@ ipc_response_fragmented_and_truncated(struct ct *t)
|
|||||||
CT_ERRORF(t, "fragment send failed");
|
CT_ERRORF(t, "fragment send failed");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(i == n && CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1,
|
if(i == n && CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit,
|
||||||
commit, sizeof commit, preedit, sizeof preedit, &resp))){
|
&resp))){
|
||||||
CT_EQ_STR(t, "abc", commit);
|
CT_EQ_STR(t, "abc", commit);
|
||||||
CT_EQ_STR(t, "xy", preedit);
|
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));
|
CT_EQ_INT(t, 0, ipcsend(fd[0], frame, n-1));
|
||||||
shutdown(fd[0], SHUT_WR);
|
shutdown(fd[0], SHUT_WR);
|
||||||
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, sizeof commit,
|
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, preedit, &resp));
|
||||||
preedit, sizeof 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[0]);
|
||||||
close(fd[1]);
|
close(fd[1]);
|
||||||
}
|
}
|
||||||
@@ -214,4 +338,104 @@ ipc_broken_peer_send(struct ct *t)
|
|||||||
close(fd[1]);
|
close(fd[1]);
|
||||||
CT_EQ_INT(t, -1, ipcsend(fd[0], "x", 1));
|
CT_EQ_INT(t, -1, ipcsend(fd[0], "x", 1));
|
||||||
close(fd[0]);
|
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"
|
#include "test.h"
|
||||||
|
|
||||||
static Str
|
static Str
|
||||||
kotrans(char *keys)
|
kotrans(char *keys)
|
||||||
{
|
{
|
||||||
Emit e;
|
Str out, raw;
|
||||||
Im state;
|
|
||||||
Str out;
|
|
||||||
char *p;
|
|
||||||
|
|
||||||
memset(&state, 0, sizeof state);
|
raw = mkstr(keys);
|
||||||
state.l = getlang(LangKO);
|
transstr(getlang(LangKO), &raw, &out);
|
||||||
sclear(&out);
|
|
||||||
for(p = keys; *p != '\0'; p++){
|
|
||||||
e = transko(&state, (uchar)*p);
|
|
||||||
sappend(&out, &e.s);
|
|
||||||
state.pre = e.next;
|
|
||||||
if(!e.eat)
|
|
||||||
sputr(&out, (uchar)*p);
|
|
||||||
}
|
|
||||||
sappend(&out, &state.pre);
|
|
||||||
return out;
|
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)
|
self.assertIn("한\t", result.stdout)
|
||||||
|
|
||||||
def test_generator_keeps_runtime_candidate_limit(self):
|
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))
|
result = run(GENERATE, self.source(rows))
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
candidates = result.stdout.rstrip().split("\t", 1)[1].split()
|
candidates = result.stdout.rstrip().split("\t", 1)[1].split()
|
||||||
self.assertEqual(len(candidates), 32)
|
self.assertEqual(len(candidates), 128)
|
||||||
self.assertEqual(candidates[-1], chr(0x4E00 + 31))
|
self.assertEqual(candidates[-1], chr(0x4E00 + 127))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,34 +1,343 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.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
|
void
|
||||||
popup_layout(struct ct *t)
|
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;
|
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);
|
memset(&caret, 0, sizeof caret);
|
||||||
popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y);
|
area = (Area){1920, 30, 1280, 900};
|
||||||
CT_EQ_INT(t, 50, x);
|
popupposition(&caret, 2000, 50, &area, 100, 60, &x, &y);
|
||||||
|
CT_EQ_INT(t, 2010, x);
|
||||||
CT_EQ_INT(t, 60, y);
|
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.valid = 1;
|
||||||
caret.x = 70;
|
caret.x = 2100;
|
||||||
caret.y = 80;
|
caret.y = 80;
|
||||||
caret.h = 20;
|
caret.h = 20;
|
||||||
popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y);
|
popupposition(&caret, 0, 0, &area, 100, 60, &x, &y);
|
||||||
CT_EQ_INT(t, 70, x);
|
CT_EQ_INT(t, 2100, x);
|
||||||
CT_EQ_INT(t, 100, y);
|
CT_EQ_INT(t, 100, y);
|
||||||
caret.x = 790;
|
caret.x = 3150;
|
||||||
caret.y = 590;
|
caret.y = 850;
|
||||||
popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y);
|
popupposition(&caret, 0, 0, &area, 200, 100, &x, &y);
|
||||||
CT_EQ_INT(t, 700, x);
|
CT_EQ_INT(t, 3000, x);
|
||||||
CT_EQ_INT(t, 540, y);
|
CT_EQ_INT(t, 750, y);
|
||||||
caret.x = INT_MIN;
|
|
||||||
caret.y = INT_MAX;
|
/* A popup no larger than a small work area stays inside its origin. */
|
||||||
caret.h = INT_MAX;
|
area = (Area){100, 200, 80, 80};
|
||||||
popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y);
|
caret.x = 120;
|
||||||
CT_EQ_INT(t, 0, x);
|
caret.y = 230;
|
||||||
CT_EQ_INT(t, 540, y);
|
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 <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "../srv.c"
|
#include "srv.c"
|
||||||
#include "../cutest/cutest.h"
|
#include "test.h"
|
||||||
|
|
||||||
void testengineinit(int);
|
|
||||||
void testenginehandle(Keyreq*);
|
|
||||||
void* testengineowner(void);
|
|
||||||
void testenginepreedit(Str*);
|
|
||||||
|
|
||||||
#undef recv
|
#undef recv
|
||||||
|
|
||||||
typedef struct Enginegate Enginegate;
|
typedef struct Serverfix Serverfix;
|
||||||
typedef struct Testclient Testclient;
|
typedef struct Testclient Testclient;
|
||||||
|
|
||||||
struct Enginegate
|
/* A held pump plus a private client-slot channel of the wanted depth. */
|
||||||
|
struct Serverfix
|
||||||
{
|
{
|
||||||
Channel *seen;
|
Pump pump;
|
||||||
Channel *go;
|
Channel *oldclientc;
|
||||||
Channel *stop;
|
|
||||||
Channel *done;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Testclient
|
struct Testclient
|
||||||
@@ -34,30 +28,30 @@ struct Testclient
|
|||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
enginegate(void *arg)
|
serverbegin(Serverfix *f, int nclients)
|
||||||
{
|
{
|
||||||
Enginegate *g;
|
Drawcmd dc;
|
||||||
Keyreq req;
|
|
||||||
uchar token;
|
uchar token;
|
||||||
Alt alts[] = {
|
|
||||||
{keyc, &req, CHANRCV, nil},
|
|
||||||
{nil, &token, CHANRCV, nil},
|
|
||||||
{nil, nil, CHANEND, nil},
|
|
||||||
};
|
|
||||||
|
|
||||||
g = arg;
|
f->oldclientc = clientc;
|
||||||
alts[1].c = g->stop;
|
while(channbrecv(drawc, &dc) > 0)
|
||||||
for(;;)
|
;
|
||||||
switch(alt(alts)){
|
clientc = chancreate(sizeof token, nclients);
|
||||||
case 0:
|
testengineinit(LangJP);
|
||||||
chansend(g->seen, &req);
|
pumpstart(&f->pump, 0);
|
||||||
chanrecv(g->go, &token);
|
pumphold(&f->pump, Pumpall);
|
||||||
testenginehandle(&req);
|
}
|
||||||
break;
|
|
||||||
case 1:
|
static void
|
||||||
chansend(g->done, &token);
|
serverend(Serverfix *f)
|
||||||
return;
|
{
|
||||||
}
|
Drawcmd dc;
|
||||||
|
|
||||||
|
pumpstop(&f->pump);
|
||||||
|
chanfree(clientc);
|
||||||
|
clientc = f->oldclientc;
|
||||||
|
while(channbrecv(drawc, &dc) > 0)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -142,38 +136,70 @@ sendreset(struct ct *t, Testclient *client, int want)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Keyreq
|
static Keyreq
|
||||||
nextrequest(struct ct *t, Enginegate *g, int op)
|
nextrequestcap(struct ct *t, Pump *p, int op, int cap)
|
||||||
{
|
{
|
||||||
Keyreq req;
|
Keyreq req;
|
||||||
|
|
||||||
memset(&req, 0, sizeof 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, op, req.op);
|
||||||
|
CT_EQ_INT(t, cap, req.cap);
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Keyreq
|
||||||
|
nextrequest(struct ct *t, Pump *p, int op)
|
||||||
|
{
|
||||||
|
return nextrequestcap(t, p, op, Cclientpreedit);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
allowrequest(Enginegate *g)
|
allowrequest(Pump *p)
|
||||||
{
|
{
|
||||||
uchar token;
|
uchar token;
|
||||||
|
|
||||||
token = 0;
|
token = 0;
|
||||||
chansend(g->go, &token);
|
chansend(p->go, &token);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
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;
|
Ipcresp resp;
|
||||||
|
|
||||||
if(ipcreadresp(client->peer, want, commit, sizeof commit,
|
if(ipcreadresp(client->peer, want, commit, preedit, &resp) < 0)
|
||||||
preedit, npreedit, &resp) < 0)
|
|
||||||
return CT_ERRORF(t, "response read failed: %s", strerror(errno));
|
return CT_ERRORF(t, "response read failed: %s", strerror(errno));
|
||||||
CT_EQ_STR(t, "", commit);
|
CT_EQ_STR(t, wantcommit, commit);
|
||||||
return resp.eaten;
|
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
|
static void
|
||||||
waitclient(struct ct *t, Testclient *client)
|
waitclient(struct ct *t, Testclient *client)
|
||||||
{
|
{
|
||||||
@@ -191,127 +217,112 @@ waitclient(struct ct *t, Testclient *client)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
disconnectclient(struct ct *t, Enginegate *g, Testclient *client, void *owner)
|
disconnectclient(struct ct *t, Pump *p, Testclient *client, void *owner)
|
||||||
{
|
{
|
||||||
Keyreq req;
|
Keyreq req;
|
||||||
uchar token;
|
uchar token;
|
||||||
|
|
||||||
close(client->peer);
|
close(client->peer);
|
||||||
client->peer = -1;
|
client->peer = -1;
|
||||||
req = nextrequest(t, g, Keyrelease);
|
req = nextrequest(t, p, Keyrelease);
|
||||||
if(owner != nil)
|
if(owner != nil)
|
||||||
CT_EQ_PTR(t, owner, req.owner);
|
CT_EQ_PTR(t, owner, req.owner);
|
||||||
CT_CHECK(t, channbrecv(client->done, &token) <= 0);
|
CT_CHECK(t, channbrecv(client->done, &token) <= 0);
|
||||||
allowrequest(g);
|
allowrequest(p);
|
||||||
waitclient(t, client);
|
waitclient(t, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
server_connection_ownership(struct ct *t)
|
server_connection_ownership(struct ct *t)
|
||||||
{
|
{
|
||||||
Channel *oldclientc;
|
Serverfix f;
|
||||||
Enginegate gate;
|
|
||||||
Testclient a, b, c;
|
Testclient a, b, c;
|
||||||
Keyreq req;
|
Keyreq req;
|
||||||
Drawcmd dc;
|
|
||||||
Str shown;
|
Str shown;
|
||||||
char preedit[Maxutf];
|
char preedit[Ipcfieldmax+1];
|
||||||
void *aowner, *bowner, *cowner;
|
void *aowner, *bowner, *cowner;
|
||||||
uchar byte, token;
|
uchar byte, token;
|
||||||
ssize_t n;
|
ssize_t n;
|
||||||
int gateactive;
|
|
||||||
|
|
||||||
memset(&gate, 0, sizeof gate);
|
|
||||||
memset(&a, 0, sizeof a);
|
memset(&a, 0, sizeof a);
|
||||||
memset(&b, 0, sizeof b);
|
memset(&b, 0, sizeof b);
|
||||||
memset(&c, 0, sizeof c);
|
memset(&c, 0, sizeof c);
|
||||||
a.fd = a.peer = b.fd = b.peer = c.fd = c.peer = -1;
|
a.fd = a.peer = b.fd = b.peer = c.fd = c.peer = -1;
|
||||||
aowner = bowner = cowner = nil;
|
aowner = bowner = cowner = nil;
|
||||||
oldclientc = clientc;
|
serverbegin(&f, 3);
|
||||||
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;
|
|
||||||
if(!startclient(t, &a) || !startclient(t, &b))
|
if(!startclient(t, &a) || !startclient(t, &b))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
if(!sendkey(t, &a, 1, 0, 'k'))
|
if(!sendkey(t, &a, 1, 0, 'k'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
aowner = req.owner;
|
aowner = req.owner;
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
|
||||||
CT_EQ_STR(t, "k", preedit);
|
CT_EQ_STR(t, "k", preedit);
|
||||||
if(!sendkey(t, &a, 1, 0, 'a'))
|
if(!sendkey(t, &a, 1, 0, 'a'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
CT_EQ_PTR(t, aowner, req.owner);
|
CT_EQ_PTR(t, aowner, req.owner);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
|
||||||
CT_EQ_STR(t, "か", preedit);
|
CT_EQ_STR(t, "か", preedit);
|
||||||
CT_EQ_PTR(t, aowner, testengineowner());
|
CT_EQ_PTR(t, aowner, testengineowner());
|
||||||
|
|
||||||
if(!sendkey(t, &b, 1, 0, 'n'))
|
if(!sendkey(t, &b, 1, 0, 'n'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
bowner = req.owner;
|
bowner = req.owner;
|
||||||
CT_CHECK(t, bowner != aowner);
|
CT_CHECK(t, bowner != aowner);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
||||||
CT_EQ_STR(t, "ん", preedit);
|
CT_EQ_STR(t, "ん", preedit);
|
||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
|
|
||||||
if(!sendreset(t, &a, 1))
|
if(!sendreset(t, &a, 1))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keyreset);
|
req = nextrequest(t, &f.pump, Keyreset);
|
||||||
CT_EQ_PTR(t, aowner, req.owner);
|
CT_EQ_PTR(t, aowner, req.owner);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
|
||||||
CT_EQ_STR(t, "", preedit);
|
CT_EQ_STR(t, "", preedit);
|
||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
if(!sendkey(t, &b, 1, 0, Kmodfirst))
|
if(!sendkey(t, &b, 1, 0, Kmodfirst))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, !readreply(t, &b, 1, preedit, sizeof preedit));
|
CT_CHECK(t, !readreply(t, &b, 1, "", preedit));
|
||||||
CT_EQ_STR(t, "ん", preedit);
|
CT_EQ_STR(t, "ん", preedit);
|
||||||
|
|
||||||
disconnectclient(t, &gate, &a, aowner);
|
disconnectclient(t, &f.pump, &a, aowner);
|
||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
if(!sendkey(t, &b, 1, 0, 'y'))
|
if(!sendkey(t, &b, 1, 0, 'y'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
||||||
if(!sendkey(t, &b, 1, 0, 'a'))
|
if(!sendkey(t, &b, 1, 0, 'a'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
||||||
CT_EQ_STR(t, "にゃ", preedit);
|
CT_EQ_STR(t, "にゃ", preedit);
|
||||||
|
|
||||||
if(!sendreset(t, &b, 1))
|
if(!sendreset(t, &b, 1))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keyreset);
|
req = nextrequest(t, &f.pump, Keyreset);
|
||||||
CT_EQ_PTR(t, bowner, req.owner);
|
CT_EQ_PTR(t, bowner, req.owner);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &b, 1, "にゃ", preedit));
|
||||||
CT_EQ_STR(t, "", preedit);
|
CT_EQ_STR(t, "", preedit);
|
||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
|
|
||||||
if(!sendkey(t, &b, 0, 0, 'k'))
|
if(!sendkey(t, &b, 0, 0, 'k'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequestcap(t, &f.pump, Keypress, 0);
|
||||||
CT_EQ_PTR(t, bowner, req.owner);
|
CT_EQ_PTR(t, bowner, req.owner);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &b, 0, nil, 0));
|
CT_CHECK(t, readreply(t, &b, 0, "", preedit));
|
||||||
errno = 0;
|
errno = 0;
|
||||||
n = recv(b.peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
|
n = recv(b.peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
|
||||||
CT_EQ_INT(t, -1, n);
|
CT_EQ_INT(t, -1, n);
|
||||||
@@ -319,12 +330,12 @@ server_connection_ownership(struct ct *t)
|
|||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
if(!sendkey(t, &b, 1, 0, 'a'))
|
if(!sendkey(t, &b, 1, 0, 'a'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
||||||
CT_EQ_STR(t, "か", preedit);
|
CT_EQ_STR(t, "か", preedit);
|
||||||
|
|
||||||
disconnectclient(t, &gate, &b, bowner);
|
disconnectclient(t, &f.pump, &b, bowner);
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
testenginepreedit(&shown);
|
testenginepreedit(&shown);
|
||||||
CT_EQ_INT(t, 0, shown.n);
|
CT_EQ_INT(t, 0, shown.n);
|
||||||
@@ -333,38 +344,205 @@ server_connection_ownership(struct ct *t)
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
if(!sendkey(t, &c, 1, 0, 'k'))
|
if(!sendkey(t, &c, 1, 0, 'k'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
cowner = req.owner;
|
cowner = req.owner;
|
||||||
close(c.peer);
|
close(c.peer);
|
||||||
c.peer = -1;
|
c.peer = -1;
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
req = nextrequest(t, &gate, Keyrelease);
|
req = nextrequest(t, &f.pump, Keyrelease);
|
||||||
CT_EQ_PTR(t, cowner, req.owner);
|
CT_EQ_PTR(t, cowner, req.owner);
|
||||||
CT_EQ_PTR(t, cowner, testengineowner());
|
CT_EQ_PTR(t, cowner, testengineowner());
|
||||||
CT_CHECK(t, channbrecv(c.done, &token) <= 0);
|
CT_CHECK(t, channbrecv(c.done, &token) <= 0);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
waitclient(t, &c);
|
waitclient(t, &c);
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
CT_CHECK(t, channbrecv(clientc, &token) <= 0);
|
CT_CHECK(t, channbrecv(clientc, &token) <= 0);
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
if(a.peer >= 0)
|
if(a.peer >= 0)
|
||||||
disconnectclient(t, &gate, &a, aowner);
|
disconnectclient(t, &f.pump, &a, aowner);
|
||||||
if(b.peer >= 0)
|
if(b.peer >= 0)
|
||||||
disconnectclient(t, &gate, &b, bowner);
|
disconnectclient(t, &f.pump, &b, bowner);
|
||||||
if(c.peer >= 0)
|
if(c.peer >= 0)
|
||||||
disconnectclient(t, &gate, &c, cowner);
|
disconnectclient(t, &f.pump, &c, cowner);
|
||||||
if(gateactive){
|
serverend(&f);
|
||||||
token = 0;
|
}
|
||||||
chansend(gate.stop, &token);
|
|
||||||
chanrecv(gate.done, &token);
|
void
|
||||||
}
|
server_extension_stream(struct ct *t)
|
||||||
chanfree(gate.seen);
|
{
|
||||||
chanfree(gate.go);
|
Serverfix f;
|
||||||
chanfree(gate.stop);
|
Testclient a, b;
|
||||||
chanfree(gate.done);
|
Keyreq req;
|
||||||
chanfree(clientc);
|
uchar frame[Ipccaretsz];
|
||||||
clientc = oldclientc;
|
char preedit[Ipcfieldmax+1];
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
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"
|
#include "test.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
str_init_utf8(struct ct *t)
|
str_init_utf8(struct ct *t)
|
||||||
{
|
{
|
||||||
|
static char badtail[] = { 'a', (char)0x80 };
|
||||||
|
static char incomplete[] = { (char)0xea, (char)0xb0 };
|
||||||
|
static char overlong[] = { (char)0xc0, (char)0xaf };
|
||||||
|
static char outofrange[] = {
|
||||||
|
(char)0xf4, (char)0x90, (char)0x80, (char)0x80,
|
||||||
|
};
|
||||||
|
static char surrogate[] = {
|
||||||
|
(char)0xed, (char)0xa0, (char)0x80,
|
||||||
|
};
|
||||||
|
static char unexpected[] = { (char)0x80 };
|
||||||
|
static const struct {
|
||||||
|
char *name;
|
||||||
|
char *src;
|
||||||
|
int n;
|
||||||
|
int ok;
|
||||||
|
char *want;
|
||||||
|
} cases[] = {
|
||||||
|
{ "canonical Runeerror", "\xef\xbf\xbd", 3, 1, "\xef\xbf\xbd" },
|
||||||
|
{ "incomplete", incomplete, sizeof incomplete, 0, "" },
|
||||||
|
{ "unexpected continuation", unexpected, sizeof unexpected, 0, "" },
|
||||||
|
{ "overlong", overlong, sizeof overlong, 0, "" },
|
||||||
|
{ "surrogate", surrogate, sizeof surrogate, 0, "" },
|
||||||
|
{ "out of range", outofrange, sizeof outofrange, 0, "" },
|
||||||
|
{ "bad tail", badtail, sizeof badtail, 0, "" },
|
||||||
|
};
|
||||||
char full[Maxrunes+2];
|
char full[Maxrunes+2];
|
||||||
char incomplete[] = { (char)0xea, (char)0xb0 };
|
|
||||||
Str s;
|
Str s;
|
||||||
|
int i;
|
||||||
|
|
||||||
s = mkstr("A한😀");
|
s = mkstr("A한😀");
|
||||||
CT_EQ_INT(t, 3, s.n);
|
CT_EQ_INT(t, 3, s.n);
|
||||||
checkstr(t, "round trip", "A한😀", &s);
|
checkstr(t, "round trip", "A한😀", &s);
|
||||||
sinit(&s, incomplete, sizeof incomplete);
|
for(i = 0; i < nelem(cases); i++){
|
||||||
CT_EQ_INT(t, 0, s.n);
|
memset(&s, 0xa5, sizeof s);
|
||||||
|
if(sinit(&s, cases[i].src, cases[i].n) != cases[i].ok)
|
||||||
|
CT_ERRORF(t, "%s: wrong validity", cases[i].name);
|
||||||
|
checkstr(t, cases[i].name, cases[i].want, &s);
|
||||||
|
}
|
||||||
memset(full, 'a', sizeof full);
|
memset(full, 'a', sizeof full);
|
||||||
full[sizeof full-1] = '\0';
|
full[sizeof full-1] = '\0';
|
||||||
sinit(&s, full, strlen(full));
|
CT_CHECK(t, !sinit(&s, full, strlen(full)));
|
||||||
CT_EQ_INT(t, Maxrunes, s.n);
|
CT_EQ_INT(t, 0, s.n);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -45,7 +76,6 @@ str_utf8_capacity(struct ct *t)
|
|||||||
int n;
|
int n;
|
||||||
char *want;
|
char *want;
|
||||||
} cases[] = {
|
} cases[] = {
|
||||||
{ 0, 0, nil },
|
|
||||||
{ 1, 0, "" },
|
{ 1, 0, "" },
|
||||||
{ 2, 1, "a" },
|
{ 2, 1, "a" },
|
||||||
{ 3, 1, "a" },
|
{ 3, 1, "a" },
|
||||||
@@ -64,10 +94,6 @@ str_utf8_capacity(struct ct *t)
|
|||||||
if(n != cases[i].n)
|
if(n != cases[i].n)
|
||||||
CT_ERRORF(t, "size %d: want length %d, got %d",
|
CT_ERRORF(t, "size %d: want length %d, got %d",
|
||||||
cases[i].size, cases[i].n, n);
|
cases[i].size, cases[i].n, n);
|
||||||
if(cases[i].size == 0){
|
|
||||||
CT_EQ_INT(t, 'Z', buf[0]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(strcmp(cases[i].want, buf) != 0)
|
if(strcmp(cases[i].want, buf) != 0)
|
||||||
CT_ERRORF(t, "size %d: want \"%s\", got \"%s\"",
|
CT_ERRORF(t, "size %d: want \"%s\", got \"%s\"",
|
||||||
cases[i].size, cases[i].want, buf);
|
cases[i].size, cases[i].want, buf);
|
||||||
@@ -89,10 +115,6 @@ str_invalid_and_full_appends(struct ct *t)
|
|||||||
Str s;
|
Str s;
|
||||||
int i;
|
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++)
|
for(i = 0; i < Maxrunes; i++)
|
||||||
s.r[i] = 'a';
|
s.r[i] = 'a';
|
||||||
s.n = Maxrunes;
|
s.n = Maxrunes;
|
||||||
|
|||||||
74
tests/test.h
74
tests/test.h
@@ -1,22 +1,50 @@
|
|||||||
#include "../cutest/cutest.h"
|
/* Included after dat.h and fn.h (or after the source under test). */
|
||||||
#include "../dat.h"
|
#include "cutest/cutest.h"
|
||||||
#include "../fn.h"
|
|
||||||
|
|
||||||
extern Lang testvi;
|
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*);
|
Str mkstr(char*);
|
||||||
int checkstr(struct ct*, char*, char*, Str*);
|
int checkstr(struct ct*, char*, char*, Str*);
|
||||||
Str shownpre(Im*);
|
void checkenginepreedit(struct ct*, char*);
|
||||||
|
void pumpstart(Pump*, int);
|
||||||
|
void pumphold(Pump*, int);
|
||||||
|
void pumpstop(Pump*);
|
||||||
|
|
||||||
|
/* Engine internals reached through engine_test.c. */
|
||||||
|
void testengineinit(int);
|
||||||
|
void testenginehandle(Keyreq*);
|
||||||
|
void* testengineowner(void);
|
||||||
|
void testenginepreedit(Str*);
|
||||||
|
void testenginecaret(Caret*);
|
||||||
|
|
||||||
void str_init_utf8(struct ct*);
|
void str_init_utf8(struct ct*);
|
||||||
void str_edit_and_alias(struct ct*);
|
void str_edit_and_alias(struct ct*);
|
||||||
void str_utf8_capacity(struct ct*);
|
void str_utf8_capacity(struct ct*);
|
||||||
void str_invalid_and_full_appends(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_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 popup_layout(struct ct*);
|
||||||
void font_render(struct ct*);
|
void font_render(struct ct*);
|
||||||
void production_maps_load(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_backspace_clears_candidates(struct ct*);
|
||||||
void engine_selects_visible_candidate(struct ct*);
|
void engine_selects_visible_candidate(struct ct*);
|
||||||
void engine_candidate_shortcut_modifiers(struct ct*);
|
void engine_candidate_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_lifecycle(struct ct*);
|
||||||
void engine_active_owner_reset(struct ct*);
|
void engine_active_owner_reset(struct ct*);
|
||||||
void engine_active_owner_caret(struct ct*);
|
void engine_active_owner_caret(struct ct*);
|
||||||
|
void engine_popup_preedit_capability(struct ct*);
|
||||||
|
void engine_vietnamese_client_preedit(struct ct*);
|
||||||
void engine_commit_contract(struct ct*);
|
void engine_commit_contract(struct ct*);
|
||||||
void engine_language_switch_state(struct ct*);
|
void engine_language_switch_state(struct ct*);
|
||||||
void engine_telex_history_bound(struct ct*);
|
void engine_telex_history_bound(struct ct*);
|
||||||
@@ -50,9 +83,9 @@ void engine_emoji_queries(struct ct*);
|
|||||||
void engine_emoji_japanese_and_multirune(struct ct*);
|
void engine_emoji_japanese_and_multirune(struct ct*);
|
||||||
void engine_emoji_digit_aliases(struct ct*);
|
void engine_emoji_digit_aliases(struct ct*);
|
||||||
void engine_emoji_navigation(struct ct*);
|
void engine_emoji_navigation(struct ct*);
|
||||||
|
void engine_search_candidate_keys(struct ct*);
|
||||||
void engine_emoji_preedit_languages(struct ct*);
|
void engine_emoji_preedit_languages(struct ct*);
|
||||||
void engine_emoji_start_and_unknown(struct ct*);
|
void engine_emoji_start_and_unknown(struct ct*);
|
||||||
void engine_emoji_dictionary_identity(struct ct*);
|
|
||||||
void engine_hanja_search(struct ct*);
|
void engine_hanja_search(struct ct*);
|
||||||
void engine_hanja_unknown_and_cancel(struct ct*);
|
void engine_hanja_unknown_and_cancel(struct ct*);
|
||||||
void engine_hanja_korean_keys(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_randomized_stress(struct ct*);
|
||||||
void engine_full_boundary_passthrough(struct ct*);
|
void engine_full_boundary_passthrough(struct ct*);
|
||||||
void dictionary_candidates(struct ct*);
|
void dictionary_candidates(struct ct*);
|
||||||
void dictionary_misses_clear_result(struct ct*);
|
void dictionary_misses(struct ct*);
|
||||||
void dictionary_emoji_identity(struct ct*);
|
void dictionary_emoji_identity(struct ct*);
|
||||||
void ipc_masks_modifiers(struct ct*);
|
void ipc_masks_modifiers(struct ct*);
|
||||||
|
void ipc_control_and_caret_frames(struct ct*);
|
||||||
void ipc_runtime_path(struct ct*);
|
void ipc_runtime_path(struct ct*);
|
||||||
void ipc_response_pack_boundaries(struct ct*);
|
void ipc_response_pack_boundaries(struct ct*);
|
||||||
void ipc_response_empty_and_preedit(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_response_fragmented_and_truncated(struct ct*);
|
||||||
void ipc_broken_peer_send(struct ct*);
|
void ipc_broken_peer_send(struct ct*);
|
||||||
void server_connection_ownership(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_machine_id_fallback(struct ct*);
|
||||||
void ibus_startup_requires_ownership(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_context_lifecycle(struct ct*);
|
||||||
void ibus_active_release_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"
|
#include "test.h"
|
||||||
|
|
||||||
Str
|
Str
|
||||||
@@ -21,22 +23,82 @@ checkstr(struct ct *t, char *where, char *want, Str *got)
|
|||||||
where, want, buf);
|
where, want, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
Str
|
void
|
||||||
shownpre(Im *state)
|
checkenginepreedit(struct ct *t, char *want)
|
||||||
{
|
{
|
||||||
Str mapped, shown;
|
Str preedit;
|
||||||
|
|
||||||
if(state->l->lang == LangJP || state->l->lang == LangJPK){
|
testenginepreedit(&preedit);
|
||||||
shown = state->pre;
|
checkstr(t, "engine preedit", want, &preedit);
|
||||||
if(state->raw.n == 0)
|
}
|
||||||
return shown;
|
|
||||||
if(mapget(state->l->map, &state->raw, &mapped))
|
static void
|
||||||
sappend(&shown, &mapped);
|
pumpthread(void *arg)
|
||||||
else
|
{
|
||||||
sappend(&shown, &state->raw);
|
Pump *p;
|
||||||
return shown;
|
Keyreq req;
|
||||||
}
|
uchar token;
|
||||||
if(state->l->map != nil && mapget(state->l->map, &state->pre, &shown))
|
Alt alts[] = {
|
||||||
return shown;
|
{nil, &req, CHANRCV, nil},
|
||||||
return state->pre;
|
{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"
|
#include "test.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
trie_exact_prefix_and_duplicate(struct ct *t)
|
trie_exact_prefix_and_duplicate(struct ct *t)
|
||||||
{
|
{
|
||||||
char *v, *sentinel;
|
static const struct {
|
||||||
|
char *key;
|
||||||
|
int match;
|
||||||
|
char *want;
|
||||||
|
} cases[] = {
|
||||||
|
{ "a", TrieExact, "alpha" },
|
||||||
|
{ "ab", TrieExact, "beta" },
|
||||||
|
{ "한", TrieExact, "값" },
|
||||||
|
{ "dupli", TriePrefix, nil },
|
||||||
|
{ "duplicate", TrieExact, "second" },
|
||||||
|
{ "missing", TrieMiss, nil },
|
||||||
|
{ "", TriePrefix, nil },
|
||||||
|
};
|
||||||
|
char *v;
|
||||||
Trie *trie;
|
Trie *trie;
|
||||||
int n;
|
Str key;
|
||||||
|
int i, match, n;
|
||||||
|
|
||||||
trie = trieopen("data/trie.map");
|
trie = trieopen("data/trie.map");
|
||||||
v = trieget(trie, "a", 1, &n);
|
for(i = 0; i < nelem(cases); i++){
|
||||||
if(!CT_CHECK(t, v != nil))
|
key = mkstr(cases[i].key);
|
||||||
goto cleanup;
|
match = trielookup(trie, &key, &v, &n);
|
||||||
CT_EQ_INT(t, 5, n);
|
if(match != cases[i].match){
|
||||||
CT_EQ_MEM(t, "alpha", v, n);
|
CT_ERRORF(t, "case %d: want match %d, got %d",
|
||||||
sentinel = "unchanged";
|
i, cases[i].match, match);
|
||||||
v = sentinel;
|
continue;
|
||||||
n = 77;
|
}
|
||||||
CT_CHECK(t, trielookup(trie, "dupli", 5, &v, &n));
|
if(cases[i].want == nil){
|
||||||
CT_EQ_PTR(t, sentinel, v);
|
CT_EQ_PTR(t, nil, v);
|
||||||
CT_EQ_INT(t, 77, n);
|
CT_EQ_INT(t, 0, n);
|
||||||
v = trieget(trie, "duplicate", 9, &n);
|
}else{
|
||||||
if(!CT_CHECK(t, v != nil))
|
CT_EQ_INT(t, strlen(cases[i].want), n);
|
||||||
goto cleanup;
|
CT_EQ_MEM(t, cases[i].want, v, n);
|
||||||
CT_EQ_INT(t, 6, n);
|
}
|
||||||
CT_EQ_MEM(t, "second", v, n);
|
}
|
||||||
CT_EQ_PTR(t, nil, trieget(trie, "missing", 7, &n));
|
|
||||||
cleanup:
|
|
||||||
trieclose(trie);
|
trieclose(trie);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
trie_optional_outputs_and_invalid_lengths(struct ct *t)
|
trie_put_and_unloaded(struct ct *t)
|
||||||
{
|
{
|
||||||
char *v;
|
char *v;
|
||||||
Trie *trie;
|
Trie *trie;
|
||||||
|
Str key;
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
trie = trieopen("data/trie.map");
|
key = mkstr("k");
|
||||||
CT_CHECK(t, trieget(trie, "a", 1, nil) != nil);
|
CT_EQ_INT(t, TrieMiss, trielookup(nil, &key, &v, &n));
|
||||||
v = nil;
|
trie = trienew();
|
||||||
CT_CHECK(t, trielookup(trie, "a", 1, &v, nil));
|
CT_EQ_INT(t, TrieMiss, trielookup(trie, &key, &v, &n));
|
||||||
CT_CHECK(t, v != nil);
|
trieput(trie, "k", 1, "one two", 7);
|
||||||
n = -1;
|
trieput(trie, "ka", 2, "", 0);
|
||||||
CT_CHECK(t, trielookup(trie, "a", 1, nil, &n));
|
if(CT_EQ_INT(t, TrieExact, trielookup(trie, &key, &v, &n)))
|
||||||
CT_EQ_INT(t, 5, n);
|
CT_EQ_MEM(t, "one two", v, 7);
|
||||||
CT_CHECK(t, trielookup(trie, "dupli", 5, nil, nil));
|
key = mkstr("ka");
|
||||||
CT_EQ_PTR(t, nil, trieget(trie, nil, 1, &n));
|
if(CT_EQ_INT(t, TrieExact, trielookup(trie, &key, &v, &n)))
|
||||||
CT_EQ_PTR(t, nil, trieget(trie, "a", -1, &n));
|
CT_EQ_INT(t, 0, n);
|
||||||
CT_CHECK(t, !trielookup(trie, nil, 1, &v, &n));
|
trieput(trie, "k", 1, "three", 5);
|
||||||
CT_CHECK(t, !trielookup(trie, "a", -1, &v, &n));
|
key = mkstr("k");
|
||||||
|
if(CT_EQ_INT(t, TrieExact, trielookup(trie, &key, &v, &n)))
|
||||||
|
CT_EQ_MEM(t, "three", v, 5);
|
||||||
trieclose(trie);
|
trieclose(trie);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,13 +101,12 @@ transmap_states(struct ct *t)
|
|||||||
int eat;
|
int eat;
|
||||||
char *emit;
|
char *emit;
|
||||||
char *next;
|
char *next;
|
||||||
char *mapped;
|
|
||||||
} cases[] = {
|
} cases[] = {
|
||||||
{ "", 'k', 1, "", "k", "" },
|
{ "", 'k', 1, "", "k" },
|
||||||
{ "k", 'a', 1, "", "ka", "か" },
|
{ "k", 'a', 1, "", "ka" },
|
||||||
{ "ka", 's', 1, "か", "s", "" },
|
{ "ka", 's', 1, "か", "s" },
|
||||||
{ "ka", 'q', 0, "か", "", "" },
|
{ "ka", 'q', 0, "か", "" },
|
||||||
{ "k", 'q', 0, "k", "", "" },
|
{ "k", 'q', 0, "k", "" },
|
||||||
};
|
};
|
||||||
Emit e;
|
Emit e;
|
||||||
Im state;
|
Im state;
|
||||||
@@ -110,7 +126,6 @@ transmap_states(struct ct *t)
|
|||||||
i, cases[i].eat, e.eat);
|
i, cases[i].eat, e.eat);
|
||||||
checkstr(t, "emit", cases[i].emit, &e.s);
|
checkstr(t, "emit", cases[i].emit, &e.s);
|
||||||
checkstr(t, "next", cases[i].next, &e.next);
|
checkstr(t, "next", cases[i].next, &e.next);
|
||||||
checkstr(t, "mapped", cases[i].mapped, &e.dict);
|
|
||||||
}
|
}
|
||||||
trieclose(fixture);
|
trieclose(fixture);
|
||||||
state.l->map = saved;
|
state.l->map = saved;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#define CT_IMPLEMENTATION
|
#define CT_IMPLEMENTATION
|
||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
Channel *drawc;
|
Channel *drawc;
|
||||||
Channel *keyc;
|
Channel *keyc;
|
||||||
Channel *dictreqc;
|
|
||||||
Channel *dictresc;
|
|
||||||
Lang testvi;
|
Lang testvi;
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -61,16 +61,14 @@ testmapinit(void)
|
|||||||
testvi.map = trieopen("../map/telex.map");
|
testvi.map = trieopen("../map/telex.map");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef STRESS
|
||||||
static const struct ct_test tests[] = {
|
static const struct ct_test tests[] = {
|
||||||
{ "str/init-utf8", str_init_utf8 },
|
{ "str/init-utf8", str_init_utf8 },
|
||||||
{ "str/edit-and-alias", str_edit_and_alias },
|
{ "str/edit-and-alias", str_edit_and_alias },
|
||||||
{ "str/utf8-capacity", str_utf8_capacity },
|
{ "str/utf8-capacity", str_utf8_capacity },
|
||||||
{ "str/invalid-full-appends", str_invalid_and_full_appends },
|
{ "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/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 },
|
{ "popup/layout", popup_layout },
|
||||||
{ "font/render", font_render },
|
{ "font/render", font_render },
|
||||||
{ "map/production-lifecycle", production_maps_load },
|
{ "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/backspace-clears-candidates", engine_backspace_clears_candidates },
|
||||||
{ "engine/selects-visible-candidate", engine_selects_visible_candidate },
|
{ "engine/selects-visible-candidate", engine_selects_visible_candidate },
|
||||||
{ "engine/candidate-shortcut-modifiers", engine_candidate_shortcut_modifiers },
|
{ "engine/candidate-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-lifecycle", engine_active_owner_lifecycle },
|
||||||
{ "engine/active-owner-reset", engine_active_owner_reset },
|
{ "engine/active-owner-reset", engine_active_owner_reset },
|
||||||
{ "engine/active-owner-caret", engine_active_owner_caret },
|
{ "engine/active-owner-caret", engine_active_owner_caret },
|
||||||
|
{ "engine/popup-preedit-capability", engine_popup_preedit_capability },
|
||||||
|
{ "engine/vietnamese-client-preedit", engine_vietnamese_client_preedit },
|
||||||
{ "engine/commit-contract", engine_commit_contract },
|
{ "engine/commit-contract", engine_commit_contract },
|
||||||
{ "engine/language-switch-state", engine_language_switch_state },
|
{ "engine/language-switch-state", engine_language_switch_state },
|
||||||
{ "engine/telex-history-bound", engine_telex_history_bound },
|
{ "engine/telex-history-bound", engine_telex_history_bound },
|
||||||
@@ -104,20 +107,20 @@ static const struct ct_test tests[] = {
|
|||||||
{ "engine/emoji-japanese-multirune", engine_emoji_japanese_and_multirune },
|
{ "engine/emoji-japanese-multirune", engine_emoji_japanese_and_multirune },
|
||||||
{ "engine/emoji-digit-aliases", engine_emoji_digit_aliases },
|
{ "engine/emoji-digit-aliases", engine_emoji_digit_aliases },
|
||||||
{ "engine/emoji-navigation", engine_emoji_navigation },
|
{ "engine/emoji-navigation", engine_emoji_navigation },
|
||||||
|
{ "engine/search-candidate-keys", engine_search_candidate_keys },
|
||||||
{ "engine/emoji-preedit-languages", engine_emoji_preedit_languages },
|
{ "engine/emoji-preedit-languages", engine_emoji_preedit_languages },
|
||||||
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
|
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
|
||||||
{ "engine/emoji-dictionary-identity", engine_emoji_dictionary_identity },
|
|
||||||
{ "engine/hanja-search", engine_hanja_search },
|
{ "engine/hanja-search", engine_hanja_search },
|
||||||
{ "engine/hanja-unknown-cancel", engine_hanja_unknown_and_cancel },
|
{ "engine/hanja-unknown-cancel", engine_hanja_unknown_and_cancel },
|
||||||
{ "engine/hanja-korean-keys", engine_hanja_korean_keys },
|
{ "engine/hanja-korean-keys", engine_hanja_korean_keys },
|
||||||
{ "engine/hanja-backspace", engine_hanja_backspace },
|
{ "engine/hanja-backspace", engine_hanja_backspace },
|
||||||
{ "engine/hanja-input-languages", engine_hanja_input_languages },
|
{ "engine/hanja-input-languages", engine_hanja_input_languages },
|
||||||
{ "engine/randomized-stress", engine_randomized_stress },
|
|
||||||
{ "engine/full-boundary-passthrough", engine_full_boundary_passthrough },
|
{ "engine/full-boundary-passthrough", engine_full_boundary_passthrough },
|
||||||
{ "dict/candidates", dictionary_candidates },
|
{ "dict/candidates", dictionary_candidates },
|
||||||
{ "dict/misses-clear-result", dictionary_misses_clear_result },
|
{ "dict/misses", dictionary_misses },
|
||||||
{ "dict/emoji-identity", dictionary_emoji_identity },
|
{ "dict/emoji-identity", dictionary_emoji_identity },
|
||||||
{ "ipc/masks-modifiers", ipc_masks_modifiers },
|
{ "ipc/masks-modifiers", ipc_masks_modifiers },
|
||||||
|
{ "ipc/control-caret-frames", ipc_control_and_caret_frames },
|
||||||
{ "ipc/runtime-path", ipc_runtime_path },
|
{ "ipc/runtime-path", ipc_runtime_path },
|
||||||
{ "ipc/response-pack-boundaries", ipc_response_pack_boundaries },
|
{ "ipc/response-pack-boundaries", ipc_response_pack_boundaries },
|
||||||
{ "ipc/response-empty-preedit", ipc_response_empty_and_preedit },
|
{ "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/response-fragmented-truncated", ipc_response_fragmented_and_truncated },
|
||||||
{ "ipc/broken-peer-send", ipc_broken_peer_send },
|
{ "ipc/broken-peer-send", ipc_broken_peer_send },
|
||||||
{ "server/connection-ownership", server_connection_ownership },
|
{ "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/machine-id-fallback", ibus_machine_id_fallback },
|
||||||
{ "ibus/startup-requires-ownership", ibus_startup_requires_ownership },
|
{ "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/context-lifecycle", ibus_context_lifecycle },
|
||||||
{ "ibus/active-release-lifecycle", ibus_active_release_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
|
void
|
||||||
threadmain(int argc, char **argv)
|
threadmain(int argc, char **argv)
|
||||||
@@ -138,8 +164,6 @@ threadmain(int argc, char **argv)
|
|||||||
|
|
||||||
drawc = chancreate(sizeof(Drawcmd), 4);
|
drawc = chancreate(sizeof(Drawcmd), 4);
|
||||||
keyc = chancreate(sizeof(Keyreq), 0);
|
keyc = chancreate(sizeof(Keyreq), 0);
|
||||||
dictreqc = chancreate(sizeof(Dictreq), 64);
|
|
||||||
dictresc = chancreate(sizeof(Dictres), 0);
|
|
||||||
testmapinit();
|
testmapinit();
|
||||||
status = CT_RUN_ARGS(tests, argc, argv);
|
status = CT_RUN_ARGS(tests, argc, argv);
|
||||||
for(i = 0; i < nlang; i++){
|
for(i = 0; i < nlang; i++){
|
||||||
@@ -150,7 +174,5 @@ threadmain(int argc, char **argv)
|
|||||||
testvi.map = nil;
|
testvi.map = nil;
|
||||||
chanfree(drawc);
|
chanfree(drawc);
|
||||||
chanfree(keyc);
|
chanfree(keyc);
|
||||||
chanfree(dictreqc);
|
|
||||||
chanfree(dictresc);
|
|
||||||
threadexitsall(status == 0 ? nil : "tests failed");
|
threadexitsall(status == 0 ? nil : "tests failed");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,14 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
typevi(struct ct *t, char *keys, char *want)
|
typevi(struct ct *t, char *keys, char *want)
|
||||||
{
|
{
|
||||||
Emit e;
|
Str out, raw;
|
||||||
Im state;
|
|
||||||
Str out, shown;
|
|
||||||
char *p;
|
|
||||||
|
|
||||||
memset(&state, 0, sizeof state);
|
raw = mkstr(keys);
|
||||||
state.l = &testvi;
|
transstr(&testvi, &raw, &out);
|
||||||
sclear(&out);
|
|
||||||
for(p = keys; *p != '\0'; p++){
|
|
||||||
e = transvi(&state, (uchar)*p);
|
|
||||||
sappend(&out, &e.s);
|
|
||||||
state.pre = e.next;
|
|
||||||
if(!e.eat)
|
|
||||||
sputr(&out, (uchar)*p);
|
|
||||||
}
|
|
||||||
shown = shownpre(&state);
|
|
||||||
sappend(&out, &shown);
|
|
||||||
checkstr(t, keys, want, &out);
|
checkstr(t, keys, want, &out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +35,11 @@ vietnamese_transitions(struct ct *t)
|
|||||||
{ "gias", "giá" },
|
{ "gias", "giá" },
|
||||||
{ "Gias", "Giá" },
|
{ "Gias", "Giá" },
|
||||||
{ "gif", "gì" },
|
{ "gif", "gì" },
|
||||||
|
{ "cuar", "của" },
|
||||||
|
{ "vowis", "với" },
|
||||||
|
{ "cuwar", "cửa" },
|
||||||
|
{ "nguyeenx", "nguyễn" },
|
||||||
|
{ "dduwowngf", "đường" },
|
||||||
{ "\\s", "s" },
|
{ "\\s", "s" },
|
||||||
{ "a\\s", "as" },
|
{ "a\\s", "as" },
|
||||||
};
|
};
|
||||||
@@ -78,7 +72,7 @@ vietnamese_backspace(struct ct *t)
|
|||||||
state.raw = mkstr(cases[i].raw);
|
state.raw = mkstr(cases[i].raw);
|
||||||
backvi(&state);
|
backvi(&state);
|
||||||
checkstr(t, cases[i].raw, cases[i].rawafter, &state.raw);
|
checkstr(t, cases[i].raw, cases[i].rawafter, &state.raw);
|
||||||
shown = shownpre(&state);
|
impre(&state, &shown);
|
||||||
checkstr(t, cases[i].raw, cases[i].after, &shown);
|
checkstr(t, cases[i].raw, cases[i].after, &shown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 "dat.h"
|
||||||
#include "fn.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
|
static int
|
||||||
newnode(Trie *t)
|
newnode(Trie *t)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if(t->n >= t->cap){
|
if(t->n == t->cap){
|
||||||
t->cap *= 2;
|
t->cap *= 2;
|
||||||
t->nodes = erealloc(t->nodes, t->cap * sizeof(Tnode));
|
t->nodes = erealloc(t->nodes, t->cap * sizeof(Tnode));
|
||||||
}
|
}
|
||||||
@@ -40,13 +47,25 @@ add(Trie *t, int ni, char c)
|
|||||||
return pi;
|
return pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
Trie*
|
||||||
insert(Trie *t, char *key, int klen, char *val, int vlen)
|
trienew(void)
|
||||||
{
|
{
|
||||||
int ni, ci;
|
Trie *t;
|
||||||
int i;
|
|
||||||
|
|
||||||
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++){
|
for(i = 0; i < klen; i++){
|
||||||
ci = find(t, ni, key[i]);
|
ci = find(t, ni, key[i]);
|
||||||
if(ci < 0)
|
if(ci < 0)
|
||||||
@@ -60,47 +79,64 @@ insert(Trie *t, char *key, int klen, char *val, int vlen)
|
|||||||
t->nodes[ni].vlen = 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*
|
Trie*
|
||||||
trieopen(char *path)
|
trieopen(char *path)
|
||||||
{
|
{
|
||||||
Trie *t;
|
Trie *t;
|
||||||
Biobuf *b;
|
Biobuf *b;
|
||||||
char *line, *tab, *key, *val;
|
Str s;
|
||||||
int klen, vlen;
|
char *e, *line, *p, *tab;
|
||||||
|
int len, lineno;
|
||||||
|
|
||||||
b = Bopen(path, OREAD);
|
b = Bopen(path, OREAD);
|
||||||
if(b == nil)
|
if(b == nil)
|
||||||
die("can't open: %s", path);
|
die("can't open %s: %r", path);
|
||||||
t = emalloc(sizeof(*t));
|
t = trienew();
|
||||||
t->cap = 1024;
|
for(lineno = 1; (line = readline(b, path)) != nil; lineno++){
|
||||||
t->nodes = emalloc(t->cap * sizeof(Tnode));
|
len = Blinelen(b);
|
||||||
t->n = 0;
|
if(memchr(line, '\0', len) != nil)
|
||||||
t->root = newnode(t);
|
die("%s:%d: NUL byte", path, lineno);
|
||||||
while((line = Brdstr(b, '\n', 1)) != nil){
|
if(len > 0 && line[len-1] == '\r')
|
||||||
vlen = strlen(line);
|
line[--len] = '\0';
|
||||||
if(vlen > 0 && line[vlen-1] == '\r')
|
if(len == 0 || line[0] == ';'){
|
||||||
line[--vlen] = '\0';
|
|
||||||
if(line[0] == '\0' || line[0] == ';'){
|
|
||||||
free(line);
|
free(line);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tab = strchr(line, '\t');
|
tab = memchr(line, '\t', len);
|
||||||
if(tab == nil || tab == line || tab[1] == '\0' ||
|
if(tab == nil || tab == line || tab == line+len-1 ||
|
||||||
strchr(tab+1, '\t') != nil)
|
memchr(tab+1, '\t', line+len-(tab+1)) != nil)
|
||||||
die("malformed map: %s", path);
|
die("%s:%d: expected key, tab, value", path, lineno);
|
||||||
*tab = '\0';
|
if(!sinit(&s, line, tab-line))
|
||||||
key = line;
|
die("%s:%d: invalid or oversized key", path, lineno);
|
||||||
klen = tab - line;
|
for(p = tab+1; p < line+len; p = e+1){
|
||||||
if(utflen(key) > Maxrunes)
|
e = memchr(p, ' ', line+len-p);
|
||||||
die("map key too long: %s", path);
|
if(e == nil)
|
||||||
val = tab + 1;
|
e = line+len;
|
||||||
vlen = strlen(val);
|
if(!sinit(&s, p, e-p))
|
||||||
if(utflen(val) > Maxrunes)
|
die("%s:%d: invalid or oversized value", path, lineno);
|
||||||
die("map value too long: %s", path);
|
}
|
||||||
insert(t, key, klen, val, vlen);
|
trieput(t, line, tab-line, tab+1, line+len-(tab+1));
|
||||||
free(line);
|
free(line);
|
||||||
}
|
}
|
||||||
Bterm(b);
|
if(Bterm(b) < 0)
|
||||||
|
die("can't close %s: %r", path);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,46 +153,27 @@ trieclose(Trie *t)
|
|||||||
free(t);
|
free(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
char*
|
/* A nil trie is an unloaded map: every key misses. */
|
||||||
trieget(Trie *t, char *key, int klen, int *vlen)
|
int
|
||||||
|
trielookup(Trie *t, Str *key, char **val, int *vlen)
|
||||||
{
|
{
|
||||||
int ni;
|
char buf[Maxutf];
|
||||||
int i;
|
int i, klen, ni;
|
||||||
|
|
||||||
if(t == nil || klen < 0 || (klen > 0 && key == nil))
|
*val = nil;
|
||||||
return nil;
|
*vlen = 0;
|
||||||
ni = t->root;
|
if(t == nil)
|
||||||
|
return TrieMiss;
|
||||||
|
klen = stoutf(key, buf, sizeof buf);
|
||||||
|
ni = 0;
|
||||||
for(i = 0; i < klen; i++){
|
for(i = 0; i < klen; i++){
|
||||||
ni = find(t, ni, key[i]);
|
ni = find(t, ni, buf[i]);
|
||||||
if(ni < 0)
|
if(ni < 0)
|
||||||
return nil;
|
return TrieMiss;
|
||||||
}
|
}
|
||||||
if(t->nodes[ni].val == nil)
|
if(t->nodes[ni].val == nil)
|
||||||
return nil;
|
return TriePrefix;
|
||||||
if(vlen != nil)
|
*val = t->nodes[ni].val;
|
||||||
*vlen = t->nodes[ni].vlen;
|
*vlen = t->nodes[ni].vlen;
|
||||||
return t->nodes[ni].val;
|
return TrieExact;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
69
vi.c
69
vi.c
@@ -31,12 +31,6 @@ static struct {
|
|||||||
{L'Y', {L'Ý', L'Ỳ', L'Ỷ', L'Ỹ', L'Ỵ'}},
|
{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
|
static int
|
||||||
toneidx(Rune c)
|
toneidx(Rune c)
|
||||||
{
|
{
|
||||||
@@ -114,20 +108,44 @@ onsetglide(Str *m)
|
|||||||
return -1;
|
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;
|
Str mapped, pre;
|
||||||
int i, tidx, vi, last, penult, glide;
|
int i, tidx, vi, last, penult, glide;
|
||||||
Rune v, b1, b2;
|
Rune v, b1, b2;
|
||||||
|
|
||||||
if(!istone(c) && c != 'z')
|
e = transmap(im, c);
|
||||||
return transmap(im, c);
|
if(e.eat)
|
||||||
mappedkey = transmap(im, c);
|
return e;
|
||||||
if(mappedkey.eat)
|
|
||||||
return mappedkey;
|
|
||||||
|
|
||||||
memset(&e, 0, sizeof e);
|
memset(&e, 0, sizeof e);
|
||||||
if(im->pre.n == 0)
|
if(im->pre.n == 0)
|
||||||
return e;
|
return e;
|
||||||
@@ -175,17 +193,24 @@ transvi(Im *im, Rune c)
|
|||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(c == 'z')
|
tidx = toneidx(c);
|
||||||
|
if(tidx < 0)
|
||||||
mapped.r[vi] = removetone(mapped.r[vi]);
|
mapped.r[vi] = removetone(mapped.r[vi]);
|
||||||
else{
|
else
|
||||||
tidx = toneidx(c);
|
|
||||||
mapped.r[vi] = applytone(mapped.r[vi], tidx);
|
mapped.r[vi] = applytone(mapped.r[vi], tidx);
|
||||||
}
|
|
||||||
e.eat = 1;
|
e.eat = 1;
|
||||||
e.next = mapped;
|
e.next = mapped;
|
||||||
return e;
|
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
|
void
|
||||||
backvi(Im *im)
|
backvi(Im *im)
|
||||||
{
|
{
|
||||||
@@ -200,10 +225,10 @@ backvi(Im *im)
|
|||||||
raw = im->raw;
|
raw = im->raw;
|
||||||
spopr(&raw);
|
spopr(&raw);
|
||||||
sclear(&im->pre);
|
sclear(&im->pre);
|
||||||
|
sclear(&im->raw);
|
||||||
for(i = 0; i < raw.n; i++){
|
for(i = 0; i < raw.n; i++){
|
||||||
e = transvi(im, raw.r[i]);
|
e = transvi(im, raw.r[i]);
|
||||||
sclear(&im->pre);
|
im->pre = e.next;
|
||||||
sappend(&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/xcb.h>
|
||||||
|
#include <xcb/randr.h>
|
||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
enum {
|
|
||||||
Asciitofull = 0xFEE0,
|
|
||||||
};
|
|
||||||
|
|
||||||
extern char **fontfiles;
|
|
||||||
extern int nfontfiles;
|
|
||||||
|
|
||||||
static xcb_connection_t *conn;
|
static xcb_connection_t *conn;
|
||||||
static xcb_screen_t *scr;
|
static xcb_screen_t *scr;
|
||||||
static xcb_window_t win;
|
static xcb_window_t win;
|
||||||
static xcb_gcontext_t gc;
|
static xcb_gcontext_t gc;
|
||||||
static xcb_pixmap_t pix;
|
static xcb_pixmap_t pix;
|
||||||
static u32int *img;
|
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*
|
static xcb_screen_t*
|
||||||
getscr(xcb_connection_t *c, int n)
|
getscr(xcb_connection_t *c, int n)
|
||||||
@@ -46,6 +42,7 @@ getvisual(xcb_screen_t *s)
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* putimage writes native-endian x8r8g8b8; the root must take that. */
|
||||||
static int
|
static int
|
||||||
validformat(xcb_connection_t *c, xcb_screen_t *s)
|
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);
|
setup = xcb_get_setup(c);
|
||||||
v = getvisual(s);
|
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->_class != XCB_VISUAL_CLASS_TRUE_COLOR ||
|
||||||
v->bits_per_rgb_value != 8 ||
|
|
||||||
v->red_mask != 0xff0000 || v->green_mask != 0x00ff00 ||
|
v->red_mask != 0xff0000 || v->green_mask != 0x00ff00 ||
|
||||||
v->blue_mask != 0x0000ff)
|
v->blue_mask != 0x0000ff)
|
||||||
return 0;
|
return 0;
|
||||||
@@ -76,19 +72,129 @@ validformat(xcb_connection_t *c, xcb_screen_t *s)
|
|||||||
return 0;
|
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
|
static void
|
||||||
wincleanup(void)
|
wincleanup(void)
|
||||||
{
|
{
|
||||||
|
textclose();
|
||||||
free(img);
|
free(img);
|
||||||
img = nil;
|
xcb_disconnect(conn);
|
||||||
if(conn != nil){
|
|
||||||
xcb_disconnect(conn);
|
|
||||||
conn = nil;
|
|
||||||
}
|
|
||||||
scr = nil;
|
|
||||||
win = 0;
|
|
||||||
gc = 0;
|
|
||||||
pix = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -96,11 +202,17 @@ wininit(void)
|
|||||||
{
|
{
|
||||||
int n;
|
int n;
|
||||||
u32int mask, vals[4];
|
u32int mask, vals[4];
|
||||||
xcb_intern_atom_cookie_t c1, c2;
|
xcb_atom_t type, tooltip;
|
||||||
xcb_intern_atom_reply_t *r1, *r2;
|
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);
|
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");
|
fprint(2, "strans: popup disabled: cannot connect to X display\n");
|
||||||
wincleanup();
|
wincleanup();
|
||||||
return 0;
|
return 0;
|
||||||
@@ -111,7 +223,16 @@ wininit(void)
|
|||||||
wincleanup();
|
wincleanup();
|
||||||
return 0;
|
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);
|
win = xcb_generate_id(conn);
|
||||||
mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
|
mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
|
||||||
XCB_CW_OVERRIDE_REDIRECT | XCB_CW_SAVE_UNDER;
|
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,
|
xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, scr->root,
|
||||||
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||||
scr->root_visual, mask, vals);
|
scr->root_visual, mask, vals);
|
||||||
c1 = xcb_intern_atom(conn, 0,
|
type = getatom("_NET_WM_WINDOW_TYPE");
|
||||||
strlen("_NET_WM_WINDOW_TYPE"), "_NET_WM_WINDOW_TYPE");
|
tooltip = getatom("_NET_WM_WINDOW_TYPE_TOOLTIP");
|
||||||
c2 = xcb_intern_atom(conn, 0,
|
if(type != XCB_ATOM_NONE && tooltip != XCB_ATOM_NONE)
|
||||||
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)
|
|
||||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE,
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE,
|
||||||
win, r1->atom, XCB_ATOM_ATOM, 32, 1, &r2->atom);
|
win, type, XCB_ATOM_ATOM, 32, 1, &tooltip);
|
||||||
free(r1);
|
|
||||||
free(r2);
|
|
||||||
gc = xcb_generate_id(conn);
|
gc = xcb_generate_id(conn);
|
||||||
xcb_create_gc(conn, gc, win, 0, nil);
|
xcb_create_gc(conn, gc, win, 0, nil);
|
||||||
pix = xcb_generate_id(conn);
|
if(!textinit()){
|
||||||
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)){
|
|
||||||
fprint(2, "strans: popup disabled: no usable fonts\n");
|
fprint(2, "strans: popup disabled: no usable fonts\n");
|
||||||
wincleanup();
|
wincleanup();
|
||||||
return 0;
|
return 0;
|
||||||
@@ -149,53 +258,50 @@ wininit(void)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
/* The image and its retained pixmap only ever grow. */
|
||||||
drawstr(u32int *buf, int x, int y, Rune *r, int n, int maxw, int maxh)
|
static int
|
||||||
|
resizebacking(int w, int h)
|
||||||
{
|
{
|
||||||
while(n-- > 0){
|
xcb_generic_error_t *err;
|
||||||
if(*r != 0xfe0e && *r != 0xfe0f){
|
xcb_pixmap_t old, new;
|
||||||
putfont(buf, maxw, maxh, x, y, *r);
|
xcb_void_cookie_t cookie;
|
||||||
x += Fontsz;
|
int nh, nw;
|
||||||
}
|
|
||||||
r++;
|
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;
|
||||||
}
|
}
|
||||||
}
|
cookie = xcb_change_window_attributes_checked(conn, win,
|
||||||
|
XCB_CW_BACK_PIXMAP, &new);
|
||||||
static void
|
err = xcb_request_check(conn, cookie);
|
||||||
fill(u32int *buf, int n, u32int color)
|
if(err != nil){
|
||||||
{
|
free(err);
|
||||||
int i;
|
xcb_free_pixmap(conn, new);
|
||||||
|
return 0;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
old = pix;
|
||||||
|
pix = new;
|
||||||
|
imgw = nw;
|
||||||
|
imgh = nh;
|
||||||
|
if(old != 0)
|
||||||
|
xcb_free_pixmap(conn, old);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
putimage(int w, int h)
|
putimage(int w, int h)
|
||||||
{
|
{
|
||||||
xcb_put_image(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc,
|
xcb_put_image(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc,
|
||||||
w, h, 0, 0, 0, depth, w * h * 4, (u8int*)img);
|
w, h, 0, 0, 0, scr->root_depth, w * h * 4, (u8int*)img);
|
||||||
/* The retained background pixmap lets the server repaint exposures. */
|
/* The retained background pixmap lets the server repaint exposures. */
|
||||||
xcb_clear_area(conn, 0, win, 0, 0, w, h);
|
xcb_clear_area(conn, 0, win, 0, 0, w, h);
|
||||||
}
|
}
|
||||||
@@ -207,24 +313,19 @@ winhide(void)
|
|||||||
return xcb_flush(conn) > 0;
|
return xcb_flush(conn) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Draws dc at the caret, or by the pointer when the caret is unknown. */
|
||||||
static int
|
static int
|
||||||
winshow(Drawcmd *dc)
|
winshow(Drawcmd *dc)
|
||||||
{
|
{
|
||||||
int npre, px, py, w, h, i, n, maxw;
|
Area area;
|
||||||
u32int vals[4];
|
Popup p;
|
||||||
|
int ax, ay, px, py, x, y;
|
||||||
|
u32int vals[5];
|
||||||
xcb_query_pointer_reply_t *ptr;
|
xcb_query_pointer_reply_t *ptr;
|
||||||
xcb_query_pointer_cookie_t cookie;
|
xcb_query_pointer_cookie_t cookie;
|
||||||
|
|
||||||
n = min(max(dc->nkouho, 0), Maxdisp);
|
if(dc->nkouho == 0 && dc->pre.n == 0)
|
||||||
npre = dc->pre.n != 0;
|
|
||||||
if(n == 0 && npre == 0){
|
|
||||||
return winhide();
|
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;
|
px = py = 0;
|
||||||
if(!dc->caret.valid){
|
if(!dc->caret.valid){
|
||||||
cookie = xcb_query_pointer(conn, scr->root);
|
cookie = xcb_query_pointer(conn, scr->root);
|
||||||
@@ -235,17 +336,26 @@ winshow(Drawcmd *dc)
|
|||||||
py = ptr->root_y;
|
py = ptr->root_y;
|
||||||
free(ptr);
|
free(ptr);
|
||||||
}
|
}
|
||||||
popupposition(&dc->caret, px, py, scr->width_in_pixels,
|
ax = dc->caret.valid ? dc->caret.x : px;
|
||||||
scr->height_in_pixels, w, h, &px, &py);
|
ay = dc->caret.valid ? dc->caret.y : py;
|
||||||
vals[1] = py;
|
popupwork(ax, ay, &area);
|
||||||
vals[0] = px;
|
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_configure_window(conn, win,
|
||||||
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
||||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
|
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
|
||||||
vals);
|
XCB_CONFIG_WINDOW_STACK_MODE, vals);
|
||||||
|
popupdraw(img, dc, &p);
|
||||||
|
putimage(p.w, p.h);
|
||||||
xcb_map_window(conn, win);
|
xcb_map_window(conn, win);
|
||||||
drawkouho(dc, n, w, h);
|
|
||||||
putimage(w, h);
|
|
||||||
return xcb_flush(conn) > 0;
|
return xcb_flush(conn) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,14 +367,8 @@ drawthread(void*)
|
|||||||
threadsetname("draw");
|
threadsetname("draw");
|
||||||
if(!wininit())
|
if(!wininit())
|
||||||
return;
|
return;
|
||||||
while(chanrecv(drawc, &dc) > 0){
|
while(chanrecv(drawc, &dc) > 0)
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
if(!winshow(&dc))
|
||||||
;
|
|
||||||
if(dc.nkouho == 0 && dc.pre.n == 0){
|
|
||||||
if(!winhide())
|
|
||||||
break;
|
|
||||||
}else if(!winshow(&dc))
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
wincleanup();
|
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 <stdint.h>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
#include <xkbcommon/xkbcommon.h>
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
#include <xkbcommon/xkbcommon-names.h>
|
||||||
|
|
||||||
enum
|
/* The keysym for an X core key event, with its modifier and group state. */
|
||||||
{
|
|
||||||
ShiftMask = 1<<0,
|
|
||||||
LockMask = 1<<1,
|
|
||||||
GroupShift = 13,
|
|
||||||
GroupMask = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
uint32_t
|
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;
|
static const char *modname[] = {
|
||||||
int col, group, shift;
|
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)
|
keymap = xkb_state_get_keymap(state);
|
||||||
return XKB_KEY_NoSymbol;
|
mods = 0;
|
||||||
group = (state >> GroupShift) & GroupMask;
|
for(i = 0; i < (int)(sizeof modname / sizeof modname[0]); i++){
|
||||||
col = group * 2;
|
if(!(corestate & (1U << i)))
|
||||||
if(col >= nsyms || syms[col] == XKB_KEY_NoSymbol)
|
continue;
|
||||||
col = 0;
|
index = xkb_keymap_mod_get_index(keymap, modname[i]);
|
||||||
lo = syms[col];
|
if(index != XKB_MOD_INVALID)
|
||||||
if(lo == XKB_KEY_NoSymbol)
|
mods |= (xkb_mod_mask_t)1 << index;
|
||||||
return XKB_KEY_NoSymbol;
|
}
|
||||||
hi = col + 1 < nsyms ? syms[col + 1] : XKB_KEY_NoSymbol;
|
xkb_state_update_mask(state, mods, 0, 0, 0, 0,
|
||||||
lower = xkb_keysym_to_lower(lo);
|
(corestate >> 13) & 3);
|
||||||
upper = xkb_keysym_to_upper(lo);
|
return xkb_state_key_get_one_sym(state, keycode);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
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