Compare commits

...

94 Commits

Author SHA1 Message Date
ea363ca792 font: initialise fontconfig ourselves
fontconfig 2.16 warns on every start when Pango reaches it before
FcInit(); calling it first keeps stderr quiet.
2026-08-16 21:41:36 +09:00
bab6077c75 srv, ibus: remove the endpoints on a signal too
A stopped daemon left its socket and IBus address file behind, since
plan9port ends a process on SIGTERM without running exit handlers. Both
files now go on a note as well as on exit, and only while they are still
the ones this daemon made, so a successor is never robbed of its own.
2026-08-16 21:40:42 +09:00
48d3e165c3 xim: dead keys and Compose
Xlib does no local composing for a client on an input method server,
so under XIM a dead key was forwarded as a bare keysym and vanished.
The XIM frontend now feeds keys through xkbcommon's Compose table for
the daemon's locale and commits the composed character itself.
2026-08-16 21:39:22 +09:00
6b1de2df4f popup: flip above the pointer at the bottom edge
Without a caret the popup sat at pointer+10 and was clamped upward at
the bottom of the work area, covering the pointer; it now goes above the
pointer like it goes above a caret.
2026-08-16 21:36:31 +09:00
45c6be7e1b docs: rewrap the popup paragraph 2026-08-16 21:35:51 +09:00
4ff696d06c popup: follow GDK_SCALE on HiDPI
The popup was 32-pixel rows whatever the display; next to 2x application
text it was small. Its metrics now scale with the daemon's GDK_SCALE,
the setting the applications use.
2026-08-16 21:35:43 +09:00
30abd05e68 engine: room for 128 candidates
32 hid the tail of common readings: きょう has 41 kanji, 구 has 352
hanja. 128 covers every kanji entry and the hanja dictionary now keeps
that many per reading.
2026-08-16 21:33:18 +09:00
693a1210a6 engine: romaji is case-blind
Shift or Caps Lock made a Japanese key pass through as a Latin capital,
so nothing composed with Caps Lock on; Korean already folds case. Latin
text still has Ctrl+T.
2026-08-16 21:30:59 +09:00
91b3e538f1 engine: Ctrl+H converts the pending syllable; Backspace on an empty search stays put
Ctrl+H committed the syllable being composed and opened an empty search,
so converting 한 meant typing it twice; the pending syllable now seeds the
query. Backspace on an already empty search ended it and then reached the
application, deleting a character; it now only ends the search.
2026-08-16 21:30:16 +09:00
5e72a48c2c engine: Korean and Telex let Enter and Tab through after committing
Enter with a pending syllable committed it and stopped there, so a chat
message needed Enter twice; Korean input methods commit and pass the key
on. Japanese keeps consuming the key that confirms a reading or a chosen
candidate, as Japanese input methods do.
2026-08-16 21:28:11 +09:00
11bf379ad3 fix(engine): a reset or a lost focus commits the pending text
Clicking elsewhere, changing focus, or any client reset dropped the
composition in the GTK module and IBus (only XIM ResetIC handed it
back), so typing 안녕 and clicking Send lost 녕. The engine now returns
the pending text — a moved-to candidate first, as Enter would — on
Keyreset and Keyrelease; the GTK module asks for it before closing on
focus-out and commits it, IBus commits it on FocusOut, Reset and a
switch to a password field, and XIM commits it on focus loss and hands
it back on ResetIC without a separate capability probe.
2026-08-16 21:05:31 +09:00
4c8954b8ae test: keep the IBus startup check off a desktop's IBUS_ADDRESS_FILE
buildaddrpath honours IBUS_ADDRESS_FILE, so under an IBus session the
ownership check looked at the live address file and failed.
2026-08-16 20:32:20 +09:00
ae1bc51256 data(kana): one sokuon rule for every doubled consonant, and the missing hira rows
Hiragana spelled out 42 doubled syllables (kka, ssha, ttsu, ...) and so
knew none it did not list: matcha, baggu and beddo came out as ma-tc-ha,
ba-gg-u. Katakana already used the engine's small-tsu rule for a few
doubles. Both maps now list every doubled consonant but n, plus tch, and
hiragana gains di, che and fa/fi/fe/fo like katakana.
2026-08-16 20:31:48 +09:00
e860008d84 fix(telex): know the rimes Telex needs, and put the ưa tone on ư
The map is the syllable grammar: a key sequence stays pending only
while it is a prefix of some entry, so a rime the table lacked split
the syllable and a trailing tone key landed on the wrong letter (vowis
gave vơí, rooif rôì, thaays thâý), ua+tone was a valueless prefix that
committed the raw keys (cuar gave cuar, muaf muaf), uyê and ươ typed as
uwow took no coda (nguyễn, được), and ưa toned the a (cửa gave cưả).
Add âu ây êu ôi ơi eo ia ưi ưu uê ươu uây oeo uya uyu, the uyê, uwow,
uê and oe codas, and tone ưa on ư.
2026-08-16 20:30:21 +09:00
32a9723043 fix(engine): the search keys commit the query they end or replace
Ctrl+E during an emoji search, Ctrl+H during a hanja search, or either
during the other search dropped the typed query, while every other
Ctrl key committed it as the README says. Escape is the way to cancel;
the search keys now commit the shown text like a language switch.
2026-08-16 20:27:31 +09:00
6402c26830 fix(engine): movement keys without candidates commit the pending text
Up, Down, PageUp and PageDown with a composition pending and nothing to
move through returned the key uneaten but left the text pending, so the
client moved its cursor and reset the context: the pending Korean
syllable vanished, where Left or Home would have committed it. Let them
fall through to the special-key path that commits and passes the key.
2026-08-16 20:26:28 +09:00
9853d65d4c fix(engine): keep composing when a reading reaches Maxrunes
At the limit the key that did not fit was handed back to the client as
plain ASCII, so a long Japanese reading ended in a Latin letter, and a
pending romaji consonant was flushed as ASCII with it (…あka for か).
Commit the completed kana, keep the pending syllable, and transliterate
the key as usual; other languages commit and go on the same way.
2026-08-16 20:25:38 +09:00
cdd3e38195 fix(ibus): drop a context on the Service.Destroy libibus really sends
libibus destroys an input context with Destroy on
org.freedesktop.IBus.Service, not on the InputContext interface, so the
call went unanswered and the context slot stayed taken until the whole
connection closed. A long-lived GTK process that opens and closes text
widgets exhausted the 1024 slots and then got LimitsExceeded for every
new field. The live contract test now destroys a context that way and
checks the path is gone.
2026-08-16 20:24:26 +09:00
f6adb117c9 fix(popup): raise the popup whenever it is shown
The popup window is created once at startup and only mapped afterwards,
and MapWindow does not restack, so every application window (or WM
frame) opened after the daemon sat above it: candidates and preedit were
invisible in those windows until the daemon restarted. Configure the
window above its siblings on every show. The XIM live test now checks
the popup stacks above a client window created after the daemon.
2026-08-16 20:23:29 +09:00
333300b297 ipc: fold Shift+Tab and the keypad Enter and Tab onto Tab and Enter
Shift+Tab arrives from every frontend as ISO_Left_Tab (0xfe20), which
has no Unicode value and lies outside the function keysym range, so
ipckeysym gave key 0 and the documented Shift-Tab wrap through search
results never reached the engine; GTK moved focus instead. KP_Enter and
KP_Tab likewise became unknown special keys, so the keypad Enter
committed the reading and passed a newline instead of taking the
highlighted candidate.
2026-08-16 20:22:17 +09:00
4b6cc6fb2f test: name the release-then-drop check the two IBus lifecycle tests share 2026-08-16 18:37:29 +09:00
0aef3506b6 str: drop argument checks no caller can trip
sinit's nil and negative-length tests and stoutf's zero-size test only
had test callers; every real caller passes a buffer and its size. The
len > n check after fullrune succeeded could never fire.
2026-08-16 18:36:40 +09:00
723a271a9c build: name the Valgrind run the container is prepared for
The Dockerfile installs valgrind and the ld.so debug info it needs, but
no target used them; docker-valgrind runs the unit suite under it, so
the dependency is visible and the run is one command.
2026-08-16 18:29:03 +09:00
3f1ff03b94 popup: keep only computed geometry in Popup; one fallback for slivers
numx, textx and selx were compile-time constants stored per layout;
the number column starts at PopupPad and text at PopupPad+PopupNumw,
which dat.h now says once. The height fallback tried dropping the
marker, then the preedit, then the padding as three separate refits;
below one padded row it now shows as many bare rows as fit, at least
one, which is what those steps added up to.
2026-08-16 18:27:24 +09:00
68678283cd ibus: room for a thousand input contexts
Toolkits keep one IBus context per widget that ever took focus, so 64
across every client could run out in a long session, after which new
widgets silently got no input method. The table is static and small
(about 100 bytes a slot).
2026-08-16 18:25:55 +09:00
03c196a7f6 ibus: keep only the content purpose; nothing read the hints 2026-08-16 16:58:12 +09:00
8831b84ffc ipc: say what the want flag and the capability probe mean now 2026-08-16 16:57:15 +09:00
45c0290fd4 build: ignore a stale xim_test binary
The old xim/Makefile built xim/xim_test; nothing does now, but older
checkouts may still have it, so keep the name ignored.
2026-08-16 16:56:56 +09:00
dde9fb431b test: probe the dead IBus address in-process
rejectold forked and re-execed the test binary with --reject-address
just to call dbus_connection_open_private once against the crashed
daemon's abstract address, then waited on the helper, drained its
stderr pipe and checked its exit status. An abstract socket with no
listener refuses the connection immediately, so the call cannot block
and needs no separate process.

Open the address in-process and require a null connection with the
D-Bus error set, freeing the error afterwards. This drops the helper
process, its stderr capture, the Test.helper fields and the argv
dispatch in main. killdaemon stays; the daemons still need it.
2026-08-16 16:44:05 +09:00
7220286ab5 test: share one daemon and D-Bus harness across live tests
Six live tests carried private copies of the same daemon harness: the
private XDG_RUNTIME_DIR, the fork/exec with captured stderr, the
readiness waits, the SIGTERM and SIGKILL paths, the IBus address file
reader, the socket connect, and the IPC probe. Three of them also
carried the same libdbus helpers.

Extract one implementation into tests/live.c and, so that the binaries
that do not link dbus-1 keep not linking it, the D-Bus half into
tests/livebus.c. Both are compiled into each binary the way ../ipc.c
already is. gtk_live_test keeps its own fake-server harness and only
takes fail, nowms and leftms; xim_live_test keeps its own Xvfb and
process-group daemon spawn, which must inherit DISPLAY, and takes the
temporary directory, timing and cleanup halves.

The copies had drifted; the harness keeps the stricter behaviour.

  - nowms reports a broken clock (-1) instead of pretending it read
    zero, and leftms turns that into an expired deadline, so a loop
    ends in a timeout failure rather than spinning. livesetup checks
    the clock once up front, as daemon_restart_test did.
  - Timeouts compare with <= 0, not == 0.
  - readuntil keeps the three-way result (complete, peer closed, error)
    from ipc_live_test and daemon_restart_test rather than folding
    peer closure into ECONNRESET.
  - The daemon's stdout and stderr are both captured, and a failing
    setenv is reported, for every daemon; daemon_failure_test captured
    stderr only and said nothing about setenv.
  - The child keeps daemon_restart_test's careful redirect that also
    works when the pipe lands on fd 1 or 2.
  - parseaddress requires a positive declared PID.
  - killdaemon reports ECHILD after SIGKILL as a failure; one copy
    accepted it. It now reaps with a blocking waitpid, which SIGKILL
    guarantees will return, instead of daemon_restart_test's polled
    wait with its own timeout diagnostic.
  - stopdaemon tolerates ESRCH on SIGTERM, a benign race two copies
    reported as an error.
  - liveclean removes the socket and address files and then rmdirs
    each directory, reporting leftovers, rather than deleting the
    temporary root recursively.
  - ibus_live_test now waits for the IPC socket and the address file
    by polling, dropping its inotify variant; it asserted only the
    address file before.
2026-08-16 16:44:05 +09:00
e1c36af9ba gtk: drop two includes nothing uses 2026-08-16 16:32:37 +09:00
7c73e4a463 docs: describe XIM text and placement as they now are 2026-08-16 16:31:07 +09:00
795f11be22 test: one engine pump and one preedit check for the white-box suites
server_test, ibus_test and xim_adapter_test each ran their own copy of
the same alt loop that stands in for imthread, and two of them had
private checkstr/checkenginepreedit variants. test_util.c now provides
Pump (trace, optional hold on a chosen op, stop) in its own proc so a
test may block in socket I/O while the engine runs, and test.h declares
the engine hooks once; every .c includes dat.h and fn.h itself as the
rest of the tree does. server_test's three copies of fixture setup and
teardown became serverbegin/serverend.
2026-08-16 16:30:32 +09:00
826da31486 test: drive Korean and Vietnamese through the engine's own transstr
ko_test and vi_test replayed keys with private copies of transstr's
loop, and test_util reimplemented the shown preedit; the engine exports
impre(Im*, Str*) and transstr() instead, so a change to composition
rules is one edit.
2026-08-16 16:25:40 +09:00
849bf1278b popup: one text drawing call; drop what the draw thread never reaches
font.c exported three wrappers for one function, and the fit == -1 mode
existed only for the tests; textdraw() takes fit and colour. win.c
tested the empty picture in the thread and again in winshow, interned
two atoms by hand next to getatom(), kept a consumer-side drain that the
producer's already guarantees never finds anything, zeroed ten globals
before returning from the only thread that read them, and re-checked
sizes that resizebacking had just established. The page marker is laid
out once and drawn from the layout instead of being recomputed.
2026-08-16 16:22:37 +09:00
eaf31da0d1 ibus: one reply path, no ceremony around calls that cannot fail
Six handlers built method returns by hand and only two of them checked
for a nil message; reply() builds them all and dies on OOM like the
rest of the daemon. DBusError objects were initialised and freed but
never read: libdbus accepts nil. Properties.Get always errored, so it
is one line in onmsg; the introspection XML served nobody (libibus
never asks) and had to be kept in sync by hand. Ibushinthidden was not
an IBus hint, so that term of hidden() never fired. writeaddr uses the
syscalls directly and the address file's dev/ino is the ownership
proof; buildaddrpath makes the directory in place. An empty
UpdatePreeditText goes out only to the context that shows a preedit.
2026-08-16 16:20:10 +09:00
c2baa47026 fix(xim): follow keyboard layout changes
kinit() sets up the XKB extension, after which the server stops sending
core MappingNotify to this client, so the refresh branch never fired and
strans kept its startup keymap; setxkbmap after launch left every XIM
key resolved against the old layout. Select XKB NewKeyboardNotify and
MapNotify (with the map parts, or MapNotify is never delivered) and
rebuild the keymap on those events instead.
2026-08-16 16:16:44 +09:00
a70cfc42e3 xim: drop what no client uses; poll for owner loss like IBus
XIM text always went out through imdkit's COMPOUND_TEXT converter,
which already wraps every UTF-8 string in ESC%G; the UTF8_STRING
negotiation, the per-client encoding list, ximtext.c's fallback and its
wrapper never changed a byte on the wire. The spot location is honoured
for every style now (GTK's XIM module sends it with PreeditCallbacks)
and an unset focus window means the client window, as the spec says,
so those clients get the popup at the caret. readattrs/place kept four
transient fields to pass values between them; ximclose tore down state
right before die(); the OOM passthrough context was a third policy for
one small calloc where emalloc dies like everything else. Both frontends
now poll for engine-owner loss only while a preedit shows instead of
XIM waking on a pipe the engine had to know about; ibus stops waking
five times a second when idle. keymeaningful() is the engine's own
predicate. The standalone xim_test moved into the unit suite, so
xim/Makefile is gone.
2026-08-16 16:16:31 +09:00
9080bb833c engine: trim the key dispatch
mapget and maplookup were one trie probe with two predicates; mapmatch
returns the match kind and mapget is its exact case. movedelta is a
switch. searchkey's modifier test was unreachable behind transition's,
searchlang's Alt/Super test behind both callers' earlier returns, and
picksearch's range check behind numberedkouho. Korean key folding runs
once at the top of transition. commit() on an empty preedit is a no-op,
so the Ctrl branch and startsearch lose their haspre guards and repeated
commit/reset pairs. Ctrl+language inside a search now commits the shown
query as it does elsewhere instead of dropping it. Language ids are
documented as the Ctrl codes they are; Im parameters no longer shadow
the file's Im.
2026-08-16 16:09:05 +09:00
68869e2589 engine: redraw when the picture changes, not when the state might have
Every key copied the whole Im and Search rune by rune, compared them
after the transition, and combined the answer with 'eaten', a commit
count that is always zero, and a force flag for caret and owner changes,
while redraw() kept a separate 'visible' bit to know when to send an
empty popup. redraw() now compares the snapshot with the last one sent
and treats two empty popups as equal, so imhandlekey just ends in
redraw(). keystroke() was only a test entry point and moves there.
2026-08-16 16:06:42 +09:00
a557fb114a engine: one trans contract for every language
Japanese was never dispatched through the Lang table: dotrans and
transstr both special-cased it into transjp, which edited Im in place
and wrote commits through a side argument, while transko popped and
cleared a pre that every caller overwrote anyway, and Vietnamese kept
its key history in dotrans. Emit now carries the new raw state next to
the new pre; transjp, transko and transvi are pure functions of (Im,
key), and dotrans and transstr have a single path. Vietnamese history
bookkeeping lives in vi.c, where the full-history flush no longer
resets the caret as a side effect; istone was toneidx() >= 0.
2026-08-16 16:04:57 +09:00
abaea77248 engine: look dictionaries up synchronously
The dictionary thread ran in imthread's own proc, so a lookup could
only start once the engine blocked, and it was a trie probe anyway; the
emoji and Hanja searches already called dictlookup directly. The
request/result channels, sequence numbers, staleness checks and the
second draw per Japanese key are gone; dictqjp fills the candidates
in place. Emit.dict and Lang.dictq only ever triggered lookups for
Vietnamese, which has no dictionary. dictlookup(Lang*, key, out, max)
returns the count. The Hanja lookup no longer pre-checks for a single
syllable; a reading either has an entry or it does not.
2026-08-16 16:02:09 +09:00
ebcec3af6b fix(engine): send clients the composed Telex text, not the keys
For Vietnamese the preedit is the map key ('as', 'oong'); only the popup
and the commit path looked it up. Clients with inline preedit therefore
showed 'as' while Enter committed 'á'. impre() now maps once for every
reader, and snapshot() no longer maps a second time.
2026-08-16 15:57:55 +09:00
c7718ece52 data: one trie for maps and dictionaries, one loader
The hash map served a single exact-match lookup that the trie already
answers, at the price of a second container, a second file loader with
its own drift, and a Str-to-UTF-8 conversion on every chain probe. The
files are small (kanji.dict is 7.5k lines), so the trie holds both.
trieopen validates keys and each space-separated value word against Str
and reports path:line; trielookup takes the Str every caller holds and
treats a nil trie as an unloaded map; trienew/trieput exist for tests.
The overflow guards on growth, Trie.root (always 0) and the per-language
init loop written twice are gone.
2026-08-16 15:56:32 +09:00
eb0f88f764 ipc: strip machinery no peer uses; keep client state in one Keyreq
ipc.c: an AF_UNIX nonblocking connect completes at once or fails with
EAGAIN, so the EINPROGRESS/poll/SO_ERROR path and the fcntl juggling
were dead; the deadline plumbing checked a clock that cannot fail and
re-tested the deadline before every transfer although only waitfd
blocks; readfield's truncation and discard loop served the unit test,
since every caller owns char[Ipcfieldmax+1]; NULL-argument checks on
in-tree encoders are gone (peer validation stays). The primary key frame
is Ipckey, not 'legacy'; ipckeysym names the keysym mapping.

srv.c: the per-connection capability and caret already lived in the
persistent Keyreq; the mirror locals and 'negotiated' flag guarded a
protocol rule no client relied on. proccreate never fails in libthread.

gtk: focus-out no longer round-trips a reset before closing the socket
that releases the engine; the caret dedup compares the packed frame;
srvconnect's preedit no-ops and the insimple flag are gone.
2026-08-16 15:53:08 +09:00
43b469f70b ipc: share one keysym and modifier mapping across frontends
The keysym-to-engine-key rule lived in ibus.c, xim/xim.c and gtk/main.c,
and the copies disagreed: GTK sent keypad digits as special keys while
IBus and XIM sent '1'..'9'. Modifiers were rebuilt bit by bit in two
places although Mmask already is the X core layout; only the virtual
Super bit (26) that GDK and IBus set needs folding. ipckey/ipcmod in
ipc.c serve the daemon, the module and the tests.
2026-08-16 15:47:47 +09:00
66ea2892f8 bench, docs: let the client detect readiness; say what Enter commits
bench.sh copied ipcpath() and the IBus address-file discovery into shell,
although the benchmark client only speaks IPC; retrying the client itself
is the readiness test. It also forwarded arguments to a daemon that takes
exactly one. README claimed Enter confirms the selection, but Enter and Tab
commit the reading unless a candidate was moved to.
2026-08-16 15:46:28 +09:00
d3c9813f6e build: scope IBus flags, rebuild containers with clean, dedup gtk install
Only ibus.o needs the D-Bus and xkbcommon flags, so give them the same
per-object scoping as the popup and XIM objects. The docker targets used
-B, which recursive makes inherit: the daemon was linked three times and
the live tests built twice per docker-check. gtk/Makefile repeated the
module-directory check and cache refresh in install and uninstall.
2026-08-16 15:46:00 +09:00
61840fc108 build: ignore the stress test binary 2026-08-16 15:45:18 +09:00
c1971c8096 fix(popup): accept depth-24 roots regardless of colormap precision
bits_per_rgb_value is the colormap component precision, not the pixel
layout; the NVIDIA driver reports 11 for its ordinary x8r8g8b8 visuals,
which left the popup disabled there. Depth, class, masks, byte order and
the pixmap format already pin the layout that putimage writes.
2026-08-16 15:45:09 +09:00
7047c57d29 fix(gtk): unset cache override during install 2026-08-15 16:10:34 +09:00
a1ef8a4744 fix(frontends): restart preedit around commits 2026-08-15 15:42:35 +09:00
67ed2ef478 fix(engine): redraw only for visible caret changes 2026-08-15 15:07:13 +09:00
f489c56fb1 fix(ipc): retry nonblocking I/O until deadline 2026-08-15 15:07:07 +09:00
d29d43f443 fix(frontends): preserve preedit lifecycle 2026-08-15 15:05:49 +09:00
aeb6010f3c fix(runtime): restart daemon in background 2026-08-15 00:09:15 +09:00
f0abace40b fix(gtk): install prebuilt modules without development files 2026-08-14 23:54:41 +09:00
7468d573ec test: make container Valgrind runnable 2026-08-14 23:25:07 +09:00
133cc97e55 fix(runtime): make startup and GTK installation explicit 2026-08-14 23:17:18 +09:00
516ec98a70 test: separate quick, live, and stress checks 2026-08-14 23:13:32 +09:00
bfe83d0119 fix(data): reject partial and malformed map data 2026-08-14 23:09:55 +09:00
38475318db fix(popup): fit the current X11 monitor 2026-08-14 22:54:15 +09:00
59482d00f3 fix(engine): compare logical transition state
Avoid reading inactive Str storage when deciding whether to publish a redraw. Keep the focused engine suite compact by retaining one case for each page, navigation, and empty-result invariant.
2026-08-14 22:46:42 +09:00
8df43cecd8 fix(ipc): reject unconnected backlog clients 2026-08-14 22:46:29 +09:00
a637f457f7 fix(xim): honor reset and X11 keyboard state 2026-08-14 22:34:37 +09:00
9804c1f55c fix(gtk): restore compose fallback and clean lifecycle 2026-08-14 22:29:14 +09:00
534ddcd9bb fix(ipc): preserve legacy preedit and bound client waits 2026-08-14 22:17:59 +09:00
5dc2530951 fix(engine): make composition transitions deterministic 2026-08-14 22:13:16 +09:00
f2fb71cd89 fix(ibus): make the X11 frontend fail truthfully 2026-08-14 22:10:40 +09:00
77c6dd66ca test: make clean checks trustworthy 2026-08-14 22:06:54 +09:00
0471d58dec popup: stabilize the input panel width 2026-08-14 21:13:49 +09:00
f222b4d573 popup: refine the input panel 2026-08-14 20:54:09 +09:00
3576c4ea68 tests: cover frontend input-panel protocols 2026-08-14 20:09:49 +09:00
e4fc327ffd popup: show the candidate page 2026-08-14 20:06:53 +09:00
193ed009bd popup: place and bound the input panel 2026-08-14 20:00:36 +09:00
7619d2bd9d ibus: support modern client preedit 2026-08-14 19:59:52 +09:00
a841894015 ibus: honor preedit and private-input policy 2026-08-14 19:41:07 +09:00
4cac6f1a15 engine: use stable candidate pages 2026-08-14 19:27:22 +09:00
2e6d7d8e48 xim: clear callback preedit on owner loss 2026-08-14 19:17:37 +09:00
76f9e1a21f gtk: honor preedit policy and cursor placement 2026-08-14 19:12:53 +09:00
6a58b63223 ipc: negotiate preedit capability and caret messages 2026-08-14 18:53:14 +09:00
b536573622 engine: update active preedit capability 2026-08-14 18:44:52 +09:00
9a3a92170e engine: reject stale dictionary results 2026-08-14 18:43:12 +09:00
76af1a022f xim: refresh placement before each key 2026-08-14 17:43:56 +09:00
68dc1e6a18 docs: describe frontend preedit behavior 2026-08-14 17:35:57 +09:00
e6bf32b0f6 xim: place popup from input context 2026-08-14 17:35:33 +09:00
2b4c509d0d xim: add callback preedit and UTF-8 negotiation 2026-08-14 17:20:38 +09:00
2376035524 engine: route popup preedit by client capability 2026-08-14 17:02:46 +09:00
c1b1b7016e docs: remove historical provenance files 2026-08-14 16:20:26 +09:00
4eba8c2904 tests: cover in-process XIM lifecycle 2026-08-14 16:17:27 +09:00
f5dc552c08 build: use configured include paths 2026-08-14 16:17:07 +09:00
4e07699460 xim: run server inside strans 2026-08-14 16:16:27 +09:00
08369e718e popup: use system fonts exclusively 2026-08-14 15:17:00 +09:00
54355b8190 popup: replace FreeType renderer with PangoCairo 2026-08-14 14:37:30 +09:00
77 changed files with 11419 additions and 8954 deletions

5
.gitignore vendored
View File

@@ -2,13 +2,16 @@
/strans
/tests/*.o
/tests/unit_test
/tests/stress_test
/tests/ibus_live_test
/tests/ibus_client_smoke
/tests/gtk_live_test
/tests/xim_live_test
/tests/ipc_live_test
/tests/daemon_collision_test
/tests/daemon_failure_test
/tests/daemon_restart_test
/xim/*.o
/xim/strans-xim
/xim/xim_test
/gtk/im-strans.so
/bench/bench

View File

@@ -1,23 +1,40 @@
FROM archlinux:base@sha256:b0deabeb3d283da2c7f7dbf0eea051b7b2cd0554e0b737cc457fd21683bdcdd1
RUN pacman -Syu --noconfirm --needed \
cairo \
fontconfig \
diffutils \
freetype2 \
gcc \
gtk3 \
ibus \
harfbuzz \
libx11 \
libxcb \
libxkbcommon \
make \
noto-fonts-emoji \
pango \
pkgconf \
plan9port \
python \
ttf-dejavu \
ttf-jigmo \
valgrind \
which \
xorg-server-xvfb \
xcb-imdkit \
dbus \
&& pacman -Scc --noconfirm
RUN export DEBUGINFOD_URLS=https://debuginfod.archlinux.org \
&& buildid="$(readelf -n /usr/lib/ld-linux-x86-64.so.2 | sed -n 's/.*Build ID: //p')" \
&& test -n "$buildid" \
&& debugdir="/usr/lib/debug/.build-id/$(printf '%.2s' "$buildid")" \
&& mkdir -p "$debugdir" \
&& install -m 0644 "$(debuginfod-find debuginfo /usr/lib/ld-linux-x86-64.so.2)" \
"$debugdir/${buildid#??}.debug" \
&& rm -rf /root/.cache/debuginfod_client
RUN printf "egrep='grep -E'\n" > /usr/lib/plan9/config
RUN dbus-uuidgen --ensure=/etc/machine-id

View File

@@ -6,7 +6,4 @@
- `GPL-2.0-or-later.txt` contains GNU GPL version 2. The header of
`map/kanji.dict` grants the option to use GPL version 2 or any later
version.
- License texts and provenance for the historically bundled fonts are retained
in `font/`; no font binary remains in the source tree.
No repository-wide license has been declared for original strans source.

View File

@@ -1,37 +1,50 @@
CC = 9c
LD = 9l
PKG_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
PKG_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
FT_LIBS = $(shell pkg-config --libs freetype2)
CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS)
PKG_CONFIG ?= pkg-config
CFLAGS ?= -O2 -g
WARN_CFLAGS = -Wall -Wextra
IBUS_CFLAGS = $(shell $(PKG_CONFIG) --cflags dbus-1 xkbcommon)
IBUS_LIBS = $(shell $(PKG_CONFIG) --libs dbus-1 xkbcommon)
XIM_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-imdkit xcb-xkb xkbcommon-x11)
XIM_LIBS = $(shell $(PKG_CONFIG) --libs xcb-imdkit xcb-xkb xkbcommon-x11)
TEXT_CFLAGS = $(shell $(PKG_CONFIG) --cflags pangocairo cairo fontconfig)
TEXT_LIBS = $(shell $(PKG_CONFIG) --libs pangocairo cairo fontconfig)
POPUP_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-randr)
POPUP_LIBS = $(shell $(PKG_CONFIG) --libs xcb-randr)
PROJECT_CPPFLAGS =
PROJECT_LDLIBS = -lthread -lbio -lxcb $(IBUS_LIBS) $(TEXT_LIBS) \
$(XIM_LIBS) $(POPUP_LIBS)
PROG = strans
DOCKER_IMAGE = strans-build
DOCKER_RUN = docker run --rm --user "$$(id -u):$$(id -g)" \
-v "$(CURDIR):/src" $(DOCKER_IMAGE)
SRCS = dict.c font.c hash.c ibus.c ipc.c ko.c main.c popup_layout.c \
SRCS = dict.c font.c ibus.c ipc.c ko.c main.c popup_layout.c \
srv.c str.c strans.c trie.c vi.c win.c
OBJS = $(SRCS:.c=.o)
XIMSRCS = xim/xim.c xim/keymap.c
XIMOBJS = $(XIMSRCS:.c=.o)
OBJS = $(SRCS:.c=.o) $(XIMOBJS)
all: $(PROG) xim gtk
all: $(PROG) gtk
$(PROG): $(OBJS)
$(LD) -o $@ $(OBJS) -lthread -lbio -lxcb $(PKG_LIBS) $(FT_LIBS)
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(PROJECT_LDLIBS) $(LDLIBS)
$(OBJS): dat.h fn.h ipc.h
font.o: CFLAGS += $(FT_CFLAGS)
ibus.o: PROJECT_CPPFLAGS += $(IBUS_CFLAGS)
font.o: PROJECT_CPPFLAGS += $(TEXT_CFLAGS)
win.o: PROJECT_CPPFLAGS += $(POPUP_CFLAGS)
$(XIMOBJS): PROJECT_CPPFLAGS += -I. $(XIM_CFLAGS)
%.o: %.c
$(CC) $(CPPFLAGS) $(PROJECT_CPPFLAGS) $(WARN_CFLAGS) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(OBJS) $(PROG)
$(MAKE) -C tests clean
$(MAKE) -C xim/ clean
$(MAKE) -C gtk/ clean
$(MAKE) -C bench/ clean
xim:
$(MAKE) -C xim/
gtk:
$(MAKE) -C gtk/
@@ -42,22 +55,38 @@ docker-image: Dockerfile
docker build -t $(DOCKER_IMAGE) - < Dockerfile
docker-build:
$(DOCKER_RUN) make -B all
$(DOCKER_RUN) sh -c 'make clean && make all'
docker-check:
$(DOCKER_RUN) make -B check TESTARGS="$(TESTARGS)"
$(DOCKER_RUN) sh -c 'make clean && \
make check check-live check-stress UNITARGS="$(UNITARGS)"'
docker-check-live:
$(DOCKER_RUN) sh -c 'make clean && make check-live'
docker-check-stress:
$(DOCKER_RUN) sh -c 'make clean && make check-stress'
docker-bench:
$(DOCKER_RUN) make -B bench
$(DOCKER_RUN) sh -c 'make clean && make bench'
docker-valgrind:
$(DOCKER_RUN) sh -c 'make clean && make -C tests unit_test && \
cd tests && valgrind -q --error-exitcode=1 ./unit_test $(UNITARGS)'
docker: docker-image
$(MAKE) docker-build
check: verify-map test
$(MAKE) -C xim check
check: verify-map
$(MAKE) -C tests check UNITARGS="$(UNITARGS)"
test: $(PROG)
$(MAKE) -C tests check TESTARGS="$(TESTARGS)"
check-live: $(PROG) gtk
$(MAKE) -C tests check-live
check-stress: $(PROG)
$(MAKE) -C tests check-stress
test: check
verify-map:
python3 map/mktelex.py | cmp - map/telex.map
@@ -68,5 +97,6 @@ verify-map:
python3 -B tests/mkhanja_test.py
python3 -B tests/skk2ktrans_test.py
.PHONY: all check test verify-map clean xim gtk bench docker docker-image \
docker-build docker-check docker-bench
.PHONY: all check check-live check-stress test verify-map clean gtk bench \
docker docker-image docker-build docker-check docker-check-live \
docker-check-stress docker-bench docker-valgrind

137
README.md
View File

@@ -15,53 +15,116 @@ Vietnamese Telex is also available as a compatibility mode.
| `Ctrl+V` | Vietnamese Telex |
| `Ctrl+E` | Emoji and symbol search |
| `Ctrl+H` | One-shot Hanja search |
| `Ctrl+P` | Toggle popup preedit |
Use `Up`/`Down` to move through candidates, `Enter` or `1`-`9` to select,
`Tab` to cycle temporary search results, and `Esc` to cancel. `0` commits the
current reading without conversion.
Use `Up`/`Down` to move through candidates and `PageUp`/`PageDown` to move by
nine without wrapping. An unmodified `1`-`9` selects that row on the current
page. `Enter` and `Tab` commit a candidate you have moved to; otherwise they
commit the reading, as `0` always does. Japanese consumes the confirming key;
Korean and Vietnamese pass it on to the application. In a temporary Emoji or
Hanja search, `Enter` commits the highlighted result and `Tab`/`Shift-Tab`
wrap through the results. Leaving the field or clicking elsewhere commits
what is pending; `Esc` cancels it.
Hiragana mode composes a complete reading before offering Kanji candidates.
Katakana mode does not perform Kanji conversion. Emoji and Hanja searches
return to the previous mode after use. Hanja lookup currently supports one
modern Hangul syllable at a time.
return to the previous mode after use; switching language during a search
commits the shown query. `Ctrl+H` takes the syllable being composed as its
query; the Hanja dictionary lists one modern Hangul syllable at a time.
## Preedit and candidates
| Frontend | Preedit | Candidates |
| --- | --- | --- |
| GTK 3 and IBus | inline in the client | strans popup |
| XIM PreeditCallbacks | inline through XIM callbacks | strans popup |
| XIM PreeditPosition | strans popup | strans popup |
| XIM PreeditNothing | strans popup | strans popup |
XIM text travels as `COMPOUND_TEXT`, which carries any UTF-8. A client that
sends `XNSpotLocation` gets the popup at that spot whatever its preedit
style; otherwise the popup sits under the focus window, or under the client
window when no focus window is set.
The popup is an X11 window sized by `GDK_SCALE` on HiDPI displays, XIM is
X11-only, and GTK popup placement converts the GTK caret to X11 root
coordinates. There is no native Wayland popup surface or Wayland caret
positioning. GTK and IBus clients can still use inline preedit where their
display environment supports it.
## Build and test
Docker is the supported build environment:
Initialize the bundled test library, then use the pinned Docker environment
for a reproducible build:
```sh
git submodule update --init
make docker-image
make docker-build
make docker-check
```
Build output:
```text
strans daemon and IBus frontend
xim/strans-xim XIM frontend
gtk/im-strans.so GTK 3 frontend
strans daemon with IBus and XIM frontends
gtk/im-strans.so GTK 3 frontend module
```
Use `make docker-check TESTARGS=hangul` to run selected C tests. Native
`make all`, `make check`, and `make bench` are also available when the
dependencies listed in [`Dockerfile`](Dockerfile) are installed.
Tests have three tiers:
```sh
make check # generated-map validation and focused unit tests
make check-live # one IBus, GTK, XIM, and IPC daemon smoke
make check-stress # randomized, capacity, collision, failure, and restart tests
```
`UNITARGS` filters only the focused C unit suite, for example
`make check UNITARGS=hangul`. It does not select or skip live and stress
tests. `make docker-check` rebuilds and runs all three tiers in the
container, and `make docker-valgrind` runs the unit suite under Valgrind
there; run `make docker-image` first after changing `Dockerfile`.
Native Linux source builds require a C toolchain, Make, pkg-config, Python 3,
and cmp/diff, plus development files for:
- Plan 9 port (`9c`, `9l`, `libthread`, and `libbio`);
- D-Bus development files;
- XCB, XCB RandR, XCB XKB, xcb-imdkit, xkbcommon, and xkbcommon-x11;
- Pango, PangoCairo, Cairo, and Fontconfig;
- GTK 3, plus libibus and Xlib for the live clients;
- Xvfb for the X11 live tests;
- a working `C.UTF-8` locale and fonts covering Latin, CJK, and emoji.
The package names used by the canonical Arch Linux image are listed in
[`Dockerfile`](Dockerfile).
## Run
The popup needs one to four scalable font files. No fonts are bundled.
`run.sh` uses installed DejaVu, Jigmo, or Noto CJK fonts when available, or
accepts explicit files in fallback order:
The popup uses the normal system fonts found by Pango and Fontconfig; no font
file argument or system-wide strans installation is required. Run it directly
from the build tree:
```sh
./run.sh
./run.sh /path/to/primary.ttf /path/to/fallback.ttc
# equivalently
./strans map
```
The script starts the daemon and, when `DISPLAY` is set, the XIM frontend. It
restarts any existing `strans` processes owned by the current user.
`run.sh` stops every same-user process whose exact name is `strans`, starts one
build-tree daemon in the background, and returns to the shell. It requires
`pkill` from procps. Startup and frontend failures remain visible on the
caller's standard error. A service supervisor should instead run
`./strans map` directly in the foreground.
The build does not copy or install the daemon or its data. `./strans DIR`
opens `hira.map`, `kata.map`, `telex.map`, `kanji.dict`, `emoji.dict`, and
`hanja.dict` from `DIR` at runtime; `run.sh` passes the tracked `map/`
directory. Only the GTK subdirectory has install and uninstall targets.
Pango shapes the complete string and selects glyph fallback from the system
font set.
The popup renderer uses Pango/PangoCairo and Cairo. When `DISPLAY` is set,
`strans` starts its XIM worker in the same process.
Configure clients as needed:
@@ -71,14 +134,31 @@ export XMODIFIERS=@im=strans
# GTK 3 module
doas make -C gtk install
export GTK_IM_MODULE=strans
# IBus client example
GLFW_IM_MODULE=ibus kitty
```
Building `gtk/im-strans.so` requires GTK development files. Installing an
already-built, up-to-date module from this build tree does not: the install
target first uses an explicit `GTK_MODULE_DIR`, then GTK's pkg-config
variables, and finally the module directory reported by the GTK runtime's
`gtk-query-immodules-3.0`. Packagers and cross builds should set
`GTK_MODULE_DIR` explicitly. A prebuilt module must still match the target
architecture and library ABI.
`DESTDIR` staging is supported and deliberately skips the host module-cache
update. A native install needs the GTK runtime query utility so it can refresh
that cache. Remove a native installation with:
```sh
doas make -C gtk uninstall
```
strans provides its own IBus endpoint; `ibus-daemon` and fcitx are not
required. Without `DISPLAY`, the daemon and IBus frontend still work, but the
popup is disabled.
required. Without `DISPLAY`, the daemon and IBus frontend still work, but XIM
is not started and the popup is disabled.
## Dictionary data
@@ -93,9 +173,14 @@ make verify-map
See [`map/README`](map/README) for Hanja import and Japanese dictionary
generation.
## Benchmark
`make bench` builds `bench/bench`. With the daemon stopped, `./bench.sh` starts
one build-tree instance, warms it up, and records the workload with Linux
`perf`. It requires `perf` and permission to profile the daemon, and writes
`bench/perf.data`.
## Licensing
Data and third-party notices are in
[`docs/PROVENANCE.md`](docs/PROVENANCE.md) and
[`font/PROVENANCE`](font/PROVENANCE). The repository does not currently
declare a license for the strans source as a whole.
Third-party license notices are in [`LICENSES`](LICENSES). The repository does
not currently declare a license for the strans source as a whole.

View File

@@ -4,18 +4,8 @@ set -eu
cd "$(dirname "$0")" || exit 1
case ${XDG_RUNTIME_DIR-} in
/) ipc=/strans.sock ;;
/*/) ipc=${XDG_RUNTIME_DIR}strans.sock ;;
/*) ipc=${XDG_RUNTIME_DIR}/strans.sock ;;
*) ipc=/tmp/strans.$(id -u) ;;
esac
if test -n "${XDG_CONFIG_HOME-}"; then
busdir=$XDG_CONFIG_HOME/ibus/bus
elif test "${HOME+x}" = x; then
busdir=$HOME/.config/ibus/bus
else
echo "bench.sh: HOME or XDG_CONFIG_HOME is required for IBus discovery" >&2
if test "$#" -ne 0; then
echo "usage: bench.sh" >&2
exit 1
fi
@@ -29,70 +19,36 @@ cleanup()
if test -n "$perf_pid"; then
kill -INT "$perf_pid" 2>/dev/null || :
wait "$perf_pid" 2>/dev/null || :
perf_pid=
fi
if test -n "$strans_pid"; then
kill "$strans_pid" 2>/dev/null || :
wait "$strans_pid" 2>/dev/null || :
strans_pid=
fi
exit "$status"
}
ibusready()
{
for address in "$busdir"/*; do
test -f "$address" || continue
foundpid=
foundaddress=
while IFS= read -r line; do
case $line in
IBUS_DAEMON_PID="$strans_pid") foundpid=1 ;;
IBUS_ADDRESS=unix:abstract=strans-"$strans_pid",*) foundaddress=1 ;;
esac
done <"$address"
if test -n "$foundpid" && test -n "$foundaddress"; then
return 0
fi
done
return 1
}
trap cleanup 0
trap 'exit 129' 1
trap 'exit 130' 2
trap 'exit 143' 15
./strans map "$@" &
./strans map &
strans_pid=$!
# The daemon is ready once the benchmark client can complete a round.
attempt=0
while test "$attempt" -lt 10; do
if test -S "$ipc" && ibusready; then
break
fi
until ./bench/bench bench/bench.keys 1 >/dev/null 2>&1; do
if ! kill -0 "$strans_pid" 2>/dev/null; then
if wait "$strans_pid"; then
status=0
else
status=$?
fi
strans_pid=
if test "$status" -eq 0; then
status=1
fi
echo "bench.sh: daemon exited before publishing IPC and IBus endpoints" >&2
exit "$status"
echo "bench.sh: daemon exited before accepting IPC clients" >&2
exit 1
fi
attempt=$((attempt + 1))
if test "$attempt" -lt 10; then
sleep 1
if test "$attempt" -ge 50; then
echo "bench.sh: daemon did not accept IPC clients within 10 seconds" >&2
exit 1
fi
sleep 0.2
done
if test "$attempt" -eq 10; then
echo "bench.sh: daemon did not publish IPC and IBus endpoints within 10 seconds" >&2
exit 1
fi
# warm up the renderer
./bench/bench bench/bench.keys 100

View File

@@ -1,8 +1,9 @@
CC = cc
CFLAGS = -Wall -O2
CFLAGS ?= -O2
bench: main.c ../ipc.c ../ipc.h
$(CC) $(CFLAGS) -I.. -o $@ main.c ../ipc.c
$(CC) $(CPPFLAGS) -I.. -Wall $(CFLAGS) $(LDFLAGS) -o $@ \
main.c ../ipc.c $(LDLIBS)
clean:
rm -f bench

View File

@@ -7,7 +7,7 @@
#include <string.h>
#include <unistd.h>
#include <time.h>
#include "../ipc.h"
#include "ipc.h"
typedef struct Key Key;
struct Key {
@@ -128,10 +128,10 @@ sendkey(int key, int mod)
static int
readresp(void)
{
char buf[Ipcfieldmax+1];
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
Ipcresp resp;
return ipcreadresp(fd, 0, buf, sizeof buf, NULL, 0, &resp);
return ipcreadresp(fd, 0, commit, preedit, &resp);
}
static double

120
dat.h
View File

@@ -7,6 +7,7 @@
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
/* A language id is the control code of the Ctrl key that selects it. */
enum
{
LangEN = 0x14,
@@ -17,8 +18,6 @@ enum
LangEMOJI = 0x05,
LangVI = 0x16,
Fontsz = 32,
Maxfonts = 4,
Maxrunes = 64,
Maxutf = Maxrunes * UTFmax + 1,
};
@@ -26,16 +25,26 @@ enum
enum
{
Maxclients = 64,
Maxkouho = 32,
Maxkouho = 128,
Maxdisp = 9,
Imgw = (Maxrunes + 3) * Fontsz,
Imgh = (Maxdisp + 1) * Fontsz,
Colfg = 0x000000,
Colbg = 0xffffff,
Colsel = 0xcccccc,
Colsep = 0xd0d0d0,
Colsel = 0x333333,
Colselfg = 0xffffff,
};
/* Popup metrics in pixels; popupscale is GDK_SCALE, for HiDPI. */
extern int popupscale;
#define Fontsz (32*popupscale)
#define PopupPad (4*popupscale)
#define PopupSep popupscale
#define PopupNumw Fontsz
#define PopupTextw (12*Fontsz)
#define PopupBasew (2*PopupPad + PopupNumw + PopupTextw)
#define Imgh (2*PopupPad + (Maxdisp + 2)*Fontsz + PopupSep)
typedef struct Str Str;
struct Str
{
@@ -43,53 +52,43 @@ struct Str
int n;
};
/*
* What one key does to a composition: text to commit, the new pending
* text, and the new raw state (Telex key history, or pending Japanese
* romaji), given the old ones in Im.
*/
typedef struct Emit Emit;
struct Emit
{
int eat;
Str s;
Str next;
Str dict;
};
typedef struct Hnode Hnode;
struct Hnode
{
int filled;
int next;
char *key;
int klen;
char *val;
int vlen;
Str raw;
};
typedef struct Tnode Tnode;
struct Tnode
{
char *val;
int vlen;
int child;
int sibling;
char c;
char *val;
int vlen;
};
typedef struct Trie Trie;
struct Trie
{
int root;
Tnode *nodes;
int n;
int cap;
};
typedef struct Hmap Hmap;
struct Hmap
enum
{
int nbs;
int nsz;
int len;
int cap;
uchar *nodes;
TrieMiss,
TriePrefix,
TrieExact,
};
typedef struct Lang Lang;
@@ -101,9 +100,8 @@ struct Lang
char *dictname;
Emit (*trans)(Im*, Rune);
void (*back)(Im*);
void (*dictq)(Im*);
Trie *map;
Hmap *dict;
Trie *dict;
};
struct Im
@@ -119,6 +117,15 @@ struct Im
typedef struct Drawcmd Drawcmd;
typedef struct Caret Caret;
typedef struct Area Area;
struct Area
{
int x;
int y;
int w;
int h;
};
struct Caret
{
int valid;
@@ -133,21 +140,52 @@ struct Drawcmd
Str kouho[Maxdisp];
int nkouho;
int sel;
int first;
int total;
Caret caret;
};
/*
* Popup geometry from popuplayout: n rows from row0 of the candidates,
* each Fontsz tall; a section's y is -1 when it is absent. Numbers
* start at PopupPad, text at PopupPad+PopupNumw.
*/
typedef struct Popup Popup;
struct Popup
{
int w;
int h;
int n;
int row0;
int prey;
int sepy;
int rowsy;
int textw;
int marky;
int markx;
int markw;
Str mark;
int sely;
int selw;
};
typedef struct Keyreq Keyreq;
typedef struct Keyres Keyres;
enum
{
Cclientpreedit = 1<<0,
};
enum
{
Keypress,
Keyreset,
Keyrelease,
Keycaret,
Keycap,
};
struct Keyres
{
int eaten;
int eaten; /* Keycap: caller is active and capability was applied. */
Str commit;
Str preedit;
};
@@ -155,6 +193,7 @@ struct Keyres
struct Keyreq
{
void *owner; /* stable until the context's release is acknowledged */
int cap;
int op;
u32int ks;
u32int mod;
@@ -162,26 +201,7 @@ struct Keyreq
Channel *reply;
};
typedef struct Dictreq Dictreq;
struct Dictreq
{
Str key;
Str pre;
int lang;
};
typedef struct Dictres Dictres;
struct Dictres
{
Str key;
Str kouho[Maxkouho];
int nkouho;
int lang;
};
extern Lang langs[];
extern int nlang;
extern Channel *drawc;
extern Channel *keyc;
extern Channel *dictreqc;
extern Channel *dictresc;

141
dict.c
View File

@@ -1,122 +1,59 @@
#include "dat.h"
#include "fn.h"
void
dictlookup(Dictreq *req, Dictres *res)
/*
* Fills out[] with up to max candidates for key: the space-separated words
* of the entry, minus the key itself except in the emoji dictionary, where
* a query may name its own answer.
*/
int
dictlookup(Lang *l, Str *key, Str *out, int max)
{
Lang *l;
Hmap *dict;
Hnode *n;
char *p, *e, *sp;
Str tmp;
int n, vlen;
res->key = req->pre;
res->nkouho = 0;
res->lang = req->lang;
if(req->key.n == 0)
return;
l = getlang(req->lang);
dict = l ? l->dict : nil;
if(dict == nil)
return;
n = hmapget(dict, &req->key);
if(n == nil || n->vlen == 0)
return;
p = n->val;
e = p + n->vlen;
while(res->nkouho < Maxkouho && p < e){
if(l == nil || key->n == 0 ||
trielookup(l->dict, key, &p, &vlen) != TrieExact)
return 0;
n = 0;
e = p + vlen;
while(n < max && p < e){
while(p < e && *p == ' ')
p++;
if(p == e)
break;
sp = p;
while(p < e && *p != ' ')
p++;
sinit(&tmp, sp, p - sp);
if(req->lang == LangEMOJI || scmp(&tmp, &req->key) != 0)
res->kouho[res->nkouho++] = tmp;
if(p < e)
p++;
if(sinit(&tmp, sp, p - sp) && tmp.n > 0 &&
(l->lang == LangEMOJI || scmp(&tmp, key) != 0))
out[n++] = tmp;
}
return n;
}
void
dictthread(void*)
{
Dictreq req;
static Dictres res;
threadsetname("dict");
for(;;){
if(chanrecv(dictreqc, &req) < 0)
break;
while(channbrecv(dictreqc, &req) > 0)
;
dictlookup(&req, &res);
chansend(dictresc, &res);
}
}
static Hmap*
dictopen(char *path)
{
Hmap *h;
Biobuf *b;
Str key;
char *line, *tab, *p, *e;
int len, lineno;
b = Bopen(path, OREAD);
if(b == nil)
die("can't open: %s", path);
h = hmapalloc(4096);
lineno = 0;
while((line = Brdstr(b, '\n', 1)) != nil){
lineno++;
len = strlen(line);
if(len > 0 && line[len-1] == '\r')
line[--len] = '\0';
if(len == 0 || line[0] == ';'){
free(line);
continue;
}
tab = strchr(line, '\t');
if(tab == nil || tab == line || tab >= line + len - 1 ||
strchr(tab+1, '\t') != nil)
die("malformed dictionary: %s:%d", path, lineno);
*tab = '\0';
if(utflen(line) > Maxrunes)
die("dictionary key too long: %s:%d", path, lineno);
for(p = tab+1; p < line+len; p = e+1){
e = memchr(p, ' ', line+len-p);
if(e == nil)
e = line+len;
if(utfnlen(p, e-p) > Maxrunes)
die("dictionary candidate too long: %s:%d", path, lineno);
if(e == line+len)
break;
}
sinit(&key, line, tab - line);
hmapset(&h, &key, tab+1, len - (tab - line) - 1);
free(line);
}
Bterm(b);
return h;
}
void
dictinit(char *dir)
static Trie*
langopen(char *dir, char *name, char *ext)
{
char *path;
int i;
Trie *t;
for(i = 0; i < nlang; i++){
if(langs[i].dictname == nil)
continue;
path = smprint("%s/%s.dict", dir, langs[i].dictname);
if(path == nil)
die("out of memory");
langs[i].dict = dictopen(path);
free(path);
path = smprint("%s/%s.%s", dir, name, ext);
if(path == nil)
die("out of memory");
t = trieopen(path);
free(path);
return t;
}
void
langinit(char *dir)
{
Lang *l;
for(l = langs; l < langs + nlang; l++){
if(l->mapname != nil)
l->map = langopen(dir, l->mapname, "map");
if(l->dictname != nil)
l->dict = langopen(dir, l->dictname, "dict");
}
}

View File

@@ -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
View File

@@ -1,6 +1,6 @@
void die(char*, ...);
void sinit(Str*, char*, int);
int sinit(Str*, char*, int);
void sclear(Str*);
void sputr(Str*, Rune);
void spopr(Str*);
@@ -9,28 +9,28 @@ int scmp(Str*, Str*);
int stoutf(Str*, char*, int);
Rune slastr(Str*);
Hmap* hmapalloc(int);
void hmapfree(Hmap*);
void hmapset(Hmap**, Str*, const char*, int);
Hnode* hmapget(Hmap*, Str*);
int mapget(Trie*, Str*, Str*);
Trie* trienew(void);
void trieput(Trie*, char*, int, char*, int);
Trie* trieopen(char*);
void trieclose(Trie*);
char* trieget(Trie*, char*, int, int*);
int trielookup(Trie*, char*, int, char**, int*);
int trielookup(Trie*, Str*, char**, int*);
Lang* getlang(int);
void mapinit(char*);
void dictinit(char*);
void langinit(char*);
void dictthread(void*);
void dictlookup(Dictreq*, Dictres*);
int dictlookup(Lang*, Str*, Str*, int);
void drawthread(void*);
int popupcells(Rune*, int);
void popupposition(Caret*, int, int, int, int, int, int, int*, int*);
void popuparea(Area*, int, Area*, int, int, Area*);
void popupposition(Caret*, int, int, Area*, int, int, int*, int*);
int pagemarker(char*, int, int, int, int);
void popuplayout(Drawcmd*, int, int, Popup*);
void popupdraw(u32int*, Drawcmd*, Popup*);
void imthread(void*);
void impre(Im*, Str*);
void transstr(Lang*, Str*, Str*);
Emit transmap(Im*, Rune);
Emit transko(Im*, Rune);
Emit transvi(Im*, Rune);
@@ -40,9 +40,13 @@ void backvi(Im*);
void srvinit(void);
void srvthread(void*);
void ibusthread(void*);
void ximthread(void*);
int keymeaningful(u32int);
void* emalloc(ulong);
void* erealloc(void*, ulong);
int fontinit(char**, int);
void putfont(u32int*, int, int, int, int, Rune);
int textinit(void);
void textclose(void);
int textwidth(Str*);
void textdraw(u32int*, int, int, int, int, int, u32int, Str*);

314
font.c
View File

@@ -1,168 +1,168 @@
#include "dat.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include <fontconfig/fontconfig.h>
#include <pango/pangocairo.h>
#include "fn.h"
typedef struct Font Font;
struct Font
{
FT_Face face;
int base;
};
static FT_Library lib;
static Font fonts[Maxfonts];
static int nfonts;
static u32int blendtab[2][256];
static u32int
blend(u32int bg, u32int fg, int a)
{
int r, g, b, inv;
inv = 255 - a;
r = ((bg >> 16 & 0xff) * inv + (fg >> 16 & 0xff) * a) / 255;
g = ((bg >> 8 & 0xff) * inv + (fg >> 8 & 0xff) * a) / 255;
b = ((bg & 0xff) * inv + (fg & 0xff) * a) / 255;
return (r << 16) | (g << 8) | b;
}
static int
loadfont(char *path)
{
FT_Face face;
Font *f;
vlong height, pixels;
if(nfonts >= nelem(fonts))
return 0;
if(FT_New_Face(lib, path, 0, &face) != 0){
fprint(2, "strans: popup: can't load font: %s\n", path);
return 0;
}
height = (vlong)face->ascender - face->descender;
if(height <= 0 || face->units_per_EM == 0){
fprint(2, "strans: popup: invalid font metrics: %s\n", path);
FT_Done_Face(face);
return 0;
}
pixels = (vlong)Fontsz * face->units_per_EM / height;
if(pixels < 1)
pixels = 1;
if(FT_Set_Pixel_Sizes(face, 0, pixels) != 0){
fprint(2, "strans: popup: can't size font: %s\n", path);
FT_Done_Face(face);
return 0;
}
f = &fonts[nfonts];
f->face = face;
f->base = (vlong)Fontsz * face->ascender / height;
nfonts++;
return 1;
}
static PangoFontMap *fontmap;
static PangoContext *context;
static PangoLayout *layout;
static void
clearfonts(void)
resetlayout(void)
{
int i;
for(i = 0; i < nfonts; i++){
FT_Done_Face(fonts[i].face);
fonts[i].face = nil;
}
nfonts = 0;
}
int
fontinit(char **path, int npath)
{
int i, a;
if(path == nil || npath < 1 || npath > nelem(fonts)){
fprint(2, "strans: popup: need 1-%d font files\n", nelem(fonts));
clearfonts();
return 0;
}
if(lib == nil && FT_Init_FreeType(&lib) != 0){
fprint(2, "strans: popup: can't initialize FreeType\n");
return 0;
}
clearfonts();
for(i = 0; i < npath; i++){
if(path[i] == nil || !loadfont(path[i])){
clearfonts();
return 0;
}
}
for(a = 0; a < 256; a++){
blendtab[0][a] = blend(Colbg, Colfg, a);
blendtab[1][a] = blend(Colsel, Colfg, a);
}
return 1;
}
static int
drawglyph(Font *f, u32int *buf, int w, int h, int px, int py)
{
FT_Bitmap *b;
FT_GlyphSlot g;
uchar *row;
vlong pitch, x0, x1, y0, y1;
int a, sel, x, xa, xb, y, ya, yb;
u32int *p;
g = f->face->glyph;
b = &g->bitmap;
if(b->pixel_mode != FT_PIXEL_MODE_GRAY || b->num_grays != 256)
return 0;
if(b->width == 0 || b->rows == 0)
return 1;
pitch = b->pitch;
if(pitch < 0)
pitch = -pitch;
if(b->buffer == nil || pitch < b->width)
return 0;
x0 = (vlong)px + g->bitmap_left;
y0 = (vlong)py + f->base - g->bitmap_top;
x1 = x0 + b->width;
y1 = y0 + b->rows;
if(x1 <= 0 || y1 <= 0 || x0 >= w || y0 >= h)
return 1;
xa = x0 < 0 ? 0 : x0;
xb = x1 > w ? w : x1;
ya = y0 < 0 ? 0 : y0;
yb = y1 > h ? h : y1;
for(y = ya; y < yb; y++){
if(b->pitch < 0)
row = b->buffer + ((vlong)b->rows - 1 - (y - y0)) * pitch;
else
row = b->buffer + ((vlong)y - y0) * pitch;
for(x = xa; x < xb; x++){
a = row[(vlong)x - x0];
if(a != 0){
p = &buf[y * w + x];
sel = *p == Colsel;
*p = blendtab[sel][a];
}
}
}
return 1;
pango_layout_set_width(layout, -1);
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
}
void
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
textclose(void)
{
FT_UInt g;
int f;
if(buf == nil || w <= 0 || h <= 0)
return;
for(f = 0; f < nfonts; f++){
g = FT_Get_Char_Index(fonts[f].face, r);
if(g == 0 || FT_Load_Glyph(fonts[f].face, g, FT_LOAD_DEFAULT) != 0 ||
FT_Render_Glyph(fonts[f].face->glyph, FT_RENDER_MODE_NORMAL) != 0)
continue;
if(drawglyph(&fonts[f], buf, w, h, px, py))
return;
}
if(layout != nil)
g_object_unref(layout);
if(context != nil)
g_object_unref(context);
if(fontmap != nil)
g_object_unref(fontmap);
layout = nil;
context = nil;
fontmap = nil;
}
static void
setfont(void)
{
PangoFontDescription *font;
PangoRectangle r;
int size;
font = pango_font_description_new();
pango_font_description_set_family(font, "sans");
pango_font_description_set_absolute_size(font, Fontsz * PANGO_SCALE);
pango_layout_set_font_description(layout, font);
pango_layout_set_text(layout, "Mg", -1);
pango_layout_get_pixel_extents(layout, nil, &r);
if(r.height > 0){
/* Fontsz is the popup row height, not a point size. */
size = Fontsz * PANGO_SCALE * Fontsz / r.height;
pango_font_description_set_absolute_size(font, max(size, PANGO_SCALE));
pango_layout_set_font_description(layout, font);
}
pango_font_description_free(font);
}
int
textinit(void)
{
textclose();
FcInit();
fontmap = pango_cairo_font_map_new();
if(fontmap == nil){
fprint(2, "strans: popup: can't initialize PangoCairo\n");
return 0;
}
pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(fontmap), 96);
context = pango_font_map_create_context(fontmap);
if(context != nil)
layout = pango_layout_new(context);
if(context == nil || layout == nil){
fprint(2, "strans: popup: can't create text layout\n");
textclose();
return 0;
}
pango_layout_set_single_paragraph_mode(layout, TRUE);
setfont();
return 1;
}
static int
settext(Str *s)
{
char utf[Maxutf];
int n;
if(layout == nil || s == nil || s->n <= 0)
return 0;
n = stoutf(s, utf, sizeof utf);
pango_layout_set_text(layout, utf, n);
return n > 0;
}
static void
textextents(PangoRectangle *r)
{
PangoRectangle ink, logical;
int x0, y0, x1, y1;
pango_layout_get_pixel_extents(layout, &ink, &logical);
x0 = min(ink.x, logical.x);
y0 = min(ink.y, logical.y);
x1 = max(ink.x + ink.width, logical.x + logical.width);
y1 = max(ink.y + ink.height, logical.y + logical.height);
r->x = x0;
r->y = y0;
r->width = max(x1 - x0, 0);
r->height = max(y1 - y0, 0);
}
int
textwidth(Str *s)
{
PangoRectangle r;
if(layout == nil)
return 0;
resetlayout();
if(!settext(s))
return 0;
textextents(&r);
return r.width;
}
/*
* Draws s at (x, y) into the w-by-h buffer, ellipsized to fit pixels and
* clipped to its own row so a tall fallback face cannot paint a
* neighbour.
*/
void
textdraw(u32int *buf, int w, int h, int x, int y, int fit, u32int color,
Str *s)
{
PangoRectangle r;
cairo_surface_t *surface;
cairo_t *cr;
int b, g, red, stride;
if(layout == nil || fit <= 0)
return;
resetlayout();
pango_layout_set_width(layout, fit * PANGO_SCALE);
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
if(w <= 0 || h <= 0 || !settext(s))
return;
stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w);
if(stride < 0 || (vlong)stride != (vlong)w * sizeof buf[0])
return;
surface = cairo_image_surface_create_for_data((uchar*)buf,
CAIRO_FORMAT_RGB24, w, h, stride);
if(cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS){
cairo_surface_destroy(surface);
return;
}
cairo_surface_mark_dirty(surface);
cr = cairo_create(surface);
if(cairo_status(cr) == CAIRO_STATUS_SUCCESS){
pango_cairo_update_layout(cr, layout);
textextents(&r);
cairo_rectangle(cr, x, y, fit, Fontsz);
cairo_clip(cr);
cairo_move_to(cr, x - r.x, y + (Fontsz - r.height) / 2 - r.y);
red = color >> 16 & 0xff;
g = color >> 8 & 0xff;
b = color & 0xff;
cairo_set_source_rgb(cr, red / 255.0, g / 255.0, b / 255.0);
pango_cairo_show_layout(cr, layout);
}
cairo_destroy(cr);
cairo_surface_flush(surface);
cairo_surface_destroy(surface);
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -1,24 +1,40 @@
PROG = im-strans.so
CFLAGS = -Wall -O2 -I.. $(shell pkg-config --cflags gtk+-3.0)
LDFLAGS = $(shell pkg-config --libs gtk+-3.0)
LIBDIR ?= /usr/lib
GTK_MODULE_DIR = $(LIBDIR)/gtk-3.0/3.0.0/immodules
PKG_CONFIG ?= pkg-config
CFLAGS ?= -O2
GTK_CFLAGS = $(shell $(PKG_CONFIG) --cflags gtk+-3.0)
GTK_LIBS = $(shell $(PKG_CONFIG) --libs gtk+-3.0)
GTK_QUERY_IMMODULES ?= gtk-query-immodules-3.0
GTK_LIBDIR ?= $(shell $(PKG_CONFIG) --variable=libdir gtk+-3.0 2>/dev/null)
GTK_BINARY_VERSION ?= $(shell $(PKG_CONFIG) \
--variable=gtk_binary_version gtk+-3.0 2>/dev/null)
GTK_RUNTIME_MODULE_DIR = $(shell GTK_PATH= GTK_IM_MODULE_FILE= \
$(GTK_QUERY_IMMODULES) 2>/dev/null | \
awk -F '"' '/^"\// { sub("/[^/]*$$", "", $$2); print $$2; exit }')
GTK_MODULE_DIR ?= $(strip $(if \
$(and $(strip $(GTK_LIBDIR)),$(strip $(GTK_BINARY_VERSION))),\
$(GTK_LIBDIR)/gtk-3.0/$(GTK_BINARY_VERSION)/immodules,\
$(GTK_RUNTIME_MODULE_DIR)))
all: $(PROG)
$(PROG): main.c ../ipc.c ../ipc.h
$(CC) -shared -fPIC $(CFLAGS) -o $@ main.c ../ipc.c $(LDFLAGS)
$(CC) $(CPPFLAGS) -I.. $(GTK_CFLAGS) -Wall $(CFLAGS) $(LDFLAGS) \
-shared -fPIC -o $@ main.c ../ipc.c $(GTK_LIBS) $(LDLIBS)
CHECKDIR = test -n "$$module_dir" || { \
echo "cannot find GTK 3 module directory; set GTK_MODULE_DIR" >&2; \
exit 1; }
REFRESH = test -n "$(DESTDIR)" || { \
unset GTK_PATH GTK_IM_MODULE_FILE; $(GTK_QUERY_IMMODULES) --update-cache; }
install: $(PROG)
test -n "$(LIBDIR)"
mkdir -p "$(DESTDIR)$(GTK_MODULE_DIR)"
cp $(PROG) "$(DESTDIR)$(GTK_MODULE_DIR)/"
if test -z "$(DESTDIR)"; then gtk-query-immodules-3.0 --update-cache; fi
module_dir="$(GTK_MODULE_DIR)"; $(CHECKDIR); \
mkdir -p "$(DESTDIR)$$module_dir" && \
cp "$(PROG)" "$(DESTDIR)$$module_dir/" && $(REFRESH)
uninstall:
test -n "$(LIBDIR)"
rm -f "$(DESTDIR)$(GTK_MODULE_DIR)/$(PROG)"
if test -z "$(DESTDIR)"; then gtk-query-immodules-3.0 --update-cache; fi
module_dir="$(GTK_MODULE_DIR)"; $(CHECKDIR); \
rm -f "$(DESTDIR)$$module_dir/$(PROG)" && $(REFRESH)
clean:
rm -f $(PROG)

View File

@@ -1,132 +1,246 @@
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <gtk/gtk.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "ipc.h"
typedef struct Im Im;
struct Im
{
GtkIMContext parent;
GtkIMContextSimple parent;
int fd;
int usepreedit;
int ext;
int private;
int simpleactive;
int simpledone;
char pre[Ipcfieldmax+1];
int prelen;
GdkWindow *win;
GdkRectangle cursor;
int cursorvalid;
int caretsent;
unsigned char sent[Ipccaretsz];
};
typedef struct ImClass ImClass;
struct ImClass
{
GtkIMContextClass parent;
GtkIMContextSimpleClass parent;
};
static GType imtype;
static GObjectClass *parentobject;
static GtkIMContextClass *parentim;
static void
srvconnect(Im *im)
setpreedit(Im *im, const char *s, int n)
{
int was;
if(n < 0 || n > Ipcfieldmax)
n = 0;
if(n == 0 && im->prelen == 0)
return;
was = im->prelen;
if(n > 0)
memcpy(im->pre, s, n);
im->pre[n] = '\0';
im->prelen = n;
if(was == 0 && n > 0)
g_signal_emit_by_name(im, "preedit-start");
g_signal_emit_by_name(im, "preedit-changed");
if(was > 0 && n == 0)
g_signal_emit_by_name(im, "preedit-end");
}
static void
srvdrop(Im *im, int notify)
{
if(im->fd >= 0)
return;
im->fd = ipcconnect();
close(im->fd);
im->fd = -1;
im->ext = 0;
im->caretsent = 0;
if(notify)
setpreedit(im, "", 0);
else{
im->pre[0] = '\0';
im->prelen = 0;
}
}
static void
srvclose(Im *im)
{
int was;
srvdrop(im, 1);
}
if(im->fd >= 0)
close(im->fd);
im->fd = -1;
was = im->prelen;
im->pre[0] = '\0';
im->prelen = 0;
if(was > 0){
g_signal_emit_by_name(im, "preedit-changed");
g_signal_emit_by_name(im, "preedit-end");
/* Both buffers are char[Ipcfieldmax+1]. */
static int
readresp(Im *im, int want, char *commit, char *pre, Ipcresp *resp)
{
if(ipcreadresp(im->fd, want, commit, pre, resp) < 0)
return -1;
if(!g_utf8_validate(commit, resp->commitlen, NULL) ||
(want && !g_utf8_validate(pre, resp->preeditlen, NULL))){
errno = EPROTO;
return -1;
}
return 0;
}
static void
dropwindow(Im *im)
{
if(im->win != NULL)
g_object_unref(im->win);
im->win = NULL;
im->cursorvalid = 0;
}
/* Root-relative caret in device pixels; returns whether it is known. */
static int
caretget(Im *im, int32_t *x, int32_t *y, int32_t *h)
{
gint rx, ry, scale;
int64_t sx, sy, sh;
*x = *y = *h = 0;
if(im->win != NULL && gdk_window_is_destroyed(im->win))
dropwindow(im);
if(!im->cursorvalid || im->win == NULL || im->cursor.height < 0)
return 0;
#ifdef GDK_WINDOWING_X11
if(!GDK_IS_X11_WINDOW(im->win))
return 0;
#else
return 0;
#endif
scale = gdk_window_get_scale_factor(im->win);
if(scale <= 0)
return 0;
/* GDK translates the client-relative point before device scaling. */
gdk_window_get_root_coords(im->win, 0, 0, &rx, &ry);
sx = ((int64_t)rx + im->cursor.x) * scale;
sy = ((int64_t)ry + im->cursor.y) * scale;
sh = (int64_t)im->cursor.height * scale;
if(sx < INT32_MIN || sx > INT32_MAX ||
sy < INT32_MIN || sy > INT32_MAX || sh > INT32_MAX)
return 0;
*x = sx;
*y = sy;
*h = sh;
return 1;
}
static int
readresp(Im *im, char *buf, int bufsz)
sendcaret(Im *im)
{
Ipcresp resp;
int was;
unsigned char buf[Ipccaretsz];
int32_t x, y, h;
int valid;
was = im->prelen;
if(ipcreadresp(im->fd, 1, buf, bufsz, im->pre,
sizeof(im->pre), &resp) < 0)
if(im->fd < 0 || !im->ext)
return 0;
valid = caretget(im, &x, &y, &h);
ipcpackcaret(buf, valid, x, y, h);
if(im->caretsent && memcmp(buf, im->sent, sizeof buf) == 0)
return 0;
if(ipcsend(im->fd, buf, sizeof buf) < 0)
return -1;
im->prelen = resp.npreedit;
if(was == 0 && im->prelen > 0)
g_signal_emit_by_name(im, "preedit-start");
if(was != 0 || im->prelen != 0)
g_signal_emit_by_name(im, "preedit-changed");
if(was > 0 && im->prelen == 0)
g_signal_emit_by_name(im, "preedit-end");
return resp.eaten;
memcpy(im->sent, buf, sizeof buf);
im->caretsent = 1;
return 0;
}
static uint32_t
kget(uint32_t gdk)
static int
srvconnect(Im *im)
{
uint32_t u;
unsigned char buf[Ipcreqsz];
char commit[Ipcfieldmax+1];
char pre[Ipcfieldmax+1];
Ipcresp resp;
u = gdk_keyval_to_unicode(gdk);
if((gdk & 0xff000000) == 0x01000000 && u != 0)
return u;
if(gdk >= 0xff00 && gdk <= 0xffff)
return Kspec + (gdk - 0xff00);
return u;
if(im->fd >= 0)
return 0;
im->fd = ipcconnect();
if(im->fd < 0)
return -1;
/* Marker zero is an old daemon's harmless key-zero response. */
ipcpackcap(buf, im->usepreedit);
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
readresp(im, im->usepreedit, commit, pre, &resp) < 0){
srvclose(im);
return -1;
}
im->ext = resp.eaten != 0;
if(sendcaret(im) < 0){
srvclose(im);
return -1;
}
return 0;
}
static uint32_t
mget(uint32_t state)
static void
simplecommit(GtkIMContext *ctx, const char *s, Im *im)
{
uint32_t m;
m = 0;
if(state & GDK_SHIFT_MASK)
m |= Mshift;
if(state & GDK_CONTROL_MASK)
m |= Mctrl;
if(state & GDK_MOD1_MASK)
m |= Malt;
if(state & GDK_SUPER_MASK)
m |= Msuper;
return m;
(void)ctx;
(void)s;
im->simpledone = 1;
}
static void
simpleend(GtkIMContext *ctx, Im *im)
{
(void)ctx;
im->simpledone = 1;
}
/*
* GtkIMContextSimple composes dead keys and Compose sequences; it keeps
* the key stream until it commits, ends its preedit, or rejects a press.
*/
static gboolean
simplefilter(GtkIMContext *ctx, GdkEventKey *ev, int release)
{
Im *im;
gboolean r;
im = (Im*)ctx;
im->simpleactive = 1;
im->simpledone = 0;
r = parentim->filter_keypress(ctx, ev);
if((!r && !release) || im->simpledone)
im->simpleactive = 0;
return r;
}
/* The daemon hands the pending text back; it becomes committed text. */
static void
sendreset(Im *im)
{
unsigned char buf[Ipcreqsz];
char resp[Ipcfieldmax+1];
char commit[Ipcfieldmax+1];
char pre[Ipcfieldmax+1];
Ipcresp resp;
if(im->fd < 0){
setpreedit(im, "", 0);
return;
}
ipcpackreset(buf, im->usepreedit);
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
readresp(im, im->usepreedit, commit, pre, &resp) < 0){
srvclose(im);
return;
}
ipcpackreset(buf, 1);
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
readresp(im, resp, sizeof(resp)) < 0)
srvclose(im);
}
static gboolean
plaincommit(GtkIMContext *ctx, uint32_t key, uint32_t mod)
{
char u[8];
int n;
if(mod & (Mctrl|Malt|Msuper))
return FALSE;
if(key < 0x20 || key >= Kspec)
return FALSE;
n = g_unichar_to_utf8(key, u);
u[n] = '\0';
g_signal_emit_by_name(ctx, "commit", u);
return TRUE;
setpreedit(im, "", 0);
if(commit[0] != '\0')
g_signal_emit_by_name(im, "commit", commit);
}
static gboolean
@@ -134,41 +248,45 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
{
Im *im;
unsigned char buf[Ipcreqsz];
char resp[Ipcfieldmax+1];
char commit[Ipcfieldmax+1];
char pre[Ipcfieldmax+1];
uint32_t key, mod;
int r;
GtkInputPurpose purpose;
Ipcresp resp;
im = (Im*)ctx;
if(ev->type != GDK_KEY_PRESS)
if(ev->type != GDK_KEY_PRESS){
if(im->simpleactive)
return simplefilter(ctx, ev, 1);
return FALSE;
key = kget(ev->keyval);
if(key == 0)
return FALSE;
mod = mget(ev->state);
g_object_get(ctx, "input-purpose", &purpose, NULL);
if(purpose == GTK_INPUT_PURPOSE_PASSWORD || purpose == GTK_INPUT_PURPOSE_PIN)
return plaincommit(ctx, key, mod);
srvconnect(im);
if(im->fd < 0){
srvclose(im);
return plaincommit(ctx, key, mod);
}
ipcpackreq(buf, 1, mod, key);
if(ipcsend(im->fd, buf, sizeof buf) < 0){
if(im->simpleactive)
return simplefilter(ctx, ev, 0);
key = ipckeysym(ev->keyval, gdk_keyval_to_unicode(ev->keyval));
mod = ipcmod(ev->state);
if(im->private || key == 0)
return simplefilter(ctx, ev, 0);
if(srvconnect(im) < 0)
return simplefilter(ctx, ev, 0);
/* A retained GDK window may have moved since the last cursor report. */
if(sendcaret(im) < 0){
srvclose(im);
return plaincommit(ctx, key, mod);
return simplefilter(ctx, ev, 0);
}
r = readresp(im, resp, sizeof(resp));
if(r < 0){
ipcpackreq(buf, im->usepreedit, mod, key);
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
readresp(im, im->usepreedit, commit, pre, &resp) < 0){
srvclose(im);
return plaincommit(ctx, key, mod);
return simplefilter(ctx, ev, 0);
}
if(resp[0] != '\0')
g_signal_emit_by_name(ctx, "commit", resp);
if(r != 0)
if(im->usepreedit && commit[0] != '\0' && im->prelen > 0)
setpreedit(im, "", 0);
if(commit[0] != '\0')
g_signal_emit_by_name(ctx, "commit", commit);
if(im->usepreedit)
setpreedit(im, pre, resp.preeditlen);
if(resp.eaten)
return TRUE;
return plaincommit(ctx, key, mod);
return simplefilter(ctx, ev, 0);
}
static void
@@ -179,6 +297,10 @@ getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs,
PangoAttribute *u;
im = (Im*)ctx;
if(im->simpleactive){
parentim->get_preedit_string(ctx, str, attrs, cursor_pos);
return;
}
if(str)
*str = g_strdup(im->pre);
if(attrs){
@@ -197,6 +319,9 @@ getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs,
static void
reset(GtkIMContext *ctx)
{
if(parentim->reset != NULL)
parentim->reset(ctx);
((Im*)ctx)->simpleactive = 0;
sendreset((Im*)ctx);
}
@@ -206,28 +331,136 @@ focusout(GtkIMContext *ctx)
Im *im;
im = (Im*)ctx;
if(parentim->focus_out != NULL)
parentim->focus_out(ctx);
im->simpleactive = 0;
/* Closing the connection releases the engine. */
sendreset(im);
/* Closing the context connection releases engine ownership. */
srvclose(im);
}
static void
finalize(GObject *obj)
setusepreedit(GtkIMContext *ctx, gboolean use)
{
Im *im;
unsigned char buf[Ipcreqsz];
char commit[Ipcfieldmax+1];
char pre[Ipcfieldmax+1];
Ipcresp resp;
im = (Im*)ctx;
use = use != FALSE;
if(parentim->set_use_preedit != NULL)
parentim->set_use_preedit(ctx, use);
if(im->usepreedit == use)
return;
im->usepreedit = use;
if(!use)
setpreedit(im, "", 0);
if(im->fd < 0 || !im->ext)
return;
ipcpackcap(buf, use);
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
readresp(im, use, commit, pre, &resp) < 0 ||
!resp.eaten){
srvclose(im);
return;
}
if(use)
setpreedit(im, pre, resp.preeditlen);
}
static void
setclientwindow(GtkIMContext *ctx, GdkWindow *win)
{
Im *im;
im = (Im*)ctx;
if(win != NULL && gdk_window_is_destroyed(win))
win = NULL;
if(parentim->set_client_window != NULL)
parentim->set_client_window(ctx, win);
if(im->win == win)
return;
if(win != NULL)
g_object_ref(win);
dropwindow(im);
im->win = win;
if(sendcaret(im) < 0)
srvclose(im);
}
static void
setcursorlocation(GtkIMContext *ctx, GdkRectangle *area)
{
Im *im;
im = (Im*)ctx;
if(parentim->set_cursor_location != NULL)
parentim->set_cursor_location(ctx, area);
if(im->win != NULL && gdk_window_is_destroyed(im->win))
dropwindow(im);
if(area == NULL || im->win == NULL)
im->cursorvalid = 0;
else{
im->cursor = *area;
im->cursorvalid = 1;
}
if(sendcaret(im) < 0)
srvclose(im);
}
static int
isprivate(GtkInputPurpose purpose)
{
return purpose == GTK_INPUT_PURPOSE_PASSWORD ||
purpose == GTK_INPUT_PURPOSE_PIN;
}
static void
purposechanged(GObject *obj, GParamSpec *pspec, gpointer data)
{
Im *im;
GtkInputPurpose purpose;
int private;
(void)pspec;
(void)data;
im = (Im*)obj;
g_object_get(obj, "input-purpose", &purpose, NULL);
private = isprivate(purpose);
if(private == im->private)
return;
im->private = private;
if(private)
reset(GTK_IM_CONTEXT(im));
}
static void
dispose(GObject *obj)
{
Im *im;
im = (Im*)obj;
if(im->fd >= 0)
close(im->fd);
G_OBJECT_CLASS(g_type_class_peek_parent(G_OBJECT_GET_CLASS(obj)))->finalize(obj);
srvdrop(im, 0);
dropwindow(im);
parentobject->dispose(obj);
}
static void
init(Im *im)
{
GtkInputPurpose purpose;
im->fd = -1;
im->usepreedit = 1;
im->pre[0] = '\0';
im->prelen = 0;
g_object_get(im, "input-purpose", &purpose, NULL);
im->private = isprivate(purpose);
g_signal_connect(im, "notify::input-purpose",
G_CALLBACK(purposechanged), NULL);
g_signal_connect(im, "commit", G_CALLBACK(simplecommit), im);
g_signal_connect(im, "preedit-end", G_CALLBACK(simpleend), im);
}
static void
@@ -238,11 +471,16 @@ classinit(ImClass *klass)
ic = GTK_IM_CONTEXT_CLASS(klass);
oc = G_OBJECT_CLASS(klass);
parentim = g_type_class_peek_parent(klass);
parentobject = G_OBJECT_CLASS(parentim);
ic->filter_keypress = kpress;
ic->get_preedit_string = getpreedit;
ic->reset = reset;
ic->focus_out = focusout;
oc->finalize = finalize;
ic->set_use_preedit = setusepreedit;
ic->set_client_window = setclientwindow;
ic->set_cursor_location = setcursorlocation;
oc->dispose = dispose;
}
static const GtkIMContextInfo info = {
@@ -267,7 +505,8 @@ im_module_init(GTypeModule *mod)
0,
(GInstanceInitFunc)init,
};
imtype = g_type_module_register_type(mod, GTK_TYPE_IM_CONTEXT, "strans-gtk", &ti, 0);
imtype = g_type_module_register_type(mod, GTK_TYPE_IM_CONTEXT_SIMPLE,
"strans-gtk", &ti, 0);
}
G_MODULE_EXPORT void

168
hash.c
View File

@@ -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;
}

841
ibus.c

File diff suppressed because it is too large Load Diff

306
ipc.c
View File

@@ -1,12 +1,87 @@
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "ipc.h"
static int64_t
nowms(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static int64_t
deadline(void)
{
return nowms() + Ipcwaitms;
}
static int
waitfd(int fd, short events, int64_t until)
{
struct pollfd pfd;
int64_t left;
int n;
pfd.fd = fd;
pfd.events = events;
for(;;){
left = until - nowms();
if(left <= 0){
errno = ETIMEDOUT;
return -1;
}
n = poll(&pfd, 1, left);
if(n < 0 && errno == EINTR)
continue;
if(n < 0)
return -1;
if(n == 0){
errno = ETIMEDOUT;
return -1;
}
return 0;
}
}
static int
readwait(int fd, void *buf, size_t n, int64_t until)
{
unsigned char *p;
ssize_t r;
p = buf;
while(n > 0){
r = recv(fd, p, n, MSG_DONTWAIT);
if(r < 0 && errno == EINTR)
continue;
if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){
if(waitfd(fd, POLLIN, until) < 0)
return -1;
continue;
}
if(r < 0)
return -1;
if(r == 0){
errno = ECONNRESET;
return -1;
}
p += r;
n -= r;
}
return 0;
}
static void
putlen(unsigned char p[Ipclensz], size_t n)
{
@@ -20,14 +95,33 @@ getlen(const unsigned char p[Ipclensz])
return p[0] | (p[1] << 8);
}
static void
put32(unsigned char *p, int32_t v)
{
uint32_t u;
u = v;
p[0] = u;
p[1] = u >> 8;
p[2] = u >> 16;
p[3] = u >> 24;
}
static int32_t
get32(const unsigned char *p)
{
return (int32_t)((uint32_t)p[0] |
((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) |
((uint32_t)p[3] << 24));
}
int
ipcpath(char *dst, size_t cap)
{
const char *dir, *sep;
int n;
if(dst == NULL || cap == 0)
return -1;
dir = getenv("XDG_RUNTIME_DIR");
if(dir != NULL && dir[0] == '/'){
sep = dir[strlen(dir)-1] == '/' ? "" : "/";
@@ -40,6 +134,11 @@ ipcpath(char *dst, size_t cap)
return 0;
}
/*
* A nonblocking AF_UNIX connect completes at once or fails with EAGAIN
* when the daemon's backlog is full; the socket stays nonblocking because
* every transfer below polls with a deadline.
*/
int
ipcconnect(void)
{
@@ -52,15 +151,49 @@ ipcconnect(void)
errno = ENAMETOOLONG;
return -1;
}
fd = socket(AF_UNIX, SOCK_STREAM, 0);
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if(fd < 0)
return -1;
if(connect(fd, (struct sockaddr*)&addr, sizeof addr) == 0)
return fd;
e = errno;
close(fd);
errno = e;
return -1;
if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0){
e = errno;
close(fd);
errno = e;
return -1;
}
return fd;
}
/*
* Engine key for an X keysym and its Unicode value: printable characters
* as themselves, the function keysyms 0xff00-0xffff as Kspec+offset.
* The keypad and Shift+Tab (ISO_Left_Tab) variants fold onto Enter and Tab.
*/
uint32_t
ipckeysym(uint32_t sym, uint32_t unicode)
{
if(unicode >= ' ' && unicode != 0x7f)
return unicode;
switch(sym){
case 0xfe20:
case 0xff89:
return Ktab;
case 0xff8d:
return Kret;
}
if(sym >= 0xff00 && sym <= 0xffff)
return Kspec + (sym - 0xff00);
return unicode;
}
uint32_t
ipcmod(uint32_t state)
{
uint32_t m;
m = state & Mmask;
if(state & Msupervirt)
m |= Msuper;
return m;
}
void
@@ -82,6 +215,32 @@ ipcpackreset(unsigned char req[Ipcreqsz], int want)
req[0] = Ipcreqreset | (want ? Ipcreqwant : 0);
}
/* An old server sees this reserved frame as a key-zero request. */
void
ipcpackcap(unsigned char req[Ipcreqsz], int want)
{
memset(req, 0, Ipcreqsz);
req[0] = Ipcext | (want ? Ipcreqwant : 0);
req[1] = Ipcversion;
req[2] = Ipcopcap;
}
void
ipcpackcaret(unsigned char req[Ipccaretsz], int valid, int32_t x,
int32_t y, int32_t h)
{
memset(req, 0, Ipccaretsz);
req[0] = Ipcext;
req[1] = Ipcversion;
req[2] = Ipcopcaret;
req[3] = valid != 0;
if(valid){
put32(req + 4, x);
put32(req + 8, y);
put32(req + 12, h);
}
}
void
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
uint32_t *key)
@@ -94,6 +253,40 @@ ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
((uint32_t)req[5] << 24);
}
int
ipcreqtype(const unsigned char req[Ipcreqsz])
{
if((req[0] & Ipcext) == 0)
return Ipckey;
if(req[1] != Ipcversion)
return Ipcunknown;
switch(req[2]){
case Ipcopcap:
if((req[0] & ~(Ipcext|Ipcreqwant)) != 0 ||
req[3] != 0 || req[4] != 0 || req[5] != 0)
return Ipcunknown;
return Ipccap;
case Ipcopcaret:
if(req[0] != Ipcext || req[3] > 1)
return Ipcunknown;
return Ipccaret;
}
return Ipcunknown;
}
int
ipcunpackcaret(const unsigned char req[Ipccaretsz], int *valid,
int32_t *x, int32_t *y, int32_t *h)
{
if(ipcreqtype(req) != Ipccaret || get32(req + 12) < 0)
return -1;
*valid = req[3];
*x = *valid ? get32(req + 4) : 0;
*y = *valid ? get32(req + 8) : 0;
*h = *valid ? get32(req + 12) : 0;
return 0;
}
int
ipcreqreset(const unsigned char req[Ipcreqsz])
{
@@ -109,21 +302,15 @@ ipcpackresp(unsigned char *dst, size_t cap, int eaten,
if(ncommit > Ipcfieldmax || npreedit > Ipcfieldmax)
return -1;
if((ncommit > 0 && commit == NULL) ||
(want && npreedit > 0 && preedit == NULL))
return -1;
n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0);
if(dst == NULL || cap < n)
if(cap < n)
return -1;
dst[0] = eaten != 0;
putlen(dst + 1, ncommit);
if(ncommit > 0)
memcpy(dst + Ipcresphdrsz, commit, ncommit);
memcpy(dst + Ipcresphdrsz, commit, ncommit);
if(want){
putlen(dst + Ipcresphdrsz + ncommit, npreedit);
if(npreedit > 0)
memcpy(dst + Ipcresphdrsz + ncommit + Ipclensz,
preedit, npreedit);
memcpy(dst + Ipcresphdrsz + ncommit + Ipclensz, preedit, npreedit);
}
return n;
}
@@ -152,73 +339,78 @@ ipcsend(int fd, const void *buf, size_t n)
{
const unsigned char *p;
ssize_t r;
int64_t until;
p = buf;
until = deadline();
while(n > 0){
r = send(fd, p, n, MSG_NOSIGNAL);
r = send(fd, p, n, MSG_NOSIGNAL|MSG_DONTWAIT);
if(r < 0 && errno == EINTR)
continue;
if(r <= 0)
if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){
if(waitfd(fd, POLLOUT, until) < 0)
return -1;
continue;
}
if(r < 0)
return -1;
if(r == 0){
errno = EPIPE;
return -1;
}
p += r;
n -= r;
}
return 0;
}
/* Reads a length-prefixed field into dst[Ipcfieldmax+1]. */
static int
readfield(int fd, size_t n, char *dst, size_t cap)
readfield(int fd, char *dst, int64_t until)
{
unsigned char discard[128];
size_t keep, part;
unsigned char len[Ipclensz];
size_t n;
if(cap > 0 && dst == NULL)
if(readwait(fd, len, sizeof len, until) < 0)
return -1;
keep = 0;
if(cap > 0)
keep = n >= cap ? cap - 1 : n;
if(keep > 0 && ipcreadn(fd, dst, keep) < 0)
n = getlen(len);
if(n > Ipcfieldmax){
errno = EPROTO;
return -1;
if(cap > 0)
dst[keep] = '\0';
n -= keep;
while(n > 0){
part = n < sizeof discard ? n : sizeof discard;
if(ipcreadn(fd, discard, part) < 0)
return -1;
n -= part;
}
return 0;
if(readwait(fd, dst, n, until) < 0)
return -1;
dst[n] = '\0';
return n;
}
int
ipcreadresp(int fd, int want, char *commit, size_t ccap,
char *preedit, size_t pcap, Ipcresp *resp)
ipcreadresp(int fd, int want, char *commit, char *preedit, Ipcresp *resp)
{
unsigned char hdr[Ipcresphdrsz], npreedit[Ipclensz];
unsigned char eaten;
int64_t until;
int n;
if(resp == NULL || (ccap > 0 && commit == NULL) ||
(pcap > 0 && preedit == NULL))
return -1;
if(ccap > 0)
commit[0] = '\0';
if(pcap > 0)
preedit[0] = '\0';
commit[0] = '\0';
preedit[0] = '\0';
memset(resp, 0, sizeof *resp);
if(ipcreadn(fd, hdr, sizeof hdr) < 0)
until = deadline();
if(readwait(fd, &eaten, 1, until) < 0)
return -1;
resp->eaten = hdr[0] != 0;
resp->ncommit = getlen(hdr + 1);
if(resp->ncommit > Ipcfieldmax)
if(eaten > 1){
errno = EPROTO;
return -1;
if(readfield(fd, resp->ncommit, commit, ccap) < 0)
}
resp->eaten = eaten;
n = readfield(fd, commit, until);
if(n < 0)
return -1;
resp->commitlen = n;
if(!want)
return 0;
if(ipcreadn(fd, npreedit, sizeof npreedit) < 0)
n = readfield(fd, preedit, until);
if(n < 0)
return -1;
resp->npreedit = getlen(npreedit);
if(resp->npreedit > Ipcfieldmax)
return -1;
return readfield(fd, resp->npreedit, preedit, pcap);
resp->preeditlen = n;
return 0;
}

45
ipc.h
View File

@@ -2,21 +2,33 @@
#include <stdint.h>
/*
* Request: [flags, modifiers, key byte 0, ..., key byte 3].
* Flags request preedit and distinguish lifecycle reset from physical Escape.
* Key request: [flags, modifiers, key byte 0, ..., key byte 3].
* The want flag asks for the preedit text: the client then draws it and
* the popup does not. The reset flag is a lifecycle reset, not Escape.
* The server identifies the connection as the engine owner; that identity is
* not sent on the wire.
* Only Mmask modifier bits are retained.
* Response: [eaten, commit-length-low, commit-length-high, commit...],
* followed, when requested, by
* [preedit-length-low, preedit-length-high, preedit...].
* Lengths are little-endian byte counts; fields are at most Ipcfieldmax bytes.
* Lengths are little-endian byte counts; fields are at most Ipcfieldmax bytes,
* so callers read them into char[Ipcfieldmax+1].
*
* Capability control is six bytes: [0x80|want, version, 0, 0, 0, 0]; the
* server always answers it eaten, which tells the client that extension
* frames are understood.
* Caret control is sixteen bytes: [0x80, version, 1, valid, x, y, h],
* with the signed coordinates and height encoded as little-endian 32-bit words.
*/
enum
{
Ipcreqwant = 1<<0,
Ipcreqreset = 1<<1,
Ipcext = 1<<7,
Ipcversion = 1,
Ipcopcap = 0,
Ipcopcaret = 1,
Kspec = 0x110000,
Kback = Kspec|0x08,
@@ -25,37 +37,58 @@ enum
Kesc = Kspec|0x1b,
Kup = Kspec|0x52,
Kdown = Kspec|0x54,
Kpgup = Kspec|0x55,
Kpgdown = Kspec|0x56,
Kmodfirst = Kspec|0xe1,
Kmodlast = Kspec|0xee,
/* X core modifier bits; GDK and IBus also flag Super at bit 26. */
Mshift = 1<<0,
Mctrl = 1<<2,
Malt = 1<<3,
Msuper = 1<<6,
Mmask = Mshift|Mctrl|Malt|Msuper,
Msupervirt = 1<<26,
Ipcreqsz = 6,
Ipccaretsz = 16,
Ipclensz = 2,
Ipcresphdrsz = 1 + Ipclensz,
Ipcfieldmax = 256,
Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax,
Ipcwaitms = 250,
};
enum
{
Ipcunknown = -1,
Ipckey,
Ipccap,
Ipccaret,
};
typedef struct Ipcresp Ipcresp;
struct Ipcresp
{
int eaten;
size_t ncommit;
size_t npreedit;
/* Bytes copied to each caller buffer, excluding its trailing NUL. */
size_t commitlen;
size_t preeditlen;
};
uint32_t ipckeysym(uint32_t, uint32_t);
uint32_t ipcmod(uint32_t);
void ipcpackreq(unsigned char[Ipcreqsz], int, uint32_t, uint32_t);
void ipcpackreset(unsigned char[Ipcreqsz], int);
void ipcpackcap(unsigned char[Ipcreqsz], int);
void ipcpackcaret(unsigned char[Ipccaretsz], int, int32_t, int32_t, int32_t);
void ipcunpackreq(const unsigned char[Ipcreqsz], int*, uint32_t*, uint32_t*);
int ipcunpackcaret(const unsigned char[Ipccaretsz], int*, int32_t*, int32_t*, int32_t*);
int ipcreqtype(const unsigned char[Ipcreqsz]);
int ipcreqreset(const unsigned char[Ipcreqsz]);
int ipcpackresp(unsigned char*, size_t, int, const char*, size_t, const char*, size_t, int);
int ipcreadn(int, void*, size_t);
int ipcsend(int, const void*, size_t);
int ipcreadresp(int, int, char*, size_t, char*, size_t, Ipcresp*);
int ipcreadresp(int, int, char*, char*, Ipcresp*);
int ipcpath(char*, size_t);
int ipcconnect(void);

16
ko.c
View File

@@ -189,6 +189,10 @@ issyl(Rune r)
return r >= Sbase && r < Sbase + Ncho*Njung*Njong;
}
/*
* The pending text is at most one syllable; a key either extends it,
* commits it and starts another, or commits it and passes through.
*/
Emit
transko(Im *im, Rune c)
{
@@ -199,10 +203,7 @@ transko(Im *im, Rune c)
memset(&e, 0, sizeof e);
jm = keytojamo(c);
if(jm == 0){
if(im->pre.n > 0){
e.s = im->pre;
sclear(&im->pre);
}
e.s = im->pre;
return e;
}
@@ -217,14 +218,12 @@ transko(Im *im, Rune c)
ci = Choidx(last);
ji = Jungidx(jm);
if(ci >= 0 && ji >= 0){
spopr(&im->pre);
sputr(&e.next, compose(ci, ji, 0));
return e;
}
if(Jungidx(last) >= 0 && ji >= 0){
comb = combine(cvow, nelem(cvow), last, jm);
if(comb){
spopr(&im->pre);
sputr(&e.next, comb);
return e;
}
@@ -239,7 +238,6 @@ transko(Im *im, Rune c)
if(joi == 0){
ni = Jongidx(jm);
if(ni > 0){
spopr(&im->pre);
sputr(&e.next, compose(ci, ji, ni));
return e;
}
@@ -248,7 +246,6 @@ transko(Im *im, Rune c)
comb = combine(cvow, nelem(cvow), jung[ji], jm);
if(comb){
ni = Jungidx(comb);
spopr(&im->pre);
sputr(&e.next, compose(ci, ni, 0));
return e;
}
@@ -260,7 +257,6 @@ transko(Im *im, Rune c)
if(comb){
ni = Jongidx(comb);
if(ni > 0){
spopr(&im->pre);
sputr(&e.next, compose(ci, ji, ni));
return e;
}
@@ -271,14 +267,12 @@ transko(Im *im, Rune c)
if(splitpair(cjong, nelem(cjong), jc, &stay, &next)){
si = Jongidx(stay);
ni = Choidx(next);
spopr(&im->pre);
sputr(&e.s, compose(ci, ji, si));
sputr(&e.next, compose(ni, vi, 0));
return e;
}
ni = Choidx(jc);
if(ni >= 0){
spopr(&im->pre);
sputr(&e.s, compose(ci, ji, 0));
sputr(&e.next, compose(ni, vi, 0));
return e;

31
main.c
View File

@@ -3,15 +3,11 @@
Channel *drawc;
Channel *keyc;
Channel *dictreqc;
Channel *dictresc;
char **fontfiles;
int nfontfiles;
void
usage(void)
{
fprint(2, "usage: strans mapdir fontfile [fontfile ...]\n");
fprint(2, "usage: strans mapdir\n");
threadexitsall("usage");
}
@@ -51,25 +47,20 @@ erealloc(void *p, ulong n)
void
threadmain(int argc, char **argv)
{
if(argc < 3 || argc > Maxfonts + 2)
char *display;
if(argc != 2)
usage();
fontfiles = argv + 2;
nfontfiles = argc - 2;
drawc = chancreate(sizeof(Drawcmd), 4);
keyc = chancreate(sizeof(Keyreq), 0);
dictreqc = chancreate(sizeof(Dictreq), 4);
dictresc = chancreate(sizeof(Dictres), 0);
mapinit(argv[1]);
dictinit(argv[1]);
langinit(argv[1]);
srvinit();
if(proccreate(drawthread, nil, 16384) < 0)
die("can't create draw worker");
if(proccreate(srvthread, nil, 16384) < 0)
die("can't create server worker");
if(proccreate(ibusthread, nil, 32768) < 0)
die("can't create IBus worker");
if(threadcreate(dictthread, nil, 16384) < 0)
die("can't create dictionary worker");
proccreate(drawthread, nil, 16384);
proccreate(srvthread, nil, 16384);
proccreate(ibusthread, nil, 32768);
display = getenv("DISPLAY");
if(display != nil && display[0] != '\0')
proccreate(ximthread, nil, 32768);
imthread(nil);
}

View File

@@ -14,7 +14,7 @@ It contains no word rows such as `견출지` or `방학`. `mkhanja` validates t
contract and groups rows by their Hangul reading to produce the existing
runtime dictionary format. Candidate order follows source order. The source
keeps all retained pairs for review; the generated dictionary stores the first
32 candidates per reading because that is the engine's lookup limit.
128 candidates per reading because that is the engine's lookup limit.
The source is derived from libhangul release tag `libhangul-0.2.0`. The
annotated tag object is `20afc38922e3595ee3ed5b186f2ea05afe663763`, its
@@ -78,4 +78,5 @@ rejected; rewrite or omit those rows before import.
`verifymap.py` checks UTF-8, row structure, unique keys, 64-rune keys and
values, canonical candidate spacing, and duplicate dictionary candidates.
Pass it the exact `.map` and `.dict` files installed by the build.
Pass it the exact `.map` and `.dict` files used for runtime or validation.
The normal build does not install map data.

View File

@@ -25,87 +25,87 @@
;; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
;; POSSIBILITY OF SUCH DAMAGE.
가 可 家 加 歌 價 街 假 佳 暇 架 价 賈 伽 柯 迦 軻 嘉 駕 嫁 稼 哥 呵 苛 袈 訶 枷 珂 茄 痂 跏 斝 葭
각 各 角 脚 覺 閣 刻 却 珏 恪 殼 慤 榷 骼 桷 礭 搉 卻 躩 咯 埆 擱 塙 阁 愨 覚 礐 殻 胳 傕 崅 茖 佫
간 間 干 看 乾 幹 姦 刊 肝 簡 懇 奸 艮 杆 諫 艱 揀 澗 竿 墾 癎 侃 柬 磵 桿 稈 赶 衎 慳 榦 玕 迀 偘
갈 渴 葛 鞨 竭 喝 褐 曷 碣 蝎 乫 羯 秸 鶡 楬 噶 蠍 暍 渇 輵 轕 鍻 鹖 圿 嵑 嶱 扴 擖 毼 獦 礍 鞂 騔
감 感 減 敢 甘 監 鑑 憾 邯 堪 勘 瞰 紺 柑 疳 坎 戡 鑒 嵌 橄 龕 歛 酣 撼 欿 减 弇 嵁 闞 憨 澉 轗 坩
갑 甲 閘 岬 鉀 匣 胛 韐 瞌 帢 㠷 钾 闸 厒 搕 敆 玾 笚 筪 舺 醘 㓣 㕅 㕉 㗐 㧁 㪉 㭱 䐦 䖬 䗘 䘥 䪺
가 可 家 加 歌 價 街 假 佳 暇 架 价 賈 伽 柯 迦 軻 嘉 駕 嫁 稼 哥 呵 苛 袈 訶 枷 珂 茄 痂 跏 斝 葭 舸 笳 哿 坷 檟 謌 珈 砢 佉 呿 榎 仮 傢 咖 瘕 耞 宊 椵 叚 価 猳 髂 瘸 鴐 䴥 拁 㤎 榢 毠 溊 牁 豭 槚 诃 贾 轲 镓 驾 鲄 婽 尜 岢 幏 彁 徦 愘 戓 戨 斚 樖 泇 渮 滒 炣 牫 牱 犌 玍 癿 笴 糘 胢 腵 蚵 貑 跒 酠 鉫 鉲 鎵 鎶 魺 鴚 麚 㗎 㚙 㞹 㢦 㤉 㧝 㪃 㪼 㱒 㹢 䂟 䈔 䑝 䔅 䕒 䪪 䯊 䶗 嘏
각 各 角 脚 覺 閣 刻 却 珏 恪 殼 慤 榷 骼 桷 礭 搉 卻 躩 咯 埆 擱 塙 阁 愨 覚 礐 殻 胳 傕 崅 茖 佫 墧 斍 腳 隺 㩁 㰌 㴶 壳 悫 搁 觉 愙 捔 斠 櫊 毃 玨 袼 覐 踋 鵅 鵤 㑢 㔖 㕁 㙾 㤩 㱋 㱿 㲉 㾡 䁷 䇶 䐘 䖼 䟩 䧄 䶅
간 間 干 看 乾 幹 姦 刊 肝 簡 懇 奸 艮 杆 諫 艱 揀 澗 竿 墾 癎 侃 柬 磵 桿 稈 赶 衎 慳 榦 玕 迀 偘 秆 栞 茛 齦 间 恳 旰 简 㸔 簳 瞯 㵎 趕 暕 盰 覸 顅 芉 囏 涆 貇 鬝 㿻 悭 拣 擀 涧 痫 羟 艰 裥 谏 踬 锏 龈 倝 墹 姧 婜 孯 忓 攼 檊 癇 癷 皯 硍 礀 筸 粯 繝 羥 葌 蕑 蕳 虷 衦 裉 襇 襉 豤 鐗 鐧 靬 馯 鬜 鳱 鶫 㫔 㶕 㶥 㸧 䉍 䓸 䔵 䧲 䫀 䯎 䳚 䵟 矸
갈 渴 葛 鞨 竭 喝 褐 曷 碣 蝎 乫 羯 秸 鶡 楬 噶 蠍 暍 渇 輵 轕 鍻 鹖 圿 嵑 嶱 扴 擖 毼 獦 礍 鞂 騔 骱 㐓 㓞 㓤 㠰 㬞 㮖 㵣 㵧 䂒 䅥 䈓
감 感 減 敢 甘 監 鑑 憾 邯 堪 勘 瞰 紺 柑 疳 坎 戡 鑒 嵌 橄 龕 歛 酣 撼 欿 减 弇 嵁 闞 憨 澉 轗 坩 埳 泔 淦 矙 鹻 监 瑊 鍳 砍 椷 歁 鹼 堿 塪 惂 撖 汵 蚶 詌 碱 绀 鉴 鳡 鹐 龛 佄 冚 凲 凵 厱 咁 墈 尲 尶 崁 嵅 玪 玵 甝 磡 礛 竷 篏 粓 苷 莰 譼 贑 輡 轁 鑬 魐 鱤 鵮 鹸 㒈 㔋 㔶 㘛 㙳 㜟 㤌 㦑 㪠 㮀 㰸 㺂 㽍 䀍 䃭 䇞 䊻 䌠 䗣 䘓 䛓 䤗 䧩 䲺
갑 甲 閘 岬 鉀 匣 胛 韐 瞌 帢 㠷 钾 闸 厒 搕 敆 玾 笚 筪 舺 醘 㓣 㕅 㕉 㗐 㧁 㪉 㭱 䐦 䖬 䗘 䘥 䪺
갓 㖙
강 江 强 講 降 康 綱 鋼 姜 剛 疆 岡 彊 崗 慷 腔 薑 糠 舡 絳 襁 羌 堈 畺 鱇 杠 僵 忼 跫 橿 韁 茳 壃
개 改 開 個 皆 豈 槪 介 蓋 慨 价 塏 箇 漑 芥 凱 愾 盖 鎧 愷 恝 疥 槩 剴 玠 揩 匃 闓 磕 个 丐 葢 㮣
강 江 强 講 降 康 綱 鋼 姜 剛 疆 岡 彊 崗 慷 腔 薑 糠 舡 絳 襁 羌 堈 畺 鱇 杠 僵 忼 跫 橿 韁 茳 壃 扛 釭 殭 矼 繈 穅 罡 羗 羫 傋 豇 強 冈 奋 棡 掆 犺 玒 糨 繮 峕 嵻 鏹 㽘 缰 刚 岗 绛 讲 钢 镪 冮 勥 匟 唴 啌 嗴 囥 堽 夅 嫝 嵹 弜 弶 摃 摾 椌 槺 溬 漒 漮 焵 牨 犅 犟 猐 獇 玜 琷 瓨 畕 疅 砊 碙 礓 筻 粇 糡 繦 翞 耩 膙 葁 蔃 蜣 袶 謽 躿 鎠 鏮 顜 㓻 㔔 㛨 㝩 㟠 㟵 㢜 㧏 㩖 㭃 㭎 㱂 㳾 㹔 㼚
개 改 開 個 皆 豈 槪 介 蓋 慨 价 塏 箇 漑 芥 凱 愾 盖 鎧 愷 恝 疥 槩 剴 玠 揩 匃 闓 磕 个 丐 葢 㮣 嘅 喈 湝 祴 礚 暟 蚧 稭 概 烗 䁗 䡷 凯 剀 垲 开 恺 钙 铠 锎 锴 闿 丯 乢 勓 匄 吤 嗰 奒 媘 尬 岕 幆 庎 忋 忦 戤 摡 斺 溉 炌 炏 煯 犗 琾 瓂 砎 祄 絠 腉 蒈 蝔 衸 郂 鈣 鍇 鎎 鐦 阣 颽 魀 魪 鶛 㑘 㕢 㝏 㠹 㨟 㪡 㯼 㱾 㿍 䃈 䇒 䏗 䐩 䐴 䒓 䤤 䪱 䯰 䰺 䱄 䲸
객 客 喀 塥 揢 碦 衉 㗆 䘔
갯 㖋 㖎
갱 更 坑 羹 粳 賡 鏗 硜 秔 鍞 阬 硻 䎴 硁 赓 铿 劥 坈 妔 稉 羮 銵 㧶 㰢 㹴 䃘 䡖 䡠 䡰 䢚
갹 醵 蹻 噱 谻 臄 勪 㖸 㷾 䣰
거 車 去 居 巨 擧 拒 距 據 渠 倨 醵 遽 据 祛 鉅 炬 踞 鋸 迲 裾 袪 蘧 莒 蕖 秬 籧 駏 腒 苣 胠 筥 佉
거 車 去 居 巨 擧 拒 距 據 渠 倨 醵 遽 据 祛 鉅 炬 踞 鋸 迲 裾 袪 蘧 莒 蕖 秬 籧 駏 腒 苣 胠 筥 佉 呿 昛 舉 詎 琚 柜 㨿 㩀 耟 簴 㞐 鐻 璖 虡 弆 磲 阹 椐 璩 佢 俥 壉 岠 怇 懅 涺 鶋 举 拠 挙 榉 讵 钜 键 锯 伡 凥 勮 匷 厺 啹 姖 婮 崌 攑 櫸 欅 歫 洰 淭 澽 粔 紶 翗 艍 菃 蚷 蜛 螶 蟝 衐 襷 詓 豦 躆 鮔 鱋 麮 鼁 﨔 㝒 㠪 㣄 㪯 㫢 㭕 㯫 㰦 䅕 䆽 䒧 䛯 䝣 䝻 䟊 䢹 䱟 䵕 䶙
걱 巪
건 建 乾 件 健 巾 鍵 虔 腱 愆 蹇 騫 楗 褰 謇 鞬 揵 犍 睷 搴 鶱 䖍 諐 湕 徤 乹 寋 㨜 骞 亁 囝 墘 寈
건 建 乾 件 健 巾 鍵 虔 腱 愆 蹇 騫 楗 褰 謇 鞬 揵 犍 睷 搴 鶱 䖍 諐 湕 徤 乹 寋 㨜 骞 亁 囝 墘 寈 弿 攐 攓 旔 榩 毽 漧 瀽 藆 踺 鑳 騝 鰎 鰬 㓺 㔓 㗔 㨴 㩃 㩮 㩷 㸫 䇂 䇟 䊕 䘜 䙭 䞿 䭈 䮿 䢖
걸 傑 乞 杰 桀 偈 朅 乬 榤 嵥 㐦 滐 搩 櫭 芞 藒 㘶 㹇 䎢 䒗
검 檢 儉 劍 劒 黔 鈐 瞼 撿 芡 岒 臉 劎 俭 倹 剑 剣 捡 检 検 睑 脸 钤 仱 剱 劔 忴 㧄 䇜 䈩 䩎
겁 怯 劫 迲 刦 刧 刼 㤼 㥘 袷 跲 昅 抾 极 砝 蜐 衱 鉣 魥 鵖 㭲 㹤 㾀 䀷 䂲 䂶
것 唟
게 揭 憩 偈 愒 垍 掲 憇
격 格 激 擊 隔 檄 膈 覡 翮 骼 闃 鬲 挌 鴃 毄 撃 繳 閴 綌 鵙 愅 滆 薂 諽 击 绤 缂 缴 觋 阒 䴗 呄 嗝
견 見 犬 堅 遣 牽 肩 絹 甄 譴 鵑 繭 俔 蠲 畎 縳 狷 繾 筧 羂 樫 鰹 牵 掔 汧 岍 熞 縴 梘 罥 汱 甽 詃
결 結 決 潔 缺 訣 拮 抉 闋 觖 絜 玦 鴃 决 鴂 鈌 駃 袺 刔 洯 逫 㓗 结 诀 阕 僪 妜 挗 炔 焆 砄 缼 芵
겸 兼 謙 慊 箝 鉗 鎌 歉 蒹 嗛 縑 傔 拑 檶 鹻 黚 鼸 岒 㻩 鶼 缣 谦 钳 鳒 鹣 嵰 槏 羬 膁 趝 鰜 䈤 䈴
격 格 激 擊 隔 檄 膈 覡 翮 骼 闃 鬲 挌 鴃 毄 撃 繳 閴 綌 鵙 愅 滆 薂 諽 击 绤 缂 缴 觋 阒 䴗 呄 嗝 垎 墼 搹 搿 敋 楁 欫 湨 燩 犑 狊 獥 礊 緙 臵 蛒 觡 譤 轚 郹 鄓 鞷 鶪 鸄 鼳 㐿 㦘 㦴 㪾 䁶 䂆 䈪 䐙 䙐 䛿 䞦 䠐
견 見 犬 堅 遣 牽 肩 絹 甄 譴 鵑 繭 俔 蠲 畎 縳 狷 繾 筧 羂 樫 鰹 牵 掔 汧 岍 熞 縴 梘 罥 汱 甽 詃 趼 鋻 坚 枧 笕 绢 缱 见 谴 钘 鲣 鹃 䌸 儙 幵 悓 挸 掮 撁 枤 涀 犭 猏 獧 瓹 睊 絸 茾 菺 蚈 蜸 蠒 裐 襺 豜 豣 鄄 鈃 銒 鏲 鑓 雃 鳽 鵳 麉 㑠 㝁 㢾 㭴 㯞 㯠 㹂 䄯 䅌 䅐 䋌 䋗 䌑 䑷 䗗 䚈 䚊 䡓 䣺 䪈 䭤 䳌 䵖 䵛 䵡 䵤 䶬
결 結 決 潔 缺 訣 拮 抉 闋 觖 絜 玦 鴃 决 鴂 鈌 駃 袺 刔 洯 逫 㓗 结 诀 阕 僪 妜 挗 炔 焆 砄 缼 芵 蒛 蚗 蛪 觼 赽 趹 迼 釨 鐑 魝 㓶 㛃 㭈 㮮 㹟 㼤 䀗 䊽 䍳 䏐 䝌 䤿 䥛 䦬 䦼 鍥
겸 兼 謙 慊 箝 鉗 鎌 歉 蒹 嗛 縑 傔 拑 檶 鹻 黚 鼸 岒 㻩 鶼 缣 谦 钳 鳒 鹣 嵰 槏 羬 膁 趝 鰜 䈤 䈴 䊴 䭠 䯡
겹 裌 唊 䛟 郟 郏 圶 掐 殎 鵊 䀫 䁍 䩡
경 京 敬 輕 經 慶 更 景 庚 驚 耕 競 境 竟 警 鏡 傾 卿 頃 徑 硬 梗 瓊 儆 璟 炅 勁 鯨 磬 頸 莖 脛 憬
계 計 界 癸 溪 季 鷄 啓 繼 戒 係 系 階 契 桂 械 繫 悸 谿 稽 棨 誡 蹊 屆 磎 堺 雞 烓 罽 薊 堦 髻 禊
고 古 高 告 苦 固 故 考 姑 孤 庫 鼓 稿 枯 顧 雇 賈 皐 錮 拷 痼 辜 膏 叩 股 袴 敲 呱 誥 槁 藁 攷 沽
곡 曲 谷 穀 哭 鵠 梏 斛 轂 嚳 縠 觳 槲 糓 榖 毂 牿 瑴 喾 鹄 俈 匤 唂 唃 嘝 浀 濲 瀔 瀫 焀 焅 硞 硲
곤 困 坤 昆 棍 袞 崑 梱 鯤 滾 琨 衮 閫 悃 髡 鵾 崐 褌 捆 堃 緄 裍 錕 鶤 齫 鯀 壼 滚 髠 璭 綑 稇 晜
경 京 敬 輕 經 慶 更 景 庚 驚 耕 競 境 竟 警 鏡 傾 卿 頃 徑 硬 梗 瓊 儆 璟 炅 勁 鯨 磬 頸 莖 脛 憬 痙 絅 耿 逕 坰 璥 擎 勍 涇 烱 暻 俓 倞 罄 扃 竸 冏 煢 惸 畊 鯁 哽 檠 綆 黥 褧 謦 駉 剄 熲 憼 焭 顈 経 軽 亰 獍 璚 曔 㯳 澃 挭 埛 麖 燝 牼 鞕 囧 藑 㷀 傹 剠 桱 浭 綗 莄 誙 鶊 䪫 径 茎 倾 刭 劲 庆 弪 氢 泾 烃 琼 痉 竞 经 绠 胫 茕 轻 迳 镜 顷 颈 颎 鲠 鲸 鹒 䌹 儝 冂 冋 坕 坙 埂 婛 峺 巠 幜
계 計 界 癸 溪 季 鷄 啓 繼 戒 係 系 階 契 桂 械 繫 悸 谿 稽 棨 誡 蹊 屆 磎 堺 雞 烓 罽 薊 堦 髻 禊 筓 綮 稧 瘈 縘 葪 届 継 洎 繋 鶏 笄 悈 紒 㻑 枅 䙆 鸂 啟 卟 呇 晵 暩 檕 甈 瘛 鍥 启 鸡 继 蓟 计 诫 锲 阶 㶉 乩 唘 啔 喺 嗘 峜 嵠 彐 彑 昋 栔 楐 檵 瀱 畍 痵 碶 筀 罊 葜 蓕 蘮 蘻 蟿 諬 銈 鎅 鑙 闙 鯚 鳮 齘 㑧 㒅 㕃 㖑 㛷 㡭 㪈 㬖 㮷 㲅 㳦 㳵 㾵 䀘 䁈 䁉 䋜 䋯 䏿 䚉 䛺 䠏 䡔 䦇 䫔 䭫 䭬 䳶
고 古 高 告 苦 固 故 考 姑 孤 庫 鼓 稿 枯 顧 雇 賈 皐 錮 拷 痼 辜 膏 叩 股 袴 敲 呱 誥 槁 藁 攷 沽 蠱 羔 苽 睾 菰 暠 尻 犒 瞽 刳 靠 翺 罟 觚 杲 栲 槀 估 詁 盬 篙 鴣 皋 酤 櫜 糕 胯 郜 羖 槹 鈷 凅 牯 稁 箍 鷱 塙 臯 髙 皷 泒 稾 鼔 吿 䯻 楛 翶 餻 柧 侤 蛄 嵪 笟 羙 胍 搞 㟸 桍 棝 槔 橰 狜 翱 薧 藳 鼛 㛈 库 绔 蛊 裤 诂 诰 轱 钴 铐 锆 锢 顾 鲓 鲴 鸪 僱 勂 叝 咕 哌 圐 堌 夃 婟 嫴 崓 崮 怘 挎
곡 曲 谷 穀 哭 鵠 梏 斛 轂 嚳 縠 觳 槲 糓 榖 毂 牿 瑴 喾 鹄 俈 匤 唂 唃 嘝 浀 濲 瀔 瀫 焀 焅 硞 硲 筁 粬 蔛 蛐 螜 逧 髷 﨏 㖆 㪶 㮂 㻃 䀰 䈸 䎥 䐨 䒼 䚛 䢗 䧊 䧼 䳔 䵈 䶜
곤 困 坤 昆 棍 袞 崑 梱 鯤 滾 琨 衮 閫 悃 髡 鵾 崐 褌 捆 堃 緄 裍 錕 鶤 齫 鯀 壼 滚 髠 璭 綑 稇 晜 蔉 蓘 蜫 鮌 䃂 壸 绲 裈 辊 锟 阃 鲧 鲲 鹍 丨 堒 尡 惃 捁 棞 涃 潉 焑 熴 猑 瑻 睏 睔 睴 硱 磙 祵 臗 菎 裩 謴 輥 醌 閸 騉 髨 㒭 㙥 㡓 㨰 㫻 㬷 㱎 䎾 䖵 䙛 䜇 䠅 䵪
골 骨 滑 汨 鶻 矻 榾 搰 汩 杚 圣 馉 鹘 傦 尳 愲 抇 縎 蓇 顝 餶 㐔 㐣 㒴 㾶 䮩 䯇
곱 䯩
곳 廤 蒊 㖛 㖜
공 工 公 功 空 共 恐 恭 供 貢 孔 攻 控 拱 鞏 珙 蚣 跫 倥 崆 蛩 釭 箜 悾 栱 槓 贛 蛬 龔 筇 㓛 㳟 羾
공 工 公 功 空 共 恐 恭 供 貢 孔 攻 控 拱 鞏 珙 蚣 跫 倥 崆 蛩 釭 箜 悾 栱 槓 贛 蛬 龔 筇 㓛 㳟 羾 鞚 廾 邛 笻 㓋 涳 卭 疘 糿 㭟 䂬 巩 贡 赣 龚 埪 塨 幊 愩 慐 拲 桏 灨 熕 硿 碽 篢 糼 舼 蒆 躻 輁 銎 髸 鵼 龏 㒶 㓚 㕬 㣉 㤟 㤨 㧬 㧭 㫒 㮪 㯯 㲁 㸜 㺬 㼦 䂵 䅃 䅝 䇨 䊄 䔈 䜤 䡗 䢼 䣏 䰸 䱋 䲲 䳍
곶 串
과 果 過 科 課 寡 誇 瓜 戈 菓 顆 袴 蝸 跨 鍋 裹 夸 窠 夥 媧 胯 撾 侉 踝 銙 蝌 騍 倮 堝 猓 稞 姱 薖
과 果 過 科 課 寡 誇 瓜 戈 菓 顆 袴 蝸 跨 鍋 裹 夸 窠 夥 媧 胯 撾 侉 踝 銙 蝌 騍 倮 堝 猓 稞 姱 薖 蜾 鈛 檛 剮 堁 淉 荂 輠 剐 埚 挝 课 过 锅 锞 颗 馃 骒 㶽 䯄 冎 咵 坬 垮 嵙 恗 惈 敤 晇 棵 歄 濄 煱 犐 瘑 簻 粿 綶 腂 膼 舿 萪 諣 踻 錁 鐹 餜 骻 髁 㐄 㗻 㚌 㛻 㡁 㧓 㪙 㳀 㳡 㸯 㼫 㽿 㾧 㿆 䆼 䈑 䈖 䌀 䙨 䠸 䣬 䫚 䯞 䴹
곽 郭 廓 槨 藿 霍 癨 躩 鞹 椁 矡 漷 钁 崞 瀖 墎 欔 籗 鞟 㗥 㸌 䣤 䦆 䧐
관 官 觀 關 館 管 冠 寬 貫 慣 款 灌 串 莞 琯 棺 瓘 罐 菅 梡 顴 盥 祼 錧 綰 丱 鸛 筦 窾 爟 雚 髖 鑵
관 官 觀 關 館 管 冠 寬 貫 慣 款 灌 串 莞 琯 棺 瓘 罐 菅 梡 顴 盥 祼 錧 綰 丱 鸛 筦 窾 爟 雚 髖 鑵 涫 輨 舘 関 観 䝺 欵 瘝 寛 毌 倌 窽 闗 礶 躀 䦎 宽 惯 掼 绾 观 贯 颧 馆 髋 鹳 卝 悹 悺 摜 樌 欟 歀 泴 潅 烪 痯 矔 窤 罆 蒄 覌 賗 遦 鏆 鑧 鱹 鳤 㮡 㯘 䏓 䕀 䗆 䗰 䘾 䙮 䤽 䥗 䩪 䪀 䲌 䲘
괄 括 檜 刮 适 恝 聒 栝 筈 髺 佸 鴰 鸹 劀 懖 桰 葀 銽 頢 颳 鬠 䒷 䟯 䣶 䦚 䯏 䯺
광 光 廣 狂 鑛 匡 曠 壙 胱 筐 洸 侊 珖 炚 絖 誑 纊 恇 獷 磺 桄 茪 框 爌 誆 礦 撗 劻 眶 迋 矌 灮 垙
광 光 廣 狂 鑛 匡 曠 壙 胱 筐 洸 侊 珖 炚 絖 誑 纊 恇 獷 磺 桄 茪 框 爌 誆 礦 撗 劻 眶 迋 矌 灮 垙 僙 抂 銧 㼅 圹 広 旷 犷 矿 纩 诓 诳 邝 鉱 俇 儣 匩 哐 姯 懬 懭 昿 洭 炗 炛 烡 砿 硄 穬 筺 絋 臦 臩 軖 軭 輄 逛 邼 鄺 鋛 鵟 黆 黋 㤮 㫕 㫛 㹰 㾠 㿠 䇊 䊯 䒰 䖱 䯑
괘 掛 卦 罫 挂 詿 罣 咼 喎 絓 呙 诖 㖞 叧 啩 枴 柺 褂
괴 怪 愧 壞 塊 槐 傀 乖 魁 拐 媿 坏 恠 瑰 蒯 闠 廥 襘 璝 壊 蕢 瓌 烠 凷 欳 膭 块 蒉 墤 巜 旝 晆 櫆
괴 怪 愧 壞 塊 槐 傀 乖 魁 拐 媿 坏 恠 瑰 蒯 闠 廥 襘 璝 壊 蕢 瓌 烠 凷 欳 膭 块 蒉 墤 巜 旝 晆 櫆 癐 箉 糩 蘾 謉 㔞 㕟 㘨 㙕 㚍 㧔 㻁 㾩 䂯 䂷 䃶 䈐 䈛 䓒 䓙 䙌 䯣 䯤
괵 馘 虢 㶁 蟈 幗 帼 掴 蝈 剨 啯 嘓 慖 摑 漍 聝 腘 膕 䂸 䬎
굉 宏 肱 轟 紘 閎 觥 黌 訇 浤 硡 鍠 鍧 汯 翃 翝 纮 轰 锽 闳 厷 咣 渹 竤 綋 耾 觵 軣 錓 鞃 㚚 䆖 䡌
교 校 交 敎 橋 巧 較 郊 矯 膠 絞 僑 嬌 攪 酵 驕 喬 轎 狡 皎 蛟 咬 嶠 翹 鮫 蕎 餃 徼 榷 嘐 嚙 磽 鉸
구 九 口 求 句 救 究 久 舊 具 苟 俱 區 丘 拘 球 構 懼 驅 狗 龜 邱 歐 鳩 購 鷗 玖 寇 仇 矩 溝 舅 衢
국 國 局 菊 鞠 鞫 麴 匊 跼 掬 麯 国 囯 踘 閰 囗 椈 鵴 锔 侷 囶 囻 圀 婅 巈 挶 梮 檋 毩 毱 泦 淗 焗
군 軍 君 郡 群 窘 裙 捃 皸 桾 羣 頵 涒 军 皲 鲪 侰 僒 宭 帬 攈 攟 珺 皹 莙 蜠 裠 輑 鍕 鮶 鵘 㖥 㪊
굴 屈 窟 掘 堀 崛 詘 矻 倔 淈 朏 胐 诎 崫 弡 煀 虳 袦 誳 趉 镼 鶌 㐇 㐝 㩿 㪂 㭾 㵠 㻕 䂗 䓛 䖦 䘿
굉 宏 肱 轟 紘 閎 觥 黌 訇 浤 硡 鍠 鍧 汯 翃 翝 纮 轰 锽 闳 厷 咣 渹 竤 綋 耾 觵 軣 錓 鞃 㚚 䆖 䡌 䡏 䧀 䩑 䵃
교 校 交 敎 橋 巧 較 郊 矯 膠 絞 僑 嬌 攪 酵 驕 喬 轎 狡 皎 蛟 咬 嶠 翹 鮫 蕎 餃 徼 榷 嘐 嚙 磽 鉸 姣 窖 蹻 佼 趫 撟 噭 憍 晈 嘄 暞 橇 骹 鵁 齩 教 皦 挍 轇 屩 嵺 鄥 恔 敫 珓 穚 茭 蟜 譑 儌 墽 漖 獟 礉 窌 簥 跤 蹺 鞽 頝 鷮 㝯 㶀 䮦 啮 乔 侨 娇 峤 挢 搅 桥 矫 硗 绞 翘 荞 跷 轿 较 邬 铰 鞒 饺 骄 鲛 䴔 丂 啋 嘂 嘦 嘺 噛 嬓 孂 屫 峧 撹 撽 敽 敿 曒 槗 滘 灚 烄 燆 犞 璬 硚 礄 繑 纐 臫 荍 虠
구 九 口 求 句 救 究 久 舊 具 苟 俱 區 丘 拘 球 構 懼 驅 狗 龜 邱 歐 鳩 購 鷗 玖 寇 仇 矩 溝 舅 衢 軀 鉤 廐 柩 灸 謳 垢 駒 嘔 臼 嶇 毆 枸 咎 耉 勾 毬 坵 晷 瞿 逑 絿 柾 銶 搆 扣 裘 詬 疚 遘 媾 屨 嫗 劬 捄 駈 甌 摳 窶 彀 颶 漚 韭 鸜 傴 榘 糗 蚯 覯 癯 韝 姤 匶 璆 釦 鷇 俅 佝 岣 昫 篝 胊 坸 冓 佉 傋 厹 叴 戵 痀 窛 蒟 韮 鬮 惧 旧 厩 乆 敺 廏 耇 沟 臞 緱 玽 雊 耈 呴 䪷 倶 賕 亀 冦 㡱 抅 氍
국 國 局 菊 鞠 鞫 麴 匊 跼 掬 麯 国 囯 踘 閰 囗 椈 鵴 锔 侷 囶 囻 圀 婅 巈 挶 梮 檋 毩 毱 泦 淗 焗 粷 蘜 諊 趜 躹 輂 鋦 陱 駶 驧 㘲 㥌 㩴 㹼 㽤 䆐 䋰 䎤 䏱 䕮 䗇 䜯 䡞 䪕 䱡
군 軍 君 郡 群 窘 裙 捃 皸 桾 羣 頵 涒 军 皲 鲪 侰 僒 宭 帬 攈 攟 珺 皹 莙 蜠 裠 輑 鍕 鮶 鵘 㖥 㪊 㴫 㿏 䝍 䭽
굴 屈 窟 掘 堀 崛 詘 矻 倔 淈 朏 胐 诎 崫 弡 煀 虳 袦 誳 趉 镼 鶌 㐇 㐝 㩿 㪂 㭾 㵠 㻕 䂗 䓛 䖦 䘿 䞷 䠇
굿 㖌
궁 弓 宮 窮 躬 穹 芎 躳 宫 穷 藭 匔 杛 竆 㑋 匑 宆 焪 熍 㴦 䓖 䛪 䠻 䤝
궉 鴌
권 權 卷 勸 券 拳 圈 眷 倦 捲 淃 惓 綣 睠 蜷 棬 勌 勧 劵 巻 踡 鬈 帣 䄅 劝 权 権 绻 锩 勬 啳 圏 埢
권 權 卷 勸 券 拳 圈 眷 倦 捲 淃 惓 綣 睠 蜷 棬 勌 勧 劵 巻 踡 鬈 帣 䄅 劝 权 権 绻 锩 勬 啳 圏 埢 奆 婘 孉 巏 慻 搼 桊 椦 牶 犈 箞 絭 菤 蔨 虇 蠸 裷 觠 錈 韏 飬 餋 鬳 齤 㒽 㟡 㢧 㩲 㪻 㬺 䄐 䅚 䊎 䌯 䑏 䖭 䟒 䠰
궐 厥 闕 蹶 蕨 獗 劂 撅 橛 蹷 橜 鱖 欮 灍 蟨 鷢 镢 阙 鳜 亅 噘 孒 孓 嶡 嶥 憠 氒 瘚 蟩 鐝 㙭 㵐 䙠
궤 軌 蹶 潰 机 詭 櫃 饋 几 跪 匱 樻 簋 餽 憒 劂 繢 闠 佹 氿 麂 撅 簣 䡄 匭 㧪 垝 觤 鐀 陒 馈 匦 匮
귀 貴 句 歸 鬼 龜 晷 巋 帰 䢜 宄 瞶 劌 撌 㱕 嘳 尯 腃 蘬 騩 䝿 刿 归 瞆 贵 椢 槶 櫷 皈 聭 㸵 䁛 䌆
규 規 奎 圭 糾 叫 揆 珪 閨 窺 葵 逵 硅 繆 畦 竅 赳 槻 虬 跬 睽 虯 闚 樛 巋 糺 楏 刲 嬀 潙 暌 頍 馗
궤 軌 蹶 潰 机 詭 櫃 饋 几 跪 匱 樻 簋 餽 憒 劂 繢 闠 佹 氿 麂 撅 簣 䡄 匭 㧪 垝 觤 鐀 陒 馈 匦 匮 愦 溃 篑 缋 诡 轨 阓 㱮 凧 厬 圚 姽 嬇 恑 攱 殨 祪 簂 籄 蔮 蛫 鑎 鞼 鱥 㔲 㔳 㙺 㞦 䃽 䅪 䍯 䕚 䠩 䣀 䣒 䤥
귀 貴 句 歸 鬼 龜 晷 巋 帰 䢜 宄 瞶 劌 撌 㱕 嘳 尯 腃 蘬 騩 䝿 刿 归 瞆 贵 椢 槶 櫷 皈 聭 㸵 䁛 䌆 䙡 䞈 䰎 䤥
규 規 奎 圭 糾 叫 揆 珪 閨 窺 葵 逵 硅 繆 畦 竅 赳 槻 虬 跬 睽 虯 闚 樛 巋 糺 楏 刲 嬀 潙 暌 頍 馗 邽 鮭 嫢 呌 䂓 戣 煃 摎 槼 恘 芤 頯 妫 沩 岿 窍 窥 纠 规 闺 乣 喹 媯 恷 摫 朻 椝 楑 湀 溈 犪 玌 瞡 窐 紤 聧 胿 芁 茥 藈 蝰 袿 訆 蹞 郌 鄈 鍨 鍷 頄 鬶 鬹 黊 鼼 㙓 㧃 㨒 䅅 䆻 䖯 䙵 䞨 䟸 䠑 䤆 䯓 䲅 䳏 䳫 䶯
균 均 菌 龜 鈞 筠 勻 畇 囷 麏 匀 稛 麕 袀 钧 呁 峮 抣 汮 碅 箘 箟 蚐 覠 銁 銞 麇 㚬 㟒 䇹 䐃
귤 橘 蹫 䤎 䰬
극 極 克 劇 隙 棘 戟 剋 亟 屐 郄 尅 殛 郤 㦸 茍 鋴 隟 㞃 蕀 㥛 剧 丮 勀 勊 娔 撠 橶 氪 裓 襋 㘌 㳳
근 近 根 勤 謹 斤 僅 筋 饉 瑾 槿 覲 懃 芹 菫 劤 跟 靳 卺 蘄 厪 觔 墐 巹 釿 廑 漌 堇 㘦 殣 慬 斳 䈽
극 極 克 劇 隙 棘 戟 剋 亟 屐 郄 尅 殛 郤 㦸 茍 鋴 隟 㞃 蕀 㥛 剧 丮 勀 勊 娔 撠 橶 氪 裓 襋 㘌 㳳 㻷 䓧 䧍 䩯 䪂
근 近 根 勤 謹 斤 僅 筋 饉 瑾 槿 覲 懃 芹 菫 劤 跟 靳 卺 蘄 厪 觔 墐 巹 釿 廑 漌 堇 㘦 殣 慬 斳 䈽 觐 谨 馑 伒 哏 嫤 懄 歏 瘽 荕 菦 蓳 螼 赾 馸 㝻 㞬 㢙 㧆 㨷 㮗 㹏 䈥 䌍 䒺 䤺
글 契 訖 㐎 㔕 䞘 䰴
금 金 今 禁 錦 禽 琴 襟 衾 擒 衿 鈐 芩 檎 昑 妗 噤 嶔 笒 唫 黅 紟 琹 顉 菳 赺 㯲 嵚 钅 锦 僸 凎 凚
금 金 今 禁 錦 禽 琴 襟 衾 擒 衿 鈐 芩 檎 昑 妗 噤 嶔 笒 唫 黅 紟 琹 顉 菳 赺 㯲 嵚 钅 锦 僸 凎 凚 噙 坅 庈 惍 扲 捦 搇 澿 珡 琻 砛 耹 肣 蚙 蠄 軡 釒 鈙 鍂 鐢 雂 靲 鳹 鵭 齽 㕋 㦗 㩒 㪁 㱈 㱽 㲐 㾣 䃢 䋮 䌝 䑤 䔷 䘳 䥅 䥆 䦦 䫴 䶖
급 給 急 及 級 汲 扱 伋 岌 皀 笈 礏 圾 芨 彶 级 给 喼 嬆 忣 礘 觙 鷑 﨤 㤂 㧀 㬤 㽺 䏜 䏠 䲯
긍 肯 兢 矜 亘 亙 殑 矝 堩 絚 緪 肎 刯 奟 掯 揯 搄 縆 肻 褃 錹 鹶 㥤 㮓 䱍 䱎 䱭 䱴
기 己 記 氣 技 起 期 基 其 旣 幾 豈 器 奇 機 旗 紀 棄 忌 企 欺 飢 祈 乞 騎 寄 畿 冀 箕 耆 岐 沂 驥
기 己 記 氣 技 起 期 基 其 旣 幾 豈 器 奇 機 旗 紀 棄 忌 企 欺 飢 祈 乞 騎 寄 畿 冀 箕 耆 岐 沂 驥 琦 麒 璣 琪 騏 淇 汽 譏 妓 羈 机 嗜 綺 杞 畸 朞 肌 棋 伎 崎 枳 圻 饑 祇 夔 祁 磯 祺 碁 錡 玘 璂 埼 几 覬 耭 錤 歧 弃 曁 旂 欹 頎 墍 棊 鬐 蟣 跂 剞 掎 蘄 攲 芰 猉 綦 忮 屺 愭 芪 炁 羇 覉 僛 庋 禨 肵 虁 暣 綥 蜝 鰭 隑 黖 芑 竒 覊 㡬 旡 惎 跽 鞿 怾 碕 踦 丌 徛 既 禥 蘷 衹 倛 庪 懻 暨 榿 㙨
긴 緊 紧 菣
길 吉 拮 佶 桔 姞 鴶 蛣 洁 趌 鲒 咭 狤 郆 銡 鞊 鮚 㐞 㐟 㣟 䓀 䟌
김 金
끗 㖝
끝 唜
끽 喫 噄
나 羅 那 奈 拿 裸 懶 儺 懦 螺 拏 邏 癩 蘿 喇 娜 挐 挪 糯 梛 糥 郍 砈 穤 傩 镎 乸 哪 嗱 懧 橠 稬 蒘
나 羅 那 奈 拿 裸 懶 儺 懦 螺 拏 邏 癩 蘿 喇 娜 挐 挪 糯 梛 糥 郍 砈 穤 傩 镎 乸 哪 嗱 懧 橠 稬 蒘 誽 鎿 㑚 㔮 㖠 㡅 㧱 㰙 䛔 柰 䏧
낙 落 樂 諾 洛 駱 烙 酪 珞 诺 掿
난 難 暖 卵 亂 蘭 欄 爛 鸞 煖 赧 煗 偄 餪 㬉 难 奻 戁 渜 㫱 㬮 䎡 愞
날 捺 捏 涅 陧 苶 䖓 䖧
남 南 男 濫 藍 嵐 楠 枏 湳 襤 喃 柟 娚 揇 㑲 侽 婻 暔 煵 畘 罱 腩 莮 萳 蝻 諵 遖 㓓 㣮 㭷 㵜 㽖 䈒
남 南 男 濫 藍 嵐 楠 枏 湳 襤 喃 柟 娚 揇 㑲 侽 婻 暔 煵 畘 罱 腩 莮 萳 蝻 諵 遖 㓓 㣮 㭷 㵜 㽖 䈒
납 納 拉 臘 蠟 衲 蒳 鈉 纳 钠 妠 笝 軜 靹 魶 㨥 䈫 䪏
낭 浪 娘 廊 狼 囊 朗 郎 曩 嚢 乪 儾 囔 擃 攮 欜 灢 饢 齉 㚂 㶞 䁸
내 內 來 乃 耐 奈 柰 迺 鼐 内 匂 嬭 奶 廼 㮏 褦 㮈 撆 耏 釢 孻 摨 氖 氝 渿 熋 疓 萘 螚 錼 㐻 㚷 㜨
내 內 來 乃 耐 奈 柰 迺 鼐 内 匂 嬭 奶 廼 㮏 褦 㮈 撆 耏 釢 孻 摨 氖 氝 渿 熋 疓 萘 螚 錼 㐻 㚷 㜨 㭆 㲡 㴎 㾍 䯮 䱞 䲎
냉 冷
냑 蹃 逽
냥 娘 孃
@@ -115,19 +115,19 @@
녈 涅 圼 篞 夨 㖏 㘿 䂼
념 念 恬 拈 捻 鮎 埝 焾 艌 踗 㑫 䧔 䬯
녑 鑷 喦 惗 䳖 帇 幉 敜 鈢 鉩 錜 㚔 㩶 㵊 䇣 䌰 䎎
녕 寧 獰 寗 佞 濘 嚀 儜 侫 寕 寜 甯 擰 聹 咛 拧 聍 嬣 寍 橣 檸 澝 矃 薴 鑏 鬡 鸋 㝕 㣷 㲌 㲰 㿦 䆨
녕 寧 獰 寗 佞 濘 嚀 儜 侫 寕 寜 甯 擰 聹 咛 拧 聍 嬣 寍 橣 檸 澝 矃 薴 鑏 鬡 鸋 㝕 㣷 㲌 㲰 㿦 䆨 䔭 䗿 䭢
녘 榒
녜 禰 祢 䄲
노 老 路 勞 怒 露 奴 努 盧 魯 爐 駑 蘆 鷺 虜 擄 弩 帑 櫓 瑙 孥 呶 猱 笯 臑 峱 砮 巙 怓 鑥 镥 驽 䜧
노 老 路 勞 怒 露 奴 努 盧 魯 爐 駑 蘆 鷺 虜 擄 弩 帑 櫓 瑙 孥 呶 猱 笯 臑 峱 砮 巙 怓 鑥 镥 驽 䜧 伖 伮 夒 巎 獿 粩 胬 蛯 詉 鴑 㚢 㞪 㺁 㺜 䛝 䜀
녹 綠 錄 祿 鹿 碌 菉 傉 搙
논 論 嫩 㯎
놀 㐐 㐗
놈 㖈
농 農 弄 籠 濃 膿 聾 壟 穠 儂 醲 噥 禯 癑 侬 农 哝 浓 秾 脓 㶶 嶩 檂 欁 燶 繷 蕽 襛 辳 鬞 齈 䙶 䢉
농 農 弄 籠 濃 膿 聾 壟 穠 儂 醲 噥 禯 癑 侬 农 哝 浓 秾 脓 㶶 嶩 檂 欁 燶 繷 蕽 襛 辳 鬞 齈 䙶 䢉
놔 雫
놜 豽 貀
뇌 雷 腦 惱 賂 牢 磊 餒 餧 脳 挼 憹 垴 恼 悩 脑 馁 匘 堖 娞 捼 碯 腇 鮾 鯘 㑎 㛴 㼏
뇨 溺 尿 撓 鬧 橈 嫋 裊 淖 鐃 嬲 嬈 磠 閙 褭 譊 䙚 袅 铙 闹 嫐 嬝 硇 硵 脲 㒟 㜵 㞙 㠡 㳮 䃩 䃵 䦊
뇨 溺 尿 撓 鬧 橈 嫋 裊 淖 鐃 嬲 嬈 磠 閙 褭 譊 䙚 袅 铙 闹 嫐 嬝 硇 硵 脲 㒟 㜵 㞙 㠡 㳮 䃩 䃵 䦊 䮍 䴃
누 樓 漏 淚 累 屢 陋 壘 縷 耨 啂 㝅 鎒 槈 檽 獳 羺 譨 譳 鐞 㜌 㝹 䅶 䨲
눈 嫩 媆 嫰 㜛 㡪 㶧
눌 訥 吶 肭 呐 讷 抐 㕯 䟜
@@ -137,288 +137,288 @@
늠 凜
능 能 陵 凌 綾 菱 稜 䏻 㴰 䘅
늦 莻
니 泥 尼 禰 怩 膩 呢 柅 祢 你 旎 埮 濔 臡 妳 檷 蚭 迡 腻 铌 伱 倷 坭 妮 屔 您 抳 狔 秜 胒 苨 跜 鈮
니 泥 尼 禰 怩 膩 呢 柅 祢 你 旎 埮 濔 臡 妳 檷 蚭 迡 腻 铌 伱 倷 坭 妮 屔 您 抳 狔 秜 胒 苨 跜 鈮 鉨 鑈 隬 馜 㞾 㦐 䕥 䘦 䛏 䝚
닉 溺 匿 搦 嫟 糑 嬺 愵 搻 氼 㥾 㲻
닐 昵 暱 䁥 痆 䘌 䵑 䵒
님 拰 䋻 䌾
닙 㘝 囡
다 多 茶 爹 嗲 夛 茤 觰 跢 鯺 䫂 䯬
단 單 短 端 但 丹 斷 段 團 壇 檀 旦 湍 鍛 緞 簞 蛋 亶 蓴 袒 彖 鄲 坍 担 胆 椴 慱 漙 靼 癉 蜑 耑 澶
달 達 撻 疸 獺 澾 怛 闥 韃 靼 妲 㺚 逹 橽 鐽 呾 燵 㼀 炟 狚 哒 挞 荙 跶 闼 鞑 噠 垯 墶 笪 繨 羍 薘
담 談 擔 淡 潭 膽 譚 痰 澹 曇 憺 湛 聃 覃 啖 蕁 坍 錟 担 胆 墰 禫 啗 毯 郯 噉 儋 薝 黮 壜 忐 罎 黵
답 答 踏 畓 遝 沓 荅 剳 蹋 涾 溚 褡 譶 匒 崉 榙 濌 畣 眔 褟 誻 蹹 躢 鎉 龖 龘 㙮 㜓 㟷 㧺 㭼 㯚 㳫
당 堂 當 唐 黨 糖 塘 撞 棠 螳 幢 戇 倘 儻 党 讜 搪 璫 瑭 瞠 鐺 襠 鏜 溏 餳 檔 礑 蟷 餹 螗 簹 戅 嵣
대 大 代 待 對 帶 隊 貸 臺 台 戴 垈 袋 擡 岱 黛 玳 錞 坮 懟 碓 儓 汏 隶 旲 鐓 㐲 䑓 帯 帒 憝 襨 濧
단 單 短 端 但 丹 斷 段 團 壇 檀 旦 湍 鍛 緞 簞 蛋 亶 蓴 袒 彖 鄲 坍 担 胆 椴 慱 漙 靼 癉 蜑 耑 澶 腶 断 单 摶 槫 泹 襌 㫁 瑖 猯 褖 鏄 偳 潬 煓 毈 鴠 㫜 团 坛 钽 単 団 抟 瘅 箪 簖 缎 郸 锻 刐 剬 勯 匰 唌 塅 媏 枬 柦 檲 湪 煅 玬 璮 疍 砃 碫 籪 糰 繵 葮 袇 褍 褝 襢 觛 貒 貚 踹 躖 鉭 鍴 駳 驙 鶨 鷒 鷤 鷻 㟨 㠆 㡺 㣋 㣶 㩛 㱭 䃪 䉡 䊜 䐷 䒟 䕊 䜝 䜥 䝎 䞡 䠪 䢷 䦔 䩥 䵎
달 達 撻 疸 獺 澾 怛 闥 韃 靼 妲 㺚 逹 橽 鐽 呾 燵 㼀 炟 狚 哒 挞 荙 跶 闼 鞑 噠 垯 墶 笪 繨 羍 薘 蟽 詚 躂 㒓 㣵 㳠 㿹 䃮 䵣
담 談 擔 淡 潭 膽 譚 痰 澹 曇 憺 湛 聃 覃 啖 蕁 坍 錟 担 胆 墰 禫 啗 毯 郯 噉 儋 薝 黮 壜 忐 罎 黵 惔 耼 聸 紞 倓 甔 萏 䃫 橝 窞 餤 㗖 埿 澸 罈 腅 菼 蟫 霮 顃 驔 髧 䨵 荨 谈 谭 锬 偡 啿 嘾 嚪 墵 妉 姏 婒 媅 帎 憛 曋 氮 瓭 癚 緂 舑 舕 藫 衴 贉 醈 醰 饏 馾 黕 㕪 㘱 㚮 㜤 㲜 㲭 㲷 㳩 㴂 㻼 㽎 㽑 䆦 䆱 䉞 䊤 䐺 䑙 䔜 䗊 䜖 䨢 䭛 埮 炎
답 答 踏 畓 遝 沓 荅 剳 蹋 涾 溚 褡 譶 匒 崉 榙 濌 畣 眔 褟 誻 蹹 躢 鎉 龖 龘 㙮 㜓 㟷 㧺 㭼 㯚 㳫 㾑 㿯 㿴 䂿 䈋 䌋 䍇 䍝 䎓 䐛 䓠 䜚 䠌 䪚 䳴 䵬 䶀 䶁
당 堂 當 唐 黨 糖 塘 撞 棠 螳 幢 戇 倘 儻 党 讜 搪 璫 瑭 瞠 鐺 襠 鏜 溏 餳 檔 礑 蟷 餹 螗 簹 戅 嵣 戃 䅯 儅 嘡 噇 摚 曭 澢 煻 膛 钂 闣 鞺 当 傥 垱 戆 挡 档 珰 筜 裆 谠 铛 镋 镗 饧 㓥 傏 凼 劏 啺 噹 坣 壋 憆 擋 攩 榶 欓 氹 漟 灙 爣 瓽 瞊 矘 磄 禟 篖 糃 糛 耥 膅 艡 蓎 譡 赯 蹚 躺 鄌 醣 鎕 鎲 闛 隚 饄 鶶 鼞 㑽 㙶 㜍 㜭 㭻 㲥 㼕 㼺 㽆 㿩 䃥 䉎 䌅 䕋 䚒 䣊 䣘 䣣 䧜 䭚
대 大 代 待 對 帶 隊 貸 臺 台 戴 垈 袋 擡 岱 黛 玳 錞 坮 懟 碓 儓 汏 隶 旲 鐓 㐲 䑓 帯 帒 憝 襨 濧 薱 襶 㙜 檯 祋 薹 蹛 㘆 㭖 䒫 䨴 对 対 带 怼 贷 轪 镦 队 亣 嬯 廗 憞 懛 柋 瀩 瀻 瑇 甙 簤 籉 艜 譈 譵 軑 軚 轛 鐜 鮘 黱 㒗 㓫 㙵 㚎 㛿 㝳 㞭 㠚 㣕 㦠 㬃 㬣 㯂 㳔 㷘 㸀 㻖 䃍 䈆 䙊 䚞 䚟 䯟 䲦 抬
댁 宅
덕 德 悳 徳 恴 惪 㤫 㥁
도 刀 道 度 圖 島 都 到 徒 逃 導 渡 盜 途 倒 桃 跳 陶 塗 稻 挑 禱 悼 燾 濤 蹈 屠 滔 睹 鍍 掉 堵 兜
독 讀 獨 督 毒 篤 瀆 禿 牘 犢 纛 黷 櫝 髑 独 讟 秃 凟 匵 殰 蝳 錖 韣 椟 渎 牍 犊 笃 読 读 黩 㱩 嬻
돈 敦 豚 頓 惇 沌 燉 褪 遯 旽 暾 墩 焞 潡 噸 弴 瓲 躉 鐓 炖 橔 獤 㬿 㹠 㻻 礅 伅 囤 墪 忳 扥 撉 趸
도 刀 道 度 圖 島 都 到 徒 逃 導 渡 盜 途 倒 桃 跳 陶 塗 稻 挑 禱 悼 燾 濤 蹈 屠 滔 睹 鍍 掉 堵 兜 搗 萄 賭 淘 覩 棹 韜 嶋 櫂 叨 菟 鼗 擣 饕 涂 絛 杲 闍 忉 弢 稌 鞱 搯 檮 慆 壔 洮 掏 酴 鞀 盗 謟 迯 禂 荼 翿 䆃 图 熖 舠 夲 幍 咷 釖 綯 裪 㨶 朷 縚 噵 鞉 䵚 唋 捈 暏 槝 瘏 衜 跿 鳭 䂽 䄻 捣 図 导 岛 梼 涛 焘 祷 稲 绹 赌 镀 阇 韬 鱽 凃 刂 匋 啕 喥 嘟 圗 嫍 屶 峹 嵞 嶀 嶌 嶹 帾 庩 廜 捯 旫
독 讀 獨 督 毒 篤 瀆 禿 牘 犢 纛 黷 櫝 髑 独 讟 秃 凟 匵 殰 蝳 錖 韣 椟 渎 牍 犊 笃 読 读 黩 㱩 嬻 涋 涜 瓄 痜 皾 碡 裻 豄 贕 鋵 鑟 韇 韥 騳 鵚 㒔 㞘 㸿 㾄 䄣 䈞 䐁 䓯 䛢 䢱 䫳 䮷
돈 敦 豚 頓 惇 沌 燉 褪 遯 旽 暾 墩 焞 潡 噸 弴 瓲 躉 鐓 炖 橔 獤 㬿 㹠 㻻 礅 伅 囤 墪 忳 扥 撉 趸 顿 饨 鲀 崸 庉 扽 撴 朜 犜 砘 豘 蹾 軘 逇 韕 飩 驐 魨 黗 㢯 㥫 㩐 㪟 䃦 䔻 䪃 䵊 䵍
돌 突 乭 咄 堗 宊 腯 湥 揬 柮 㐑 㟮 㻠 饳 嚉 怢 葖 迌 鈯 鍎 飿 鶟 鼵 㷝 䃐 䠈
돗 㘏
동 同 東 冬 動 洞 童 銅 凍 董 桐 棟 憧 疼 瞳 胴 仝 潼 錬 僮 蕫 彤 侗 峒 佟 蝀 涷 垌 朣 橦 艟 茼 哃
두 頭 讀 斗 豆 杜 兜 痘 竇 逗 荳 枓 斁 蠹 肚 陡 蚪 抖 脰 吋 紏 蠧 餖 㞳 㪷 兠 㺶 阧 﨣 䇺 䢏 䲧 头
동 同 東 冬 動 洞 童 銅 凍 董 桐 棟 憧 疼 瞳 胴 仝 潼 錬 僮 蕫 彤 侗 峒 佟 蝀 涷 垌 朣 橦 艟 茼 哃 苳 鮗 諌 働 埬 曈 㨂 穜 詷 膧 㠉 挏 烔 絧 餇 懂 湩 獞 罿 鮦 鼕 岽 东 冻 动 栋 胨 铜 鲖 鸫 倲 偅 勭 咚 墥 姛 娻 嬞 峂 峝 崠 崬 庝 戙 昸 晍 氃 氡 氭 浵 炵 燑 犝 狪 眮 砼 硐 秱 笗 箽 粡 腖 菄 蘔 蚒 衕 赨 迵 酮 鉖 鉵 霘 駧 鯟 鶇 㑈 㓊 㖦 㗢 㚵 㠽 㢥 㣠 㤏 㫡 㯵 㲇 㷲 㸗 㼧 㼯 㼿 䂈 䂢 䆚 䍶
두 頭 讀 斗 豆 杜 兜 痘 竇 逗 荳 枓 斁 蠹 肚 陡 蚪 抖 脰 吋 紏 蠧 餖 㞳 㪷 兠 㺶 阧 﨣 䇺 䢏 䲧 头 窦 钭 饾 亠 侸 剅 吺 唗 唞 梪 橷 殬 毭 浢 篼 荰 蔸 螙 郖 酘 鈄 鋀 閗 鬥 㓱 㛒 㟕 㢄 㨮 㰯 㿡 䄈 䇆 䕆 䕱 䚵 䬦 䱏 䵉
둑 㪲
둔 鈍 屯 遁 臀 芚 遯 窀 迍 吨 钝 坉 臋 踲 霕 㩔 㼊 䜳 䤜
둘 乧 㐈 㐙
둣 㖍
둥 㪳
득 得 淂 嘚 锝 棏 鍀 㝶 䙷 䙸
등 等 登 燈 騰 藤 鄧 嶝 謄 橙 滕 鐙 灯 磴 縢 螣 墱 凳 籐 䓁 蹬 䒭 簦 豋 驣 㽅 籘 膯 隥 㔁 腾 誊 邓
등 等 登 燈 騰 藤 鄧 嶝 謄 橙 滕 鐙 灯 磴 縢 螣 墱 凳 籐 䓁 蹬 䒭 簦 豋 驣 㽅 籘 膯 隥 㔁 腾 誊 邓 镫 僜 儯 噔 嬁 幐 戥 櫈 漛 璒 竳 艠 虅 覴 邆 霯 駦 鰧 鼟 㲪 䒅 䔲 䕨 䗳 䙞 䠬 䠮 䮴 䲍 䲢 䳾
똥 㖯 㖰
뜰 㐢
라 羅 裸 懶 螺 拏 邏 癩 蘿 喇 騾 囉 鑼 瘰 臝 砢 倮 曪 驘 覼 蠃 灕 覶 玀 躶 㞅 纙 㼈 䇔 罗 懒 椤 猡
라 羅 裸 懶 螺 拏 邏 癩 蘿 喇 騾 囉 鑼 瘰 臝 砢 倮 曪 驘 覼 蠃 灕 覶 玀 躶 㞅 纙 㼈 䇔 罗 懒 椤 猡 癞 箩 脶 萝 逻 锣 镙 骡 㑩 亽 儸 剆 啰 嚹 囖 摞 攞 攭 旯 欏 籮 罖 腡 蓏 覙 鎯 鏍 饠 鸁 㒩 㦬 㩡 㰁 㱻 㽋 㿚 䉓 䊨 䌱 䌴 䯁
락 落 樂 諾 絡 洛 駱 烙 酪 珞 犖 嗠 硌 濼 鉻 雒 泺 络 荦 铬 饹 骆 峈 笿 餎 鮥 鴼 㓢 㦡 䀩 䈷 䎊
란 丹 卵 亂 蘭 欄 爛 鸞 懶 瀾 欒 闌 鑾 攔 襴 嬾 幱 灓 乱 䦨 滦 灤 斕 圝 圞 灡 籣 兰 拦 斓 栏 栾 澜
란 丹 卵 亂 蘭 欄 爛 鸞 懶 瀾 欒 闌 鑾 攔 襴 嬾 幱 灓 乱 䦨 滦 灤 斕 圝 圞 灡 籣 兰 拦 斓 栏 栾 澜 烂 谰 銮 镧 阑 鸾 亪 囒 孄 孏 曫 欗 燗 爤 瓓 癵 糷 羉 膥 虊 襕 襽 譋 讕 躝 釠 鑭 钄 韊 鵉 㘓 㡩 㦨 㱍 㱫 㳕 䃹 䑌 䖂 䪍
랄 剌 辣 喇 埒 辢 捋 獭 垃 埓 揦 揧 攋 楋 瓎 瘌 蝲 頱 鬎 鯻 㸊 㻋 㻝 䀳 䏀 䓥 䓶 䱫 䶛
람 覽 濫 藍 籃 攬 嵐 纜 欖 擥 襤 婪 惏 覧 灠 壈 㲯 䰐 㩜 囕 㑣 岚 揽 榄 滥 漤 篮 缆 蓝 褴 览 䍀 儖
람 覽 濫 藍 籃 攬 嵐 纜 欖 擥 襤 婪 惏 覧 灠 壈 㲯 䰐 㩜 囕 㑣 岚 揽 榄 滥 漤 篮 缆 蓝 褴 览 䍀 儖 啉 嚂 懢 浨 灆 燣 燷 爁 爦 璼 礷 繿 葻 蘫 顲 㘕 㛦 㜮 㞩 㧛 㨫 㰓 㰖 䆾 䌫 䨬
랍 拉 臘 蠟 摺 鑞 臈 爉 䗶 镴 啦 搚 柆 磖 翋 菈 蝋 邋 鞡 㕇 㡴 䂰 䃳 䇱
랑 郞 浪 廊 狼 朗 琅 郎 螂 瀧 瑯 踉 閬 蜋 稂 榔 硠 莨 桹 烺 蓈 鋃 欴 艆 㫰 锒 阆 勆 啷 埌 塱 嫏 崀
래 來 萊 賚 崍 徠 来 淶 騋 顂 勑 徕 莱 䅘 猍 琜 倈 箂 棶 睞 錸 鵣 䋱 䚅 崃 俫 梾 涞 睐 铼 儽 唻 婡
랑 郞 浪 廊 狼 朗 琅 郎 螂 瀧 瑯 踉 閬 蜋 稂 榔 硠 莨 桹 烺 蓈 鋃 欴 艆 㫰 锒 阆 勆 啷 埌 塱 嫏 崀 朖 朤 樃 筤 蒗 蓢 誏 躴 郒 駺 㓪 㙟 㟍 㢃 㮾 㱢 㾿 䆡 䍚 䕞 䡙 䯖 䱶
래 來 萊 賚 崍 徠 来 淶 騋 顂 勑 徕 莱 䅘 猍 琜 倈 箂 棶 睞 錸 鵣 䋱 䚅 崃 俫 梾 涞 睐 铼 儽 唻 婡 庲 逨 郲 鯠 鶆 麳 㚓 㴺 㾢 䂾 䧒
랭 冷 唥
략 略 掠 畧 攊 圙 撂 洜 㑼 㗉 㨼 䂮 䌎 䛚
량 兩 場 良 量 凉 梁 糧 諒 亮 樑 輛 粱 倆 粮 涼 魎 踉 椋 悢 喨 两 両 惊 㾗 湸 緉 裲 輌 輬 俍 俩 唡
려 旅 慮 麗 勵 呂 廬 礪 驪 閭 戾 黎 侶 濾 犁 驢 藜 儷 蠣 櫚 厲 臚 癘 膂 蠡 糲 唳 梠 鑢 儢 邌 吕 沴
력 力 歷 曆 瀝 礫 轢 靂 櫟 櫪 鬲 酈 攊 癧 轣 歴 暦 䔉 厯 皪 屴 朸 檪 磿 礰 躒 历 呖 坜 枥 栎 沥 疬
련 連 練 鍊 憐 戀 聯 蓮 漣 輦 煉 怜 璉 攣 錬 臠 孌 鰱 楝 鏈 孿 湅 鰊 恋 堜 摙 䏈 縺 聨 㜻 链 娈 挛
렬 列 烈 劣 裂 冽 洌 捩 颲 挒 茢 迾 鋢 鴷 㭩 锊 䴕 劽 咧 哷 姴 挘 栵 浖 烮 煭 爄 睙 脟 蛚 蛶 趔 迣
렴 廉 濂 斂 簾 殮 歛 籟 瀲 奩 獫 磏 帘 㢘 嬚 鐮 搛 溓 薕 蘝 蠊 奁 敛 潋 莶 蔹 镰 亷 劆 匲 匳 澰 濓
량 兩 場 良 量 凉 梁 糧 諒 亮 樑 輛 粱 倆 粮 涼 魎 踉 椋 悢 喨 两 両 惊 㾗 湸 緉 裲 輌 輬 俍 俩 唡 谅 辆 辌 魉 哴 啢 墚 掚 斏 晾 煷 簗 綡 羪 脼 蜽 㒳 㔀 㔝 㝗 㹁 䀶 䁁 䓣 䝶 䠃 䣼 䩫 䭪 駺
려 旅 慮 麗 勵 呂 廬 礪 驪 閭 戾 黎 侶 濾 犁 驢 藜 儷 蠣 櫚 厲 臚 癘 膂 蠡 糲 唳 梠 鑢 儢 邌 吕 沴 荔 丽 侣 盭 瓈 欐 蟸 黧 棙 穭 廲 藘 攦 櫖 爈 茘 蜧 㑦 䕻 励 俪 厉 庐 榈 滤 疠 砺 稆 粝 胪 蛎 铝 闾 驴 骊 鲡 勴 囇 婯 巁 悷 挔 捛 曞 曥 栛 沵 焒 珕 矋 祣 禲 穞 絽 綟 膐 蛠 蠇 郘 鋁 鏫 鑗 馿 騹 鱱 鱺 鵹 㒧 㓯 㛎 㠟 㠠 㭚 㰀 㱺 㴝 㾐 㾔 㿛 䉫 䊪 䓞 䔣 䔧 䕡 䗍 䘈 䣓 䥨 䥿 䮉 䰕 䴡
력 力 歷 曆 瀝 礫 轢 靂 櫟 櫪 鬲 酈 攊 癧 轣 歴 暦 䔉 厯 皪 屴 朸 檪 磿 礰 躒 历 呖 坜 枥 栎 沥 疬 砾 苈 跞 轹 郦 镉 雳 儮 厤 叻 嚦 壢 擽 爏 瓅 瓑 砳 秝 蒚 藶 蚸 蝷 觻 讈 赲 鎘 靋 韷 鱳 鳨 㔏 㠣 㧰 㬏 㯤 㱹 㷴 㺡 㻺 㽁 㿨 䃯 䍥 䍽 䟏 䟐 䤙 䥶 䮥 䰛 䰜
련 連 練 鍊 憐 戀 聯 蓮 漣 輦 煉 怜 璉 攣 錬 臠 孌 鰱 楝 鏈 孿 湅 鰊 恋 堜 摙 䏈 縺 聨 㜻 链 娈 挛 撵 梿 涟 炼 琏 练 联 脔 莲 裢 辇 连 鲢 僆 嗹 噒 奱 媡 嫾 徚 慩 攆 槤 櫣 瑓 癴 纞 翴 聫 聮 膦 萰 褳 謰 蹥 鄻 㜃 㜕 㝈 㟀 㥕 㦁 㪝 㰈 㶌 㺦 㼑 䃛 䜌
렬 列 烈 劣 裂 冽 洌 捩 颲 挒 茢 迾 鋢 鴷 㭩 锊 䴕 劽 咧 哷 姴 挘 栵 浖 烮 煭 爄 睙 脟 蛚 蛶 趔 迣 鋝 鮤 㤠 㬯 㭞 㸹 㽟 䅀 䟹 䮋
렴 廉 濂 斂 簾 殮 歛 籟 瀲 奩 獫 磏 帘 㢘 嬚 鐮 搛 溓 薕 蘝 蠊 奁 敛 潋 莶 蔹 镰 亷 劆 匲 匳 澰 濓 熑 燫 稴 籢 籨 羷 臁 薟 蘞 螊 覝 鬑 㝺 㡘 㪘 㯬 㱨 㶑 㼓 䁠 䆂 䌞 䙺 䥥 䭑
렵 獵 鬣 躐 儠 擸 猟 巤 犣 聗 鱲 㯿 㲱 㼃 㼲 䁽 䉭 䜲 䝓 䪉
령 令 領 寧 靈 嶺 零 鈴 玲 囹 齡 逞 聆 怜 伶 翎 羚 岺 笭 另 岭 泠 苓 鴒 蛉 欞 呤 昤 姈 秢 軨 鹷 灵
령 令 領 寧 靈 嶺 零 鈴 玲 囹 齡 逞 聆 怜 伶 翎 羚 岺 笭 另 岭 泠 苓 鴒 蛉 欞 呤 昤 姈 秢 軨 鹷 灵 坽 㱓 櫺 醽 霛 瓴 狑 舲 彾 竛 跉 霝 燯 詅 酃 棂 铃 霊 领 鸰 齢 龄 刢 孁 拎 朎 柃 澪 炩 爧 皊 砱 紷 蕶 蘦 衑 袊 閝 阾 霗 駖 魿 麢 龗 㚑 㡵 㦭 㩕 㪮 㬡 㯪 㲆 㸳 㻏 㾉 䄥 䉁 䉖 䉹 䌢 䍅 䕘 䖅 䙥 䠲 䡼 䡿 䨩 䯍 䰱 䴇 䴒 䴫
례 禮 例 豊 隷 醴 澧 隸 鱧 礼 㘑 鳢 櫔 欚 濿 犡 瓥 砅 㡂 㤡 㽝 䅄 䵩
로 老 路 勞 怒 露 盧 魯 爐 蘆 鷺 虜 擄 撈 輅 鹵 潞 芦 櫓 瀘 潦 鑪 鱸 壚 騾 艪 顱 艫 鸕 轤 玈 窂 滷
록 綠 錄 祿 鹿 麓 碌 菉 淥 漉 彔 騄 簏 轆 圥 录 盝 籙 禄 醁 緑 廘 録 㖨 睩 蔍 逯 绿 渌 箓 辘 塶 娽
로 老 路 勞 怒 露 盧 魯 爐 蘆 鷺 虜 擄 撈 輅 鹵 潞 芦 櫓 瀘 潦 鑪 鱸 壚 騾 艪 顱 艫 鸕 轤 玈 窂 滷 癆 磠 鐪 髗 劳 㳣 澇 鏴 璐 獹 罏 攎 簵 䳓 咾 嫪 櫨 珯 矑 簬 艣 髝 卤 橹 炉 労 卢 唠 噜 垆 崂 捞 掳 撸 栌 氇 泸 涝 痨 耢 舻 虏 轳 辂 铑 铹 颅 鲁 鲈 鸬 鹭 僗 哰 嘮 嚕 嚧 塷 嶗 恅 憥 憦 擼 朥 栳 樐 橯 氌 浶 澛 瀂 狫 瓐 硓 磱 簩 籚 纑 耂 耮 蓾 蕗 虂 蟧 蠦 軂 銠 鏀 鐒 魲 鮱 黸 㔧 㔪 㗦 㟉 㢚
록 綠 錄 祿 鹿 麓 碌 菉 淥 漉 彔 騄 簏 轆 圥 录 盝 籙 禄 醁 緑 廘 録 㖨 睩 蔍 逯 绿 渌 箓 辘 塶 娽 摝 梺 椂 樚 氯 熝 琭 甪 簶 粶 膔 螰 觮 趢 蹗 錴 騼 鵦 龣 㜙 㟤 㦇 㪖 㫽 㯟 㲶 㼾 䃙 䌒 䍡 䎑 䎼 䐂 䘵 䚄 䟿 䩮 䱚 䴪
론 論 掄 惀 碖 菕 论 婨 溣 㤻
롱 弄 籠 龐 聾 壟 瓏 朧 瀧 隴 礱 曨 蘢 儱 攏 龎 櫳 壠 巃 哢 礲 䏊 嚨 竉 㺯 胧 厐 咙 垄 垅 拢 昽 栊
뢰 雷 賴 賂 儡 牢 磊 賚 瀨 罍 耒 籟 酹 誄 礧 纇 蕾 顂 攂 礌 頼 瀬 㵢 䨓 藾 鐳 傫 櫑 瓃 礨 䒹 濑 籁
료 料 了 僚 遼 療 聊 寮 燎 瞭 廖 寥 鬧 蓼 潦 醪 繚 膋 撩 暸 獠 鐐 嘹 嫽 飂 飉 憀 憭 鷯 䝤 璙 炓 㵳
롱 弄 籠 龐 聾 壟 瓏 朧 瀧 隴 礱 曨 蘢 儱 攏 龎 櫳 壠 巃 哢 礲 䏊 嚨 竉 㺯 胧 厐 咙 垄 垅 拢 昽 栊 泷 滝 珑 砻 笼 聋 茏 陇 巄 挊 挵 梇 槞 硦 篭 蠪 裃 襱 豅 鏧 鑨 靇 鸗 龓 㑝 㙙 㛞 㟖 㢅 㰍 㳥 䆍 䡁 䪊
뢰 雷 賴 賂 儡 牢 磊 賚 瀨 罍 耒 籟 酹 誄 礧 纇 蕾 顂 攂 礌 頼 瀬 㵢 䨓 藾 鐳 傫 櫑 瓃 礨 䒹 濑 籁 诔 赂 赉 赖 镭 壨 擂 檑 櫴 洡 珱 畾 癗 磥 筙 蠝 襰 讄 轠 銇 錑 鑘 鑸 靁 頛 頪 颣 鱩 㑍 㒦 㔣 㠥 㲕 㵽 㼍 㿔 䄤 䉪 䍣 䐯 䛶 䠭 䢮 䣂 䮑 䲚
료 料 了 僚 遼 療 聊 寮 燎 瞭 廖 寥 鬧 蓼 潦 醪 繚 膋 撩 暸 獠 鐐 嘹 嫽 飂 飉 憀 憭 鷯 䝤 璙 炓 㵳 尞 橑 爎 轑 㝋 疗 缭 辽 镣 鹩 佬 叾 尥 尦 屪 嶚 嶛 廫 敹 曢 熮 爒 爳 窲 窷 竂 簝 翏 膫 藔 蟉 蟟 豂 賿 蹘 蹽 鄝 镽 顟 髎 㙩 㞠 㟹 㡻 㶫 䄦 䉼 䎆 䑠 䒿 䕩 䜍 䢧 䨅 䩍 䭜
룡 龍 龒 矓 眬 龙 徿 爖 竜 蠬 贚 躘 驡 㡣
루 樓 漏 淚 累 屢 陋 壘 縷 婁 鏤 褸 蔞 瘻 螻 僂 髏 嶁 慺 嘍 耬 楼 屡 泪 蝼 塁 摟 塿 䮫 嫘 屚 廔 䫫
류 流 留 柳 類 劉 謬 溜 琉 瘤 繆 瑠 旒 榴 硫 瀏 纍 鶹 縲 瑬 遛 橊 飂 霤 騮 鏐 旈 䄂 鎏 罶 飀 藟 鎦
루 樓 漏 淚 累 屢 陋 壘 縷 婁 鏤 褸 蔞 瘻 螻 僂 髏 嶁 慺 嘍 耬 楼 屡 泪 蝼 塁 摟 塿 䮫 嫘 屚 廔 䫫 瘘 偻 喽 垒 娄 嵝 搂 篓 缕 耧 蒌 褛 镂 髅 䁖 厽 櫐 氀 涙 溇 漊 熡 甊 瘺 癳 瞜 簍 膢 艛 謱 貗 軁 遱 鞻 鷜 鼺 㒍 㔷 㙼 㜢 㟺 㡞 㥪 㪹 㲎 㴃 㶟 㹎 㺏 㻲 䄛 䅹 䉂 䝏 䣚 䱾 䴎
류 流 留 柳 類 劉 謬 溜 琉 瘤 繆 瑠 旒 榴 硫 瀏 纍 鶹 縲 瑬 遛 橊 飂 霤 騮 鏐 旈 䄂 鎏 罶 飀 藟 鎦 飅 絫 羀 蘽 虆 蹓 駠 駵 䖻 刘 浏 类 绺 缧 蔂 谬 铆 锍 镏 镠 飗 馏 骝 鹨 嚠 塯 媹 嬼 嵧 廇 懰 栁 桺 樏 橮 欙 漻 澑 灅 熘 珋 璢 畄 畱 疁 癅 磂 禷 綹 纝 艈 蒥 蓅 蕌 藰 蘱 蘲 裗 鉚 鋶 鐂 雡 飹 餾 驑 鬸 鰡 鷚 鸓 麍 㐬 㙧 㧕 㳅 㶯 㽌 㽞 㾋 䉧 䗜 䚧 䝀 䣦 䬟 䰘 䱖 䶉
륙 六 陸 戮 勠 僇 稑 穋 陆 剹 勎 坴 淕 磟 踛 鯥 鵱 㓐 㛬 䡜
륜 倫 輪 崙 綸 淪 侖 掄 錀 踚 崘 圇 棆 仑 伦 囵 抡 沦 纶 轮 嗧 埨 磮 稐 耣 腀 蜦 陯 鯩 㓆 㖮 㷍 䈁
률 律 率 栗 慄 嵂 溧 篥 㟳 硉 瑮 膟 㮚 峍 侓 傈 凓 垏 塛 寽 徍 搮 箻 繂 葎 鷅 麜 㖀 㗚 㪐 䔁 䔞 䡃
륜 倫 輪 崙 綸 淪 侖 掄 錀 踚 崘 圇 棆 仑 伦 囵 抡 沦 纶 轮 嗧 埨 磮 稐 耣 腀 蜦 陯 鯩 㓆 㖮 㷍 䈁
률 律 率 栗 慄 嵂 溧 篥 㟳 硉 瑮 膟 㮚 峍 侓 傈 凓 垏 塛 寽 徍 搮 箻 繂 葎 鷅 麜 㖀 㗚 㪐 䔁 䔞 䡃
륭 隆 瀜 癃 窿 烿 嶐 蕯 湰 霳 㚅 㝫 㦕 䃧 䥢
륵 勒 肋 泐 玏 仂 扐 鳓 嘞 忇 椚 氻 竻 簕 艻 阞 鰳
를 㔹
름 凜 廩 凛 澟 檩 廪 懍 懔 檁 癛 癝 菻 㐭 㨆 䕲
릉 陵 楞 凌 綾 菱 稜 倰 蔆 棱 崚 淩 夌 庱 掕 睖 鯪 绫 鲮 堎 婈 愣 琌 碐 祾 蓤 薐 裬 踜 輘 錂 㖫 㘄
리 里 利 理 異 李 吏 離 履 裏 梨 驪 罹 裡 籬 厘 俚 痢 悧 羸 釐 狸 犁 璃 鯉 唎 莉 浬 漓 莅 离 螭 俐
린 鄰 隣 麟 鱗 吝 躪 燐 璘 藺 潾 獜 磷 粼 嶙 驎 轔 繗 悋 躙 鏻 粦 遴 恡 瞵 䗲 疄 撛 僯 厸 轥 蔺 躏
릉 陵 楞 凌 綾 菱 稜 倰 蔆 棱 崚 淩 夌 庱 掕 睖 鯪 绫 鲮 堎 婈 愣 琌 碐 祾 蓤 薐 裬 踜 輘 錂 㖫 㘄 㥄 㱥 䈊 䉄 䔖 䧙 䬋 䮚
리 里 利 理 異 李 吏 離 履 裏 梨 驪 罹 裡 籬 厘 俚 痢 悧 羸 釐 狸 犁 璃 鯉 唎 莉 浬 漓 莅 离 螭 俐 嫠 魑 貍 邐 涖 哩 蜊 黐 剺 詈 纚 鸝 摛 醨 蒞 蘺 褵 縭 盠 劙 峛 彲 悝 樆 犂 蔾 俬 娌 棃 氂 䍦 䙰 篱 缡 蓠 逦 锂 鲤 鹂 兣 刕 剓 喱 嚟 囄 娳 孋 孷 峢 峲 彨 悡 攡 梩 梸 浰 犛 猁 琍 瓼 甅 睝 瞝 矖 穲 竰 筣 粚 粴 糎 繌 脷 艃 荲 菞 蟍 蠫 謧 鋫 鋰 錅 鬁 鯏 鯬 麶 㒿 㛤 㤦 㥎 㦒 㰚 㲠 㷰 㸚 㹈 㻳
린 鄰 隣 麟 鱗 吝 躪 燐 璘 藺 潾 獜 磷 粼 嶙 驎 轔 繗 悋 躙 鏻 粦 遴 恡 瞵 䗲 疄 撛 僯 厸 轥 蔺 躏 辚 邻 鳞 亃 壣 斴 暽 橉 焛 甐 翷 蹸 閵 麐 㔂 㖁 㷠 䉮 䚏 䚬 䢯 䫰 䮼 䱯
림 林 臨 淋 琳 霖 痳 惏 冧 崊 晽 棽 瀮 瀶 碄 箖 醂 㝝 㣩 㵉 䫐 玪
립 立 笠 粒 砬 岦 雴 苙 鴗 湁 鉝 㕸 䇉 䢂 䲞 䶘
마 馬 麻 磨 摩 魔 痲 瑪 碼 麽 麿 劘 媽 螞 蟇 麼 蟆 亇 嗎 禡 䔍 嘛 鎷 㦄 吗 唛 妈 嬷 杩 犸 玛 码 祃
마 馬 麻 磨 摩 魔 痲 瑪 碼 麽 麿 劘 媽 螞 蟇 麼 蟆 亇 嗎 禡 䔍 嘛 鎷 㦄 吗 唛 妈 嬷 杩 犸 玛 码 祃 蚂 马 傌 吀 哶 嘜 嚒 嚤 嚰 嫲 嬤 尛 懡 戂 擵 榪 溤 犘 獁 礳 耱 蔴 藦 蘑 饝 髍 鬕 鰢 鷌 㐃 㐷 㾺 䃺 䗫 䘞 䜆 䣕 䣖 䧞 䩋 䭩 䯢 䲈 䳸
막 莫 幕 漠 膜 寞 邈 藐 鏌 瞙 瘼 幙 暯 嗼 鄚 饃 馍 镆 劰 塻 瞐 縸 㠳 㦝 㱳 㷬 䒬 䮬
만 萬 滿 晩 慢 漫 蠻 灣 娩 挽 蔓 瞞 輓 彎 饅 鰻 卍 万 曼 巒 謾 幔 懣 嫚 蹣 墁 悗 縵 鏝 鬘 鏋 湾 晚
말 末 靺 抹 襪 沫 唜 茉 帕 秣 昩 袜 妺 韈 眜 枺 帓 怽 瀎 皌 睰 砞 礣 粖 袹 閁 韤 魩 㑻 㨸 㭐 㶬 䍪
만 萬 滿 晩 慢 漫 蠻 灣 娩 挽 蔓 瞞 輓 彎 饅 鰻 卍 万 曼 巒 謾 幔 懣 嫚 蹣 墁 悗 縵 鏝 鬘 鏋 湾 晚 満 熳 㬅 鬛 澫 脕 僈 摱 睌 矕 蟎 鋔 㡢 䐽 蛮 峦 弯 满 瞒 缦 螨 谩 蹒 镘 颟 馒 鳗 卐 塆 壪 屘 慲 梚 槾 樠 潫 澷 濷 獌 蔄 蘰 蟃 襔 贎 鄤 鞔 顢 鬗 㒼 㗄 㗈 㙢 㵘 㸘 㻴 㿸 䅋 䅼 䊡 䒥 䕕 䘎 䜱 䝡 䝢 䡬 䤍 䯶 䰋
말 末 靺 抹 襪 沫 唜 茉 帕 秣 昩 袜 妺 韈 眜 枺 帓 怽 瀎 皌 睰 砞 礣 粖 袹 閁 韤 魩 㑻 㨸 㭐 㶬 䍪 䏞 䬴 䯦 䱅 䴲
맘 鋄 錽 鎫 㛧 䥑
망 亡 望 忘 忙 妄 罔 茫 網 芒 莽 邙 輞 惘 魍 莾 蟒 漭 汒 网 亾 鋩 蠎 㒺 㤀 兦 㕵 䋄 恾 朢 杗 蘉 誷
매 賣 買 每 妹 梅 媒 埋 枚 魅 昧 寐 罵 邁 煤 呆 楳 眛 莓 玫 沬 苺 酶 霉 浼 霾 䨪 禖 毎 抺 痗 佅 凂
망 亡 望 忘 忙 妄 罔 茫 網 芒 莽 邙 輞 惘 魍 莾 蟒 漭 汒 网 亾 鋩 蠎 㒺 㤀 兦 㕵 䋄 恾 朢 杗 蘉 誷 蝄 釯 㟿 㬒 䰶 纲 辋 铓 吂 壾 奀 娏 杧 棢 焹 牤 琞 痝 盳 硥 硭 笀 罒 罓 茻 莣 菵 蛧 㟌 㟐 䁳 䅒 䋞 䍏 䒎 䒽 䓼 䥈 䰣 䱩 䵨
매 賣 買 每 妹 梅 媒 埋 枚 魅 昧 寐 罵 邁 煤 呆 楳 眛 莓 玫 沬 苺 酶 霉 浼 霾 䨪 禖 毎 抺 痗 佅 凂 勱 湈 脄 韎 駡 鬽 骂 买 劢 卖 売 荬 迈 呅 嘪 坆 塺 挴 槑 珻 脢 腜 蕒 薶 跊 鋂 鷶 黣 㙁 㜥 㦟 㭑 㵋 㺳 䀛 䀜 䁲 䆀 䊈 䍙 䔦 䚑 䜕 䜸 䤂 䰨 䰪 䵢
맥 麥 脈 貊 陌 驀 脉 貃 貘 霡 貉 霢 眽 覛 麦 蓦 帞 眿 絈 蛨 衇 㵹 㹮 䘑 䨫 䮮 䳮
맹 孟 盟 猛 盲 萌 氓 黽 甍 甿 虻 儚 蝱 艋 蕄 鄳 锰 勐 掹 擝 橗 瓾 莔 蜢 錳 鯭 鼆 䁅 䇇 䈍 䉚 䓝 䖟
맹 孟 盟 猛 盲 萌 氓 黽 甍 甿 虻 儚 蝱 艋 蕄 鄳 锰 勐 掹 擝 橗 瓾 莔 蜢 錳 鯭 鼆 䁅 䇇 䈍 䉚 䓝 䖟 䗈 䥰
먀 乜
며 旀 厼 㢱
멱 覓 汨 蓂 冪 糸 幎 羃 覔 幂 纟 觅 冖 塓 幦 淿 漞 濗 簚 糹 鼏 㯒 䈿 䌐 䖑 䣾 䮭
면 面 免 勉 眠 綿 冕 沔 娩 緬 眄 麵 棉 俛 緜 麪 糆 湎 靣 丏 偭 㴐 勔 愐 櫋 莬 葂 麫 绵 缅 腼 丆 喕
면 面 免 勉 眠 綿 冕 沔 娩 緬 眄 麵 棉 俛 緜 麪 糆 湎 靣 丏 偭 㴐 勔 愐 櫋 莬 葂 麫 绵 缅 腼 丆 喕 婂 媔 嬵 宀 檰 汅 矈 矊 矏 臱 芇 蝒 鮸 麺 㒙 㛯 㝃 㝰 㨠 㨺 㮌 㰃 䀎 䃇 䛉 䛲 䤄 䫵 䰓
멸 滅 蔑 衊 篾 㓕 蠛 搣 櫗 灭 孭 幭 懱 揻 薎 鑖 鱴 鴓 㒝 㩢 䁾 䈼 䌩 䘊 䡸 䩏
명 明 命 名 鳴 銘 冥 溟 暝 皿 螟 酩 瞑 蓂 茗 椧 黽 洺 㝠 眀 凕 㫥 詺 䏃 铭 鸣 佲 姳 嫇 慏 掵 朙 榠
명 明 命 名 鳴 銘 冥 溟 暝 皿 螟 酩 瞑 蓂 茗 椧 黽 洺 㝠 眀 凕 㫥 詺 䏃 铭 鸣 佲 姳 嫇 慏 掵 朙 榠 焽 熐 猽 眳 笽 覭 鄍 鵈 㓁 㗮 㟰 䄙 䆩 䊅 䒌 䫤 䳟
몌 袂
모 母 毛 暮 慕 募 模 某 謀 冒 侮 貌 謨 牟 帽 茅 矛 耗 摸 牡 眸 摹 瑁 芼 姆 糢 耄 侔 髦 旄 媢 蟊 嫫
모 母 毛 暮 慕 募 模 某 謀 冒 侮 貌 謨 牟 帽 茅 矛 耗 摸 牡 眸 摹 瑁 芼 姆 糢 耄 侔 髦 旄 媢 蟊 嫫 眊 皃 姥 蝥 粍 鉾 恈 冐 麰 㡌 謩 氁 秏 䫉 橅 䖥 牦 谋 谟 兞 冃 哞 娒 峔 慔 枆 栂 毟 毪 毮 毷 洠 獏 玥 瓱 砪 竓 罞 艒 萺 蓩 蛑 蝐 蟱 覒 軞 酕 魹 鴾 鶜 㑄 㒵 㒻 㖼 㘪 㛌 㝟 㟂 㧇 㧌 㪞 㴘 㹸 㺺 㿞 䋃 䗋 䡚 䭷
목 目 木 牧 睦 穆 沐 鶩 苜 凩 楘 莯 霂 䳱 㜈 毣 炑 钼 鹜 坶 杢 狇 蚞 鉬 雮 㣎 㾇 䀲 䊾 䑵 䜼 䲷
몰 沒 歿 没 殁 圽 朰 歽 歾 莈
몸 鯍
몽 夢 蒙 朦 矇 濛 曚 瞢 幪 雺 懞 艨 儚 溕 鸏 梦 懵 冡 懜 䑃 蠓 㠓 饛 矒 霿 䤓 䝉 鹲 夣 檬 氋 獴 甮
묘 卯 妙 廟 墓 秒 苗 猫 昴 描 畝 渺 杳 竗 錨 眇 藐 淼 貓 庙 緲 畒 玅 茆 篎 缈 锚 鹋 冇 喵 媌 嫹 峁
몽 夢 蒙 朦 矇 濛 曚 瞢 幪 雺 懞 艨 儚 溕 鸏 梦 懵 冡 懜 䑃 蠓 㠓 饛 矒 霿 䤓 䝉 鹲 夣 檬 氋 獴 甮 礞 鄸 霥 靀 㒱 㙹 㜴 㝱 㩚 㴳 䀄 䏵 䑅 䒐 䙦 䙩 䟥 䠢 䥂 䰒 䲛 䴌 䴿 䵆
묘 卯 妙 廟 墓 秒 苗 猫 昴 描 畝 渺 杳 竗 錨 眇 藐 淼 貓 庙 緲 畒 玅 茆 篎 缈 锚 鹋 冇 喵 媌 嫹 峁 庿 戼 泖 渵 畆 瞄 笷 緢 覅 鱙 鶓 㑤 㚹 䁧 䅦 䏚 䖢
묠 乮
무 無 武 務 戊 茂 舞 貿 霧 誣 巫 毋 撫 畝 蕪 憮 拇 无 懋 繆 鵡 珷 楙 廡 騖 膴 儛 嘸 抚 瞀 袤 鍪 婺
무 無 武 務 戊 茂 舞 貿 霧 誣 巫 毋 撫 畝 蕪 憮 拇 无 懋 繆 鵡 珷 楙 廡 騖 膴 儛 嘸 抚 瞀 袤 鍪 婺 嫵 碔 幠 甒 倵 堥 瞴 莁 鉧 㒇 亩 务 呒 妩 庑 怃 缪 芜 诬 贸 雾 骛 鹀 鹉 仫 劺 呣 墲 奦 娬 嵍 愗 敄 暓 柕 橆 潕 熃 牳 璑 畞 畮 胟 袰 誈 譕 踇 躌 鄮 錻 霚 鞪 髳 鵐 鷡 㡔 㣳 㭌 㮊 㮘 㱐 㳇 㵲 㶃 㷻 㽗 䀤 䉑 䋷 䌗 䍢 䏬 䒉 䓮 䟼 䥐 䥻 䨁 䱕 䳇 橅
묵 墨 黙 默 嘿 纆 嚜 爅 嫼 濹 癦 蟔 㩏 㫯 㷵 䁼 䁿 䘃
문 門 問 文 聞 紊 汶 紋 蚊 吻 刎 們 雯 捫 懣 抆 忞 悗 炆 匁 忟 璊 闻 问 门 懑 㦖 脗 暪 絻 蚉 閿 馼
문 門 問 文 聞 紊 汶 紋 蚊 吻 刎 們 雯 捫 懣 抆 忞 悗 炆 匁 忟 璊 闻 问 门 懑 㦖 脗 暪 絻 蚉 閿 馼 们 扪 纹 钔 阌 伆 勽 呡 妏 彣 渂 珳 璺 肳 芠 菛 虋 螡 蟁 鈫 鍆 閅 閺 闅 駇 魰 鳼 鴍 鼤 㗃 㡈 㭣 㵍 㻊 䊟 䎹 䎽 䘇 䟂 䠺 䦩 䫒 䰚
물 物 勿 沕 岉 昒 粅 㫚 䥼
미 美 米 未 味 尾 微 迷 眉 彌 靡 媚 薇 楣 湄 嵋 渼 謎 黴 梶 弥 弭 糜 縻 亹 瀰 麋 敉 媺 獼 娓 蘼 媄
민 民 敏 閔 憫 悶 旻 旼 珉 玟 愍 泯 岷 緡 黽 閩 忞 暋 緍 湣 忟 罠 苠 鰵 黾 僶 渑 澠 慜 潣 敃 燘 痻
미 美 米 未 味 尾 微 迷 眉 彌 靡 媚 薇 楣 湄 嵋 渼 謎 黴 梶 弥 弭 糜 縻 亹 瀰 麋 敉 媺 獼 娓 蘼 媄 苿 㫆 眯 冞 洣 罙 㣲 麛 㜷 䞔 郿 㜫 㳽 浘 眫 㞑 猕 芈 谜 镁 镅 鹛 侎 咩 咪 堳 嬍 孊 嵄 嶶 徾 捤 擟 攗 攠 斖 渳 溦 灖 煝 爢 猸 瑂 癓 睂 睸 瞇 矀 祙 穈 篃 羋 脒 艉 荱 菋 葞 葿 蒾 蔝 蘪 蝞 覹 詸 躾 醚 醾 醿 釄 銤 鎂 鎇 镾 霺 鮇 鶥 麊 㜆 㝥 㟜 㠧 㣆 㥝 㴹 㵟 㸏 䅏 䆊 䇻 䉋 䉠 䉲 䊊 䊳 䋛 䌕
민 民 敏 閔 憫 悶 旻 旼 珉 玟 愍 泯 岷 緡 黽 閩 忞 暋 緍 湣 忟 罠 苠 鰵 黾 僶 渑 澠 慜 潣 敃 燘 痻 砇 悯 焖 缗 闵 闷 闽 鳘 冺 刡 勄 垊 姄 崏 怋 抿 捪 敯 燜 琘 琝 瑉 盿 碈 笢 簢 鈱 錉 鍲 鴖 㞶 㟩 㟭 㥃 㥗 㥸 㨉 㫣 㬆 䁕 䂥 䃉 䋋 䝧 䟨 䡑 䡻 䪸 䲄 脗
밀 密 蜜 謐 宓 樒 滵 宻 谧 嘧 峚 櫁 淧 蔤 藌 蠠 㫘 㳴 㴵 㵥 䁇 䌏 䛑 䤉
박 朴 迫 博 泊 拍 薄 駁 舶 剝 縛 撲 搏 樸 珀 箔 粕 膊 雹 璞 亳 駮 鎛 墣 欂 牔 髆 剥 敀 礴 襮 廹 愽
반 半 反 飯 班 般 返 伴 盤 叛 頒 槃 斑 潘 搬 磻 攀 畔 蟠 絆 礬 拌 泮 盼 磐 瘢 胖 蹣 扳 搫 頖 攽 朌
발 發 髮 拔 跋 鉢 潑 渤 撥 勃 魃 醱 孛 浡 鈸 鵓 哱 脖 钵 発 彂 綍 髪 沷 㪍 墢 茇 㧞 䟦 袚 襏 抜 犮
박 朴 迫 博 泊 拍 薄 駁 舶 剝 縛 撲 搏 樸 珀 箔 粕 膊 雹 璞 亳 駮 鎛 墣 欂 牔 髆 剥 敀 礴 襮 廹 愽 䪨 礡 檏 擈 煿 鑮 彴 窇 㩧 瞨 䥬 缚 钋 铂 镈 镤 飑 馎 驳 啪 噗 嚗 圤 懪 炇 烞 犦 狛 猼 瓟 砶 簙 胉 艊 萡 蒪 趵 釙 鉑 鏷 颮 餺 骲 髉 㕷 㗘 㙸 㬍 㴖 㵡 㹒 㺪 㼎 㿺 䂍 䃗 䄸 䍸 䎅 䑈 䗚 䙏 䥤 䨌 䨔 䨣 䨰 䪇 䪙 䬪 䭦 䮀 䰊 䶈
반 半 反 飯 班 般 返 伴 盤 叛 頒 槃 斑 潘 搬 磻 攀 畔 蟠 絆 礬 拌 泮 盼 磐 瘢 胖 蹣 扳 搫 頖 攽 朌 仮 媻 肦 螌 盘 襻 鞶 癍 憣 詊 斒 牉 㜶 㮽 䛀 矾 绊 颁 饭 冸 坢 奤 姅 婏 嬎 嬔 幋 怑 柈 沜 溿 瀊 炍 秚 縏 蒰 虨 褩 跘 辬 鉡 鋬 鎜 鑻 闆 靽 飰 魬 鳻 㐴 㚘 㢖 㪵 㫠 㭧 㶗 㽃 㽹 㿀 䃑 䃲 䆺 䈲 䉊 䉽 䊩 䙪 䡊 䣲 䬳 䰉 䰔 豳
발 發 髮 拔 跋 鉢 潑 渤 撥 勃 魃 醱 孛 浡 鈸 鵓 哱 脖 钵 発 彂 綍 髪 沷 㪍 墢 茇 㧞 䟦 袚 襏 抜 犮 軷 㗶 柭 秡 胈 鏺 馞 䣮 发 拨 泼 袯 酦 钹 饽 鲅 鹁 侼 叐 坺 妭 愂 挬 桲 溌 炦 癶 癹 盋 砵 缽 荸 菝 葧 詙 蹳 郣 醗 鋍 餑 馛 驋 鮁 鱍 鼥 㔇 㔜 㛘 㛲 㞈 㟑 㧊 㴾 㶿 㺴 䍨 䘠 䚨 䟛 䢌 䣪 䥽 䪬 䭯 䮂 䯋 䳁 䳊
밤 湴
방 方 放 防 房 訪 邦 妨 芳 傍 倣 旁 彷 紡 龐 坊 榜 昉 謗 棒 枋 幇 肪 尨 膀 滂 蚌 磅 舫 蒡 仿 牓 髣
배 北 拜 杯 輩 背 配 倍 培 排 裵 俳 賠 陪 徘 胚 湃 裴 盃 褙 焙 坏 坯 琲 扒 蓓 軰 环 桮 辈 拝 醅 禙
방 方 放 防 房 訪 邦 妨 芳 傍 倣 旁 彷 紡 龐 坊 榜 昉 謗 棒 枋 幇 肪 尨 膀 滂 蚌 磅 舫 蒡 仿 牓 髣 厖 魴 搒 幫 螃 徬 旊 梆 舽 鎊 龎 霶 綁 逄 鈁 雱 狵 㮄 庬 哤 埅 胮 駹 昘 汸 浝 淓 眆 膖 蚄 㧍 䅭 䨦 帮 庞 纺 绑 访 谤 钫 镑 鲂 鳑 匚 嗙 垹 堏 塄 塝 嫎 幚 挷 氆 炐 牥 牻 玤 瓬 稖 篣 縍 耪 肨 艕 蛖 蜯 覫 趽 邡 邫 錺 鞤 髈 鰟 鴋 鶭 㑂 㕩 㕫 㝑 㤃 㤶 㥬 㨍 㫄 㭋 㭶 㿶 䂜 䄱 䏺 䏾 䒍 䖫 䠙 䢍
배 北 拜 杯 輩 背 配 倍 培 排 裵 俳 賠 陪 徘 胚 湃 裴 盃 褙 焙 坏 坯 琲 扒 蓓 軰 环 桮 辈 拝 醅 禙 柸 毰 肧 㚰 赔 伓 俖 偝 啡 妚 嶏 掰 揹 炋 猅 琣 碚 荖 蓜 衃 輫 阫 韛 㔨 㗑 㟝 㠔 㤳 㮎 㯁 㰆 㶔 㶨 㶪 㻗 㾦 䋔 䋳 䒔 䔒 䣙 䪹 䫊 貝
백 百 白 伯 栢 帛 魄 柏 佰 粨 苩 銆 洦 鲌 兡 岶 湐 瓸 竡 絔 鮊 㓦 㡍 㼟 㼣 㿟 䞟 䳆 珀
뱀 㖱
번 番 煩 繁 飜 磻 蕃 藩 燔 幡 樊 翻 蘩 繙 膰 袢 畨 璠 旛 轓 旙 瀿 笲 蹯 墦 橎 㺕 嬏 籓 蠜 烦 僠 勫
번 番 煩 繁 飜 磻 蕃 藩 燔 幡 樊 翻 蘩 繙 膰 袢 畨 璠 旛 轓 旙 瀿 笲 蹯 墦 橎 㺕 嬏 籓 蠜 烦 僠 勫 噃 奿 棥 瀪 緐 羳 薠 襎 鐇 鑁 鱕 鷭 㸋 䉒 䋣 䋦 䌓 䕰 䪛 䪤 䫶 䮳
벌 伐 罰 閥 筏 橃 罸 罚 阀 傠 垡 栰 浌 瞂 藅 㕹 㘺 䇅 䑔 䣹
범 凡 犯 範 范 汎 泛 帆 梵 氾 颿 笵 訉 凢 軓 杋 㠶 舤 㕨 䭵 钒 凣 忛 柉 滼 盕 舧 諚 釩 㴀 䀀 䑺 䒦
범 凡 犯 範 范 汎 泛 帆 梵 氾 颿 笵 訉 凢 軓 杋 㠶 舤 㕨 䭵 钒 凣 忛 柉 滼 盕 舧 諚 釩 㴀 䀀 䑺 䒦 䒮 䟪 䬚 渢
법 法 琺 㳒 疺 珐 佱 灋 鍅
벽 壁 碧 僻 闢 璧 癖 劈 擘 霹 蘗 檗 辟 擗 甓 椑 襞 疈 鷿 鼊 薜 皕 躄 躃 湢 澼 綼 鐴 䌟 鲾 䴙 噼 堛
변 便 變 邊 辯 辨 卞 弁 騈 玭 籩 抃 徧 忭 汴 辮 駢 腁 匾 賆 萹 骿 鴘 拚 邉 変 釆 变 辺 昪 㺹 跰 胼
별 別 瞥 鱉 鼈 鷩 彆 别 撇 蹩 莂 潎 䠥 瘪 鳖 丿 嫳 徶 憋 暼 癟 虌 蛂 蟞 襒 覕 鐅 龞 㔡 㢼 㿜 䇷 䋢
병 兵 病 丙 竝 屛 秉 炳 柄 倂 昺 昞 甁 餠 幷 騈 浜 輧 棅 並 迸 駢 缾 搒 塀 絣 鈵 鉼 鋲 并 屏 併 餅
보 步 保 報 寶 補 普 譜 輔 甫 逋 堡 潽 菩 洑 褓 珤 湺 靌 簠 俌 黼 葆 捗 鞴 鴇 盙 宝 寳 莆 歩 踄 椺
복 福 服 復 伏 卜 腹 覆 複 馥 僕 撲 輻 匐 鰒 洑 宓 茯 蔔 輹 仆 扑 幞 濮 鵩 蝮 墣 蝠 箙 菔 袱 㒒 菐
벽 壁 碧 僻 闢 璧 癖 劈 擘 霹 蘗 檗 辟 擗 甓 椑 襞 疈 鷿 鼊 薜 皕 躄 躃 湢 澼 綼 鐴 䌟 鲾 䴙 噼 堛 孹 廦 憵 挀 揊 檘 礔 礕 稫 糪 繴 釽 銢 鰏 鸊 㘠 㨽 㱸 㵨 㷶 䑀 䤨 䧗 䮠
변 便 變 邊 辯 辨 卞 弁 騈 玭 籩 抃 徧 忭 汴 辮 駢 腁 匾 賆 萹 骿 鴘 拚 邉 変 釆 变 辺 昪 㺹 跰 胼 䪻 䮁 笾 苄 辩 辫 边 骈 匥 峅 汳 炞 犿 玣 甂 稨 糄 藊 覍 辡 閞 㛹 㝸 㣐 㦚 㭓 㲢 㳎 㵷 㻘 䉸 䐔 䒪 䛒 䟍
별 別 瞥 鱉 鼈 鷩 彆 别 撇 蹩 莂 潎 䠥 瘪 鳖 丿 嫳 徶 憋 暼 癟 虌 蛂 蟞 襒 覕 鐅 龞 㔡 㢼 㿜 䇷 䋢 䌘 䏟 䘷 䟤 䤢 䥕 䨆 䭱 䳤 炦
병 兵 病 丙 竝 屛 秉 炳 柄 倂 昺 昞 甁 餠 幷 騈 浜 輧 棅 並 迸 駢 缾 搒 塀 絣 鈵 鉼 鋲 并 屏 併 餅 逬 瓶 帲 邴 傡 陃 帡 怲 栟 竮 聠 㨀 乒 幈 恲 洴 甹 苪 蛃 蛢 軿 頩 䈂 饼 偋 垪 寎 庰 抦 捠 摒 椪 氞 琕 眪 窉 箳 簈 艵 荓 誁 踫 郱 鞆 鮩 鵧 㔙 㙃 㻂 䋑 䍈 䔊 䗒 䨻 䰃 䴵 䶄
보 步 保 報 寶 補 普 譜 輔 甫 逋 堡 潽 菩 洑 褓 珤 湺 靌 簠 俌 黼 葆 捗 鞴 鴇 盙 宝 寳 莆 歩 踄 椺 賲 堢 藵 㙛 报 补 谱 辅 镨 鸨 埗 媬 寚 暜 歨 烳 煲 緥 蕔 蚥 諩 鐠 駂 鮬 鳵 㙅 㷛 㻄 㻉 㾟 䀯 䋠 䑰 䩉 䯙 䲕 䳈 䳰 䴐
복 福 服 復 伏 卜 腹 覆 複 馥 僕 撲 輻 匐 鰒 洑 宓 茯 蔔 輹 仆 扑 幞 濮 鵩 蝮 墣 蝠 箙 菔 袱 㒒 菐 攴 葍 攵 栿 覄 楅 襆 醭 澓 复 辐 鳆 垘 棴 椱 獛 畐 畗 癁 稪 穙 絥 纀 蕧 虙 虲 襥 諨 贌 踾 蹼 轐 鍑 鍢 鮲 鳪 鶝 鸔 㓀 㙏 㚆 㠅 㬼 㯷 㲫 䃼 䇚 䈏 䑑 䕎 䕐 䗱 䞳 䟮 䧤 䨱 䪁 䫝 䴆 䵗
본 本 呠 楍 絊 㡷 㮺 㯻 㶱 䬱
볼 乶
봉 奉 逢 封 鳳 峯 蜂 蓬 俸 縫 捧 鋒 烽 棒 峰 熢 琫 丰 綘 篷 葑 夆 菶 鴌 篈 鳯 塜 賵 芃 捀 艂 蠭 髼
부 父 不 夫 部 富 復 否 婦 浮 扶 府 副 付 負 賦 培 赴 附 簿 符 腐 釜 傅 敷 訃 阜 膚 俯 賻 斧 剖 芙
봉 奉 逢 封 鳳 峯 蜂 蓬 俸 縫 捧 鋒 烽 棒 峰 熢 琫 丰 綘 篷 葑 夆 菶 鴌 篈 鳯 塜 賵 芃 捀 艂 蠭 髼 唪 崶 淎 莑 鏠 凤 缝 赗 锋 仹 埄 埲 塳 妦 摓 桻 槰 樥 浲 湗 溄 漨 焨 犎 琒 甭 盽 竼 纄 蘕 覂 鎽 鑝 鞛 韸 韼 鬔 㛔 㜂 㠮 㡝 㦀 㶻 㷨 㷭 㷯 㸼 㻱 䀱 䆇 䋽 䒠 䗦 䗬 䙜 䡫 䧏 䩬 䩼 䭰 䮾 䳞 䴶
부 父 不 夫 部 富 復 否 婦 浮 扶 府 副 付 負 賦 培 赴 附 簿 符 腐 釜 傅 敷 訃 阜 膚 俯 賻 斧 剖 芙 駙 腑 埠 咐 孵 孚 溥 鳧 缶 莩 趺 俛 艀 仆 祔 俘 鈇 拊 裒 桴 蔀 榑 掊 苻 鮒 蜉 袝 頫 抔 涪 麩 玞 跗 罘 蚨 筟 胕 罦 芣 媍 専 仅 痡 瓿 冨 棓 郛 釡 巭 滏 鳬 尃 怤 捊 柎 咅 暊 砆 秿 㭪 嚩 弣 捬 枎 犃 璷 萯 詂 錇 㓡 㠸 㷌 䔰 䝾 䫍 凫 妇 怀 肤 讣 负 赋 赙 锫 驸 鲋 麸 伕 偩 剻 勏 吥 呋 哹 坿 垺
북 北 踣 僰 鉳
분 分 憤 奮 粉 紛 奔 墳 頒 芬 焚 盆 忿 糞 噴 雰 扮 吩 賁 汾 昐 体 氛 棼 坌 犇 濆 蕡 枌 畚 朌 湓 轒
불 不 佛 拂 弗 彿 黻 紱 髴 祓 茀 芾 岪 艴 韍 咈 巿 紼 怫 昢 刜 坲 帗 煘 笰 仏 払 绂 绋 韨 乀 冹 柫
분 分 憤 奮 粉 紛 奔 墳 頒 芬 焚 盆 忿 糞 噴 雰 扮 吩 賁 汾 昐 体 氛 棼 坌 犇 濆 蕡 枌 畚 朌 湓 轒 帉 棻 砏 笨 肦 膹 黺 鼢 僨 蚡 黂 翂 衯 玢 秎 羵 鼖 燌 瓫 訜 豶 躮 鐼 饙 偾 喷 坟 愤 粪 纷 豮 贲 锛 鲼 倴 兝 喯 坋 奙 妢 岎 幩 弅 捹 撪 朆 桳 梤 橨 歕 泍 渀 瀵 炃 燓 獖 瓰 竕 羒 翉 翸 聁 苯 葐 蒶 蚠 軬 輽 逩 酚 鈖 錛 隫 餴 馚 馩 魵 鱝 㖹 㞣 㤋 㤓 㥹 㨧 㮥 㯣 㱵 㷊 㸮 㿎 䩿 䭻 䴅
불 不 佛 拂 弗 彿 黻 紱 髴 祓 茀 芾 岪 艴 韍 咈 巿 紼 怫 昢 刜 坲 帗 煘 笰 仏 払 绂 绋 韨 乀 冹 柫 梻 氟 炥 甶 翇 鉘 鮄 㘬 㚕 㞸 㪄 㺻 䞞 䭮
붓 㖚
붕 朋 崩 鵬 棚 硼 繃 鬅 堋 漰 萠 弸 塴 掤 椖 綳 绷 镚 鹏 倗 傰 嘣 焩 痭 蹦 鏰 㥊 㬟 㵯 㻚 䙖 䨜
비 非 鼻 悲 備 比 飛 秘 費 妃 卑 碑 肥 批 婢 匪 丕 毖 泌 毘 鄙 扉 譬 庇 臂 裨 沸 憊 緋 脾 誹 痺 蜚
빈 貧 賓 頻 彬 嬪 殯 濱 嚬 瀕 贇 牝 斌 浜 玭 檳 鬢 擯 儐 蘋 邠 苹 顰 繽 豳 霦 鑌 鬂 矉 臏 賔 䎙 份
비 非 鼻 悲 備 比 飛 秘 費 妃 卑 碑 肥 批 婢 匪 丕 毖 泌 毘 鄙 扉 譬 庇 臂 裨 沸 憊 緋 脾 誹 痺 蜚 琵 匕 砒 翡 秕 賁 毗 菲 斐 粃 榧 枇 妣 俾 祕 閟 霏 轡 圮 沘 騑 羆 髀 棐 睥 痞 鼙 邳 紕 貔 贔 悱 椑 腓 庳 陴 仳 芘 淝 篦 芾 濞 蚍 狒 剕 埤 萆 鞴 鵯 痹 屁 淠 狉 蓖 郫 騛 篚 畀 疪 禆 庀 痱 裶 啚 媲 鞞 伾 奰 秠 柲 糒 豼 䀝 嚭 渄 蠯 䚹 俻 偹 厞 婓 嫓 屝 昲 箄 聛 膍 陫 馡 㩌 䤵 备 惫 纰 绯 罴
빈 貧 賓 頻 彬 嬪 殯 濱 嚬 瀕 贇 牝 斌 浜 玭 檳 鬢 擯 儐 蘋 邠 苹 顰 繽 豳 霦 鑌 鬂 矉 臏 賔 䎙 份 馪 璸 髩 薲 蠙 濵 梹 傧 嫔 宾 摈 槟 殡 滨 濒 缤 膑 贫 镔 频 颦 髌 鬓 㺍 娦 朩 椕 獱 瑸 礗 穦 豩 顮 驞 髕 㟗 㡦 㯽 㰋 㻞 䚔 䧬 䨈
빙 氷 聘 馮 憑 騁 冰 凭 娉 凴 俜 砯 䀻 骋 仌 冫 慿 泵 涄 淜 靐 㓈 㵗 䛣
뿐 兺 哛
사 四 事 食 士 死 思 使 史 私 寺 師 仕 絲 舍 謝 巳 射 司 辭 似 賜 社 斯 祀 査 沙 詞 寫 捨 邪 詐 斜
사 四 事 食 士 死 思 使 史 私 寺 師 仕 絲 舍 謝 巳 射 司 辭 似 賜 社 斯 祀 査 沙 詞 寫 捨 邪 詐 斜 蛇 赦 祠 泗 埈 獅 飼 唆 嗣 徙 紗 些 奢 瀉 娑 麝 簑 肆 俟 乍 伺 蓑 砂 莎 駟 裟 梭 篩 柶 渣 僿 槎 竢 蜡 糸 笥 榭 騃 姒 卸 闍 撕 楂 駛 缷 剚 咋 汜 傞 覗 鯊 崽 痧 皶 魦 鰤 辞 查 賖 涘 耜 蓰 屣 冩 簁 賒 挲 辤 禠 舎 虵 辝 髿 禩 肂 衺 柤 厙 嗄 漇 蹝 戺 柌 蒒 蕬 鍦 䣃 写 泻 丝 厍 咝 师 狮 筛 纱
삭 數 削 索 朔 鑠 槊 爍 箾 蒴 矟 搠 烁 铄 嗍 揱 溹 獡 鎙 㩂 㮶 䀥 䁻 䅴 䌇 䖛
산 山 産 散 算 酸 傘 刪 珊 疝 霰 蒜 汕 繖 訕 餕 潸 毿 狻 鏟 澘 剷 橵 姍 孿 閊 產 筭 删 㪚 跚 滻 㦃
산 山 産 散 算 酸 傘 刪 珊 疝 霰 蒜 汕 繖 訕 餕 潸 毿 狻 鏟 澘 剷 橵 姍 孿 閊 產 筭 删 㪚 跚 滻 㦃 仐 痠 隡 产 铲 伞 姗 孪 毵 浐 讪 闩 馓 匴 厁 圸 壭 嵼 摌 杣 潵 狦 祘 笇 簅 糤 舢 藖 赸 軕 邖 鏾 閂 饊 㛽 㢫 㪔 㮼 㯆 㹌 㹪 䃟 䈀 䉈 䊲 䐮 䚲 䝜 䡲 䨷 䴮
살 殺 蔡 薩 撒 煞 乷 杀 摋 㐊 蔱 萨 榝 樧 脎 虄 閷 䊛
삼 三 參 蔘 森 滲 渗 衫 芟 杉 叅 纔 摻 毿 糝 鬖 椙 櫼 釤 彡 縿 槮 襂 穇 穼 糁 钐 仨 俕 剼 嘇 幓 弎
삽 揷 澁 颯 鈒 歃 翣 鍤 卅 霎 霅 唼 挿 澀 鎝 插 牐 箑 靸 趿 涩 渋 钑 锸 飒 偛 喢 帹 歰 甴 箿 翜 臿
상 上 相 想 常 賞 商 尙 喪 傷 霜 狀 嘗 詳 祥 象 床 桑 像 償 裳 庠 箱 爽 觴 翔 孀 廂 湘 牀 橡 嫦 瀧
삼 三 參 蔘 森 滲 渗 衫 芟 杉 叅 纔 摻 毿 糝 鬖 椙 櫼 釤 彡 縿 槮 襂 穇 穼 糁 钐 仨 俕 剼 嘇 幓 弎 曑 椮 檆 毝 涁 犙 甧 糂 糣 罧 葠 蓡 薓 贂 鏒 閐 㡎 㣌 㧲 㭅 㰑 㵕 㺑 䀐 䅟 䊉 䊏 䥇 䦂 䫅 䫩 䫮 䵇
삽 揷 澁 颯 鈒 歃 翣 鍤 卅 霎 霅 唼 挿 澀 鎝 插 牐 箑 靸 趿 涩 渋 钑 锸 飒 偛 喢 帹 歰 甴 箿 翜 臿 萐 譅 遪 雭 馺 㒎 㚫 㛼 㩑 㪪 㰱 㴙 㵤 㽂 䈉 䔼 䙣 䛽 䝊 䬃 䬊 䮜 䮢
상 上 相 想 常 賞 商 尙 喪 傷 霜 狀 嘗 詳 祥 象 床 桑 像 償 裳 庠 箱 爽 觴 翔 孀 廂 湘 牀 橡 嫦 瀧 峠 倘 甞 殤 顙 徜 緗 晌 塽 鎟 鬺 状 尚 愓 恦 垧 厢 瑺 礵 丧 鋿 鏛 扄 謪 鸘 䘮 鱨 樉 贘 驦 鮝 㫾 绱 鲞 伤 偿 墒 尝 殇 缃 觞 详 赏 颡 骦 鲿 鹴 丄 仩 傸 嗓 嚐 嶑 慡 慯 搡 桒 桛 槡 滳 漡 漺 潒 灀 磉 絴 緔 縔 葙 蔏 螪 蟐 蟓 蠰 褬 襐 鏯 鐌 鑜 騻 鯗 鱌 鱶 鷞 㜀 㟄 㦂 㦼 䉘 䌮 䒙 䔗 䔪 䗮 䡦 䫙
새 塞 璽 賽 鰓 崽 壐 玺 赛 鳃 噻 愢 瓕 簺 㗷 㘔 䈢 䚡 䰄 䵘
색 色 塞 索 嗇 薔 穡 愬 槭 濇 瀒 賾 摵 矠 繬 鎍 啬 穑 赜 铯 嗦 懎 擌 栜 歮 歵 泎 洓 瘷 穯 轖 銫 齚
색 色 塞 索 嗇 薔 穡 愬 槭 濇 瀒 賾 摵 矠 繬 鎍 啬 穑 赜 铯 嗦 懎 擌 栜 歮 歵 泎 洓 瘷 穯 轖 銫 齚 齰 㣱 㥶 㩙 㮦 㱇 㾊 䂹 䉢 䊂 䞽
생 生 省 牲 甥 笙 眚 鉎 泩 栍 鼪 殅 湦 珄 苼 貹 﨡 㗂 㽒 㽓 䚇 䲼 䴤
서 西 書 序 暑 庶 徐 署 恕 緖 誓 敍 逝 瑞 舒 曙 棲 胥 黍 鼠 犀 薯 嶼 抒 壻 栖 筮 鋤 絮 墅 捿 婿 叙
석 石 夕 席 昔 惜 釋 析 錫 奭 碩 晳 潟 汐 淅 蓆 腊 舃 蜥 鉐 晰 矽 鼫 释 穸 螫 祏 裼 焬 皙 襫 邜 桞
선 先 仙 善 單 線 船 選 鮮 洗 宣 旋 禪 膳 繕 扇 煽 璿 璇 瑄 癬 腺 亘 羨 銑 蟬 詵 饍 嬋 霰 僊 蘚 跣
설 雪 說 設 舌 泄 薛 卨 屑 洩 渫 褻 楔 齧 偰 揲 爇 紲 稧 媟 碟 暬 挈 絏 䙝 枻 囓 焫 躠 緤 褉 樰 㻡
섬 暹 纖 蟾 陝 殲 閃 贍 剡 憸 摻 銛 譫 睒 韱 孅 笘 殱 爓 纎 棎 掞 㴸 䃸 纤 掺 歼 繊 谵 赡 铦 闪 嬐
섭 葉 涉 攝 燮 躡 懾 歙 囁 聶 鑷 灄 顳 爕 摂 讋 屧 籋 渉 夑 鍱 欇 鞢 躞 嗫 慑 摄 滠 聂 蹑 镊 颞 屟
성 成 星 省 性 姓 城 誠 聖 聲 盛 晟 醒 宬 惺 腥 珹 猩 筬 騂 餳 瑆 渻 垶 峸 狌 晠 煋 郕 鋮 偗 箵 絾
세 世 說 歲 細 洗 勢 稅 貰 笹 洒 繐 蛻 帨 税 嵗 歳 挩 裞 檅 涗 䵻 岁 细 贳 丗 亗 卋 朑 涚 祱 蔧 跩
소 小 少 所 笑 消 素 召 昭 訴 掃 蘇 燒 騷 疎 蔬 巢 紹 邵 沼 蕭 宵 逍 簫 梢 搔 遡 梳 塑 甦 瘙 疏 嘯
속 速 俗 續 束 屬 粟 贖 涑 謖 洬 遬 属 蔌 餗 觫 鋉 梀 誎 簌 続 续 谡 赎 僳 憟 樕 殐 潥 藗 藚 趚 㑛
서 西 書 序 暑 庶 徐 署 恕 緖 誓 敍 逝 瑞 舒 曙 棲 胥 黍 鼠 犀 薯 嶼 抒 壻 栖 筮 鋤 絮 墅 捿 婿 叙 紓 噬 撕 鉏 湑 澨 芧 揟 耡 敘 遾 閪 溆 醑 諝 蜍 㥠 漵 潻 庻 糈 緒 偦 俆 圕 垿 屖 悇 䋒 䑕 书 屿 纾 绪 谞 锄 卥 唑 嫬 徆 恓 楈 樨 氥 汿 沀 潊 濖 瑹 癙 硒 稰 簭 粞 糬 縃 聓 聟 舾 蒣 藇 蝑 蠴 觢 迉 鏣 魣 鱪 鱮 鱰 鵨 鼡 㐨 㓾 㘧 㛉 㜿 㠘 㢴 㣽 㭀 㯕 㵂 㵰 㷂 㽰 㾷 䇴 䈝 䉀 䋡 䍱 䓚 䤱 䦽 䧈
석 石 夕 席 昔 惜 釋 析 錫 奭 碩 晳 潟 汐 淅 蓆 腊 舃 蜥 鉐 晰 矽 鼫 释 穸 螫 祏 裼 焬 皙 襫 邜 桞 棤 檡 炻 睗 舄 硕 釈 锡 佦 冟 匇 唽 嬕 惁 扸 沯 澙 焟 磶 緆 菥 蕮 褯 釸 鮖 㒪 㙽 㛫 㛭 㝜 㫺 㭊 㱤 䁺 䂖 䂙 䂞 䄷 䖨 䨛 䲽
선 先 仙 善 單 線 船 選 鮮 洗 宣 旋 禪 膳 繕 扇 煽 璿 璇 瑄 癬 腺 亘 羨 銑 蟬 詵 饍 嬋 霰 僊 蘚 跣 鐥 渲 琁 敾 湺 尠 燹 譔 綫 鏇 騸 愃 墡 筅 脧 駪 熯 鱻 姍 姺 嫙 尟 屳 歚 譱 鱓 縇 鲜 缮 墠 獮 躚 僎 蟺 嬗 揎 蟮 襈 軐 㶍 㷽 䆄 僐 漩 舩 鍌 鱔 㵛 禅 线 铣 婵 狝 癣 籼 藓 蝉 诜 跹 选 镟 骟 鳝 傓 僲 塇 奾 嫸 廯 搧 暶 檈 毨 氙 灗 灷 烍 珗 碹 磰 禒 秈 箲 絤 縼 繏 缐 苮 蔙 蜁 褼 謆 赻 跴 蹮 鄯
설 雪 說 設 舌 泄 薛 卨 屑 洩 渫 褻 楔 齧 偰 揲 爇 紲 稧 媟 碟 暬 挈 絏 䙝 枻 囓 焫 躠 緤 褉 樰 㻡 说 亵 绁 设 鳕 乴 伳 僁 哾 抴 揳 榍 疶 禼 糏 絬 膤 艝 蔎 蛥 説 轌 辥 辪 鱈 㐥 㓭 㔎 㞕 㡜 㣯 㨝 㳿 㴮 㴽 㿱 䝟 䥹 䨮 䱑 契 㨹
섬 暹 纖 蟾 陝 殲 閃 贍 剡 憸 摻 銛 譫 睒 韱 孅 笘 殱 爓 纎 棎 掞 㴸 䃸 纤 掺 歼 繊 谵 赡 铦 闪 嬐 撡 攕 晱 炶 熌 燅 襳 覢 讝 㚒 㣣 㨛 㩥 㪎 㰇 㰊 㺤 䀹 䁡 䃱 䆎 䊱 䊹 䕭 䠾 䯭 䯹
섭 葉 涉 攝 燮 躡 懾 歙 囁 聶 鑷 灄 顳 爕 摂 讋 屧 籋 渉 夑 鍱 欇 鞢 躞 嗫 慑 摄 滠 聂 蹑 镊 颞 屟 弽 徢 疌 蠂 詟 讘 踂 踙 銸 韘 㒤 㦪 㰔 㰼 㴇 㸉 㸎 㽊 䜓 䢡 䤮 䯀 䯅
성 成 星 省 性 姓 城 誠 聖 聲 盛 晟 醒 宬 惺 腥 珹 猩 筬 騂 餳 瑆 渻 垶 峸 狌 晠 煋 郕 鋮 偗 箵 絾 荿 謃 声 胜 诚 铖 骍 墭 娍 曐 殸 皨 睲 窚 篂 觪 觲 鍟 鮏 鯎 鯹 㙚 㝭 㨘 㮐 㷣 㼩 㼳 㽮 㾪 䁞 䃏 䇈 䗌 䚖 䫆 貹
세 世 說 歲 細 洗 勢 稅 貰 笹 洒 繐 蛻 帨 税 嵗 歳 挩 裞 檅 涗 䵻 岁 细 贳 丗 亗 卋 朑 涚 祱 蔧 跩 軎 轊 銴 齛 齥 㑕 㔟 㔺 㢷 㱑 㸷 䬽 忕 彗
소 小 少 所 笑 消 素 召 昭 訴 掃 蘇 燒 騷 疎 蔬 巢 紹 邵 沼 蕭 宵 逍 簫 梢 搔 遡 梳 塑 甦 瘙 疏 嘯 銷 韶 瀟 炤 溯 釗 篠 踈 艘 霄 泝 埽 翛 愬 鞘 酥 繅 卲 劭 蛸 俏 捎 魈 佋 箾 嗉 筱 塐 樔 膆 鮹 鰺 䟽 㪽 咲 穌 焇 焼 销 熽 筲 謏 蠨 傃 㴑 玿 痟 矂 喿 璅 騒 㗛 愫 慅 旓 櫹 歗 溸 琑 苏 关 啸 巣 扫 潇 烧 稣 箫 绍 缫 萧 蟏 诉 骚 鲹 鳋 嘨 囌 娋 嫊 尕 巶 弰 彇 掻 柖 榡 櫯 毜 氉 溞 漅 潲 牊 窼 笤
속 速 俗 續 束 屬 粟 贖 涑 謖 洬 遬 属 蔌 餗 觫 鋉 梀 誎 簌 続 续 谡 赎 僳 憟 樕 殐 潥 藗 藚 趚 㑛 㔄 㬘 㯈 䅇 䌚 䔎 䔩 䞖 䥔 䲇 䴰
손 孫 損 遜 巽 飡 蓀 飧 飱 喰 噀 孙 损 狲 荪 逊 巺 愻 搎 槂 潠 猻 蕵 薞 顨 㢲 䁚 䐣 䕖
솔 率 蟀 窣 卛 乺 摔 甩 碿 蜶 㲞 䢦 帥
솟 㕾
송 送 松 訟 誦 頌 宋 悚 淞 竦 柗 鬆 菘 荣 㩳 㞞 讼 诵 锶 颂 倯 凇 庺 彸 愯 憽 捒 枀 枩 梥 楤 檧 蜙
송 送 松 訟 誦 頌 宋 悚 淞 竦 柗 鬆 菘 荣 㩳 㞞 讼 诵 锶 颂 倯 凇 庺 彸 愯 憽 捒 枀 枩 梥 楤 檧 蜙 鍶 鎹 餸 駷 㧐 㨦 㬝 㮤 㮸 㹐 䛦 䢠 䯳
솨 惢 䐝
솰 唰
쇄 殺 刷 鎖 灑 碎 洒 瑣 曬 惢 晒 鎻 鏁 锁 縰 鎩 嗩 埣 㬠 唢 琐 铩 嗮 涮 溑 砕 繀 㕞 䈗 䞆 䣔 䵀
쇠 衰 釗 钊 哸 夊 鞖 㲤 䪎
수 水 手 數 守 首 樹 受 授 收 雖 修 壽 須 誰 秀 愁 遂 隨 囚 殊 輸 需 獸 垂 帥 洙 睡 搜 隋 髓 銖 蒐
숙 宿 叔 淑 孰 熟 肅 夙 菽 塾 璹 潚 琡 倏 俶 橚 儵 鷫 驌 婌 䎘 璛 埱 蓿 倐 焂 鱐 玊 掓 㶖 䃤 䨹 粛
순 順 純 脣 巡 循 旬 殉 瞬 舜 淳 珣 盾 洵 筍 醇 馴 荀 唇 詢 諄 徇 恂 蓴 楯 錞 焞 蕣 栒 橓 肫 鶉 紃
수 水 手 數 守 首 樹 受 授 收 雖 修 壽 須 誰 秀 愁 遂 隨 囚 殊 輸 需 獸 垂 帥 洙 睡 搜 隋 髓 銖 蒐 酬 羞 繡 粹 袖 瘦 讐 竪 嗾 狩 嫂 穗 脩 戍 綏 藪 綬 邃 鬚 岫 燧 隧 涑 嗽 漱 琇 峀 茱 璲 蓚 銹 叟 讎 售 豎 晬 睟 陲 睢 祟 莠 瞍 颼 殳 酥 廋 溲 膄 脺 泅 濉 籔 膸 饈 随 収 数 捜 薮 虽 髄 綉 襚 瀡 倕 滫 䢘 詶 繻 鎪 䜿 誶 潄 稤 篲 酧 䏂 惥 擻 橾 櫢 穂 菙 訹 醻 扌 檖 腄 㒸 䆳 夀 栦 澻 痩 禭 雔
숙 宿 叔 淑 孰 熟 肅 夙 菽 塾 璹 潚 琡 倏 俶 橚 儵 鷫 驌 婌 䎘 璛 埱 蓿 倐 焂 鱐 玊 掓 㶖 䃤 䨹 粛 肃 骕 鹔 儴 尗 摍 珟 莤 虪 諔 跾 鮛 㑉 㑐 㓘 㜚 㝛 㩋 㪩 㳤 㴋 㴼 䃞 䏋 䑿 䱙
순 順 純 脣 巡 循 旬 殉 瞬 舜 淳 珣 盾 洵 筍 醇 馴 荀 唇 詢 諄 徇 恂 蓴 楯 錞 焞 蕣 栒 橓 肫 鶉 紃 狥 鬊 眴 侚 盹 駨 笋 峋 廵 輴 漘 㥧 㦏 䋸 䥎 莼 纯 询 谆 顺 驯 鹑 鹠 伨 偱 咰 奞 姰 憌 揗 杊 榫 槆 毥 浱 湻 滣 潃 犉 畃 瞚 瞤 碷 磭 箰 簨 芛 蒓 衠 賐 郇 醕 陙 鯙 㒐 㔼 㚝 㝄 㝇 㡄 㨚 㫬 㬀 㰬 㵌 㵮 㶷 㸪 㽦 䀢 䀵 䏛 䐇 䑞 䓐 䔚 䖲 䘩 䙉 䜦 䣨 䴄
술 戌 述 術 鉥 卹 潏 絉 銊 㺷 荗 珬 沭 欰 蒁 袕 錰 鶐 㖅 㞊 㾁 䢞 䢤 䮅 䳳
숭 崇 嵩 崧 滕 菘 硹 㓽 㟣 㣝 䯷
쉬 倅 晬 淬 焠 伜 琗 㳃 䘹
슬 瑟 膝 蝨 虱 飋 㻭 厀 鲺 濏 璱 鯴 㺩 㻎
습 習 拾 襲 濕 褶 隰 慴 熠 湿 霫 謵 习 袭 鳛 啃 嶍 槢 溼 漝 飁 騽 鰼 㒊 㗩 㠄 㦻 㿇 䏉 䒁 䗑
승 勝 承 乘 僧 昇 升 繩 丞 陞 蠅 塍 鬙 乗 蝇 䋲 鱦 椉 氶 㱡 嵊 憴 縄 绳 冼 呏 堘 抍 斘 枡 桝 榺 橳
시 十 時 始 示 市 寺 視 詩 試 是 施 侍 矢 柴 屍 諡 弑 匙 猜 豺 媤 柿 尸 恃 蓍 謚 豕 翅 屎 嘶 蒔 昰
식 食 式 植 識 息 飾 湜 殖 軾 蝕 拭 熄 寔 埴 篒 栻 喰 媳 鉽 烒 蚀 轼 饣 饰 煶 瘜 禃 籂 蒠 遈 鄎 鎴
신 身 信 臣 新 神 申 辰 辛 愼 伸 晨 紳 訊 迅 腎 燼 宸 薪 呻 蜃 娠 藎 莘 侁 汕 矧 贐 哂 汛 頣 脤 駪
승 勝 承 乘 僧 昇 升 繩 丞 陞 蠅 塍 鬙 乗 蝇 䋲 鱦 椉 氶 㱡 嵊 憴 縄 绳 冼 呏 堘 抍 斘 枡 桝 榺 橳 洆 溗 焺 畻 竔 脀 蕂 蝿 譝 阩 陹 騬 鵿 㐼 㞼 㮱 㴍 䒏 䮪 䱆
시 十 時 始 示 市 寺 視 詩 試 是 施 侍 矢 柴 屍 諡 弑 匙 猜 豺 媤 柿 尸 恃 蓍 謚 豕 翅 屎 嘶 蒔 昰 枾 啻 厮 緦 澌 廝 柹 偲 禔 撕 諟 兕 豉 塒 顋 枲 諰 釃 翤 鍉 絁 腮 颸 弒 䌤 眎 鳲 凘 翨 䲭 乨 毸 漦 襹 揌 犲 褷 䂠 埘 时 缌 莳 视 试 诗 酾 铈 飔 鲥 鸤 兘 呞 咶 宩 崼 嵵 徥 戠 揓 旹 榯 毢 沶 湤 溡 眂 礻 祡 笶 簛 罳 翄 膪 葈 葹 邿 鈰 鉃 鰣 鸍 鼭 㒋 㒮 㒾 㔭 㖷 㚸 㞔 㣈 㫭 㮛 㳏 㹝 㺇 㻢 䈕
식 食 式 植 識 息 飾 湜 殖 軾 蝕 拭 熄 寔 埴 篒 栻 喰 媳 鉽 烒 蚀 轼 饣 饰 煶 瘜 禃 籂 蒠 遈 鄎 鎴 飠 餝 㥀 㮩 㴧 㵓 䤭 䭒
신 身 信 臣 新 神 申 辰 辛 愼 伸 晨 紳 訊 迅 腎 燼 宸 薪 呻 蜃 娠 藎 莘 侁 汕 矧 贐 哂 汛 頣 脤 駪 噺 囟 姺 慎 抻 賮 兟 伩 柛 璶 㾕 峷 鋠 㜪 烬 绅 肾 荩 讯 识 赆 卂 吲 妽 孞 屾 弞 扟 敒 昚 曟 榊 氠 烥 燊 珅 甡 眒 眘 矤 砷 祳 笉 籶 籸 胂 茞 莀 裑 訙 訠 訫 訷 鉮 阠 顖 鯓 鰰 鵢 鷐 麎 㐰 㑗 㚨 㛙 㛛 㢹 㫳 㭡 㰮 㳯 㶦 㶳 䅸 䆣 䊁 䒖 䚱 䛜 䛨 䝲 䢅 䢻 䣅 䪿 䭀 䯂 䰠
실 室 失 實 悉 蟋 飋 窸 实 実 怸 榁 螅 鏭 鰘 㗭 㣰 㺙 䊝 䶡
심 心 甚 深 沈 尋 審 瀋 沁 諶 芯 沉 潯 梣 椹 燖 葚 鐔 鱏 淰 愖 諗 杺 伈 蕈 㴱 襑 㝷 宷 枔 鬵 煁 瞫
심 心 甚 深 沈 尋 審 瀋 沁 諶 芯 沉 潯 梣 椹 燖 葚 鐔 鱏 淰 愖 諗 杺 伈 蕈 㴱 襑 㝷 宷 枔 鬵 煁 瞫 覾 㻣 吣 婶 审 寻 浔 渖 谂 谌 镡 鲟 㖊 侺 吢 噚 堔 嬸 忄 憳 攳 桪 樳 橬 璕 瘎 蟳 讅 谉 邥 鄩 鈊 魫 鱘 㔤 㚞 㚯 㜄 㜦 㣺 㤈 㰂 㶒 㽸 䉇 䚓 䤁
십 十 拾 什 辻 兙 卌 椞 瓧 竍 籵 趇 䦹
쌀 㐘
쌍 雙 双 䨇 艭 孇 欆 﨎 㕠 䉶 䝄
씨 氏
씻 㘒
아 兒 我 雅 亞 餓 芽 牙 阿 衙 俄 訝 啞 鴉 峨 娥 鵝 莪 蛾 迓 疴 哦 婀 丫 峩 娿 砑 笌 錏 鵞 痾 児 婭
악 樂 惡 岳 握 愕 堊 顎 嶽 幄 渥 鰐 鄂 鍔 齷 萼 諤 鶚 咢 喔 噩 卾 腭 偓 覨 齶 楽 鸑 遌 崿 蕚 㮙 堮
안 安 案 眼 顔 岸 雁 鞍 按 晏 鮟 鴈 犴 贋 桉 顏 鷃 贗 䬶 楌 㷳 䅁 赝 颜 铵 顸 侒 偐 垵 堓 妟 姲 婩
알 謁 閼 軋 斡 遏 訐 戞 頞 揠 鴶 嘎 穵 戛 歹 嶭 猰 圠 捾 堨 钀 䵝 讦 谒 轧 阏 劜 咹 嘠 屵 挖 擜 歺
암 暗 巖 癌 庵 闇 岩 菴 俺 唵 諳 黯 嵓 頷 馣 晻 媕 啽 腤 葊 蓭 嵒 碞 韽 揞 黭 痷 萻 鵪 黤 䳺 埯 谙
압 壓 押 鴨 岬 狎 礏 罨 鸭 庒 岋 姶 㔩 䑪 压 圧 儑 匎 圔 庘 曱 炠 砐 硆 鞥 鰪 㕎 㛕 㳌 㷈 㾎 䆘 䑥
앙 仰 殃 央 怏 昻 鴦 秧 盎 鞅 泱 卬 坱 昂 枊 䭹 抰 胦 㟅 䀚 鸯 佒 傟 咉 姎 岇 岟 慃 炴 眏 紻 羻 詇
애 愛 哀 涯 礙 埃 艾 喝 崖 隘 靄 曖 厓 碍 藹 欸 騃 睚 皚 瞹 挨 僾 薆 靉 捱 獃 磑 娭 唉 啀 噯 崕 漄
액 額 厄 液 腋 扼 縊 掖 阨 搤 戹 呝 詻 軛 頟 缢 轭 额 峉 涱 砨 磀 苊 蚅 豟 貖 軶 鈪 阸 㟯 㧖 䘸 䝈
아 兒 我 雅 亞 餓 芽 牙 阿 衙 俄 訝 啞 鴉 峨 娥 鵝 莪 蛾 迓 疴 哦 婀 丫 峩 娿 砑 笌 錏 鵞 痾 児 婭 亜 椏 稏 妸 鵶 俹 聣 䋍 亚 哑 垭 娅 挜 桠 氩 痖 讶 钶 铔 锕 饿 鸦 鹅 䅉 伢 厊 吖 唖 啊 妿 孲 屙 庌 掗 氬 涐 犽 玡 珴 疨 瘂 皒 睋 硪 蕥 蚜 襾 覀 誐 鈳 錒 鐚 騀 齖 㚳 㝞 㢌 㢐 㦱 㧎 㧴 㰳 㿿 䃁 䄉 䄰 䋩 䋪 䍓 䖸 䢝 䪵 䳗 䳘 猗 枒
악 樂 惡 岳 握 愕 堊 顎 嶽 幄 渥 鰐 鄂 鍔 齷 萼 諤 鶚 咢 喔 噩 卾 腭 偓 覨 齶 楽 鸑 遌 崿 蕚 㮙 堮 恶 鳄 乐 垩 悪 谔 锷 颚 鹗 龌 偔 僫 捳 楃 櫮 湂 琧 腛 蝁 讍 鑩 鱷 㓵 㖾 㗁 㟧 㠋 㦍 㹊 䠎 䣞 䥃 䫷
안 安 案 眼 顔 岸 雁 鞍 按 晏 鮟 鴈 犴 贋 桉 顏 鷃 贗 䬶 楌 㷳 䅁 赝 颜 铵 顸 侒 偐 垵 堓 妟 姲 婩 峖 暥 氨 洝 矸 荌 軅 銨 錌 鞌 頇 騴 鳫 鴳 黫 㗴 㘖 㟁 㫨 㸩 䀂 䢿 䨃 䮗 䯃 䳛 䭓
알 謁 閼 軋 斡 遏 訐 戞 頞 揠 鴶 嘎 穵 戛 歹 嶭 猰 圠 捾 堨 钀 䵝 讦 谒 轧 阏 劜 咹 嘠 屵 挖 擜 歺 濣 焥 窫 聐 胺 錷 齃 齾 﨟 㖕 㖖 㧉 㩵 㷎 䀑 䁊 䔾 䥟 䮸 䯉 䯵 䰲
암 暗 巖 癌 庵 闇 岩 菴 俺 唵 諳 黯 嵓 頷 馣 晻 媕 啽 腤 葊 蓭 嵒 碞 韽 揞 黭 痷 萻 鵪 黤 䳺 埯 谙 颔 鹌 匼 啱 堷 壧 巌 巗 盦 盫 礹 罯 裺 誝 隌 雸 鶕 麙 麣 黬 㘙 㛺 㜝 㞄 㽢 䅖 䖗 䨄 䫡 䬓 䯥
압 壓 押 鴨 岬 狎 礏 罨 鸭 庒 岋 姶 㔩 䑪 压 圧 儑 匎 圔 庘 曱 炠 砐 硆 鞥 鰪 㕎 㛕 㳌 㷈 㾎 䆘 䑥
앙 仰 殃 央 怏 昻 鴦 秧 盎 鞅 泱 卬 坱 昂 枊 䭹 抰 胦 㟅 䀚 鸯 佒 傟 咉 姎 岇 岟 慃 炴 眏 紻 羻 詇 軮 醠 鉠 雵 駚 㒕 㭿 㹧 㼜 㿮 䄃 䇦 䒋 䒢 䘧 䩕 䬬 䭺 䱀
애 愛 哀 涯 礙 埃 艾 喝 崖 隘 靄 曖 厓 碍 藹 欸 騃 睚 皚 瞹 挨 僾 薆 靉 捱 獃 磑 娭 唉 啀 噯 崕 漄 瑷 敱 爱 閡 㝵 壒 䔽 譪 呃 毐 璦 㣻 嗳 嫒 暧 皑 硙 蔼 锿 阂 霭 伌 叆 哎 唲 嘊 堐 塧 娾 嬡 嵦 懓 懝 敳 昹 濭 燰 猚 皧 砹 硋 賹 銰 鎄 鑀 餲 馤 鱫 鴱 㕌 㗒 㘷 㤅 㦈 㱯 㶼 㾏 㿄 䅬 䕏 䝽 䨠 䬵 䶣
액 額 厄 液 腋 扼 縊 掖 阨 搤 戹 呝 詻 軛 頟 缢 轭 额 峉 涱 砨 磀 苊 蚅 豟 貖 軶 鈪 阸 㟯 㧖 䘸 䝈 䩹 䱮
앳 厑
앵 鶯 櫻 鸚 罌 嚶 鷪 罃 嫈 鸎 甖 䴍 嘤 桜 樱 罂 莺 鹦 奣 甇 譻 㴄 㹙 㹚 䁝 䨉
야 夜 野 也 若 射 耶 惹 倻 椰 爺 冶 揶 埜 捓 枒 喏 琊 鋣 嘢 焲 瑘 䤳 爷 铘 乛 亱 偌 吔 壄 婼 擨 漜
약 弱 藥 約 若 略 躍 掠 葯 蒻 鑰 籥 禴 爚 篛 鰯 龠 鶸 瀹 礿 㵸 箬 箹 蘥 㿑 药 哟 约 薬 跃 叒 喲 嵶
양 羊 兩 陽 洋 良 養 量 讓 揚 凉 梁 樣 糧 楊 壤 諒 亮 襄 孃 瘍 攘 釀 恙 禳 痒 佯 暘 穰 瀁 敭 煬 癢
어 魚 語 漁 於 御 禦 圄 瘀 馭 齬 圉 飫 敔 淤 茣 扵 蘓 峿 籞 菸 鋙 渔 语 铻 饫 驭 鱼 龉 仒 唹 棜 秗
야 夜 野 也 若 射 耶 惹 倻 椰 爺 冶 揶 埜 捓 枒 喏 琊 鋣 嘢 焲 瑘 䤳 爷 铘 乛 亱 偌 吔 壄 婼 擨 漜 祂 釾 鎁 鵺 㖡 㘃 㙒 㭨 䓉 䥺
약 弱 藥 約 若 略 躍 掠 葯 蒻 鑰 籥 禴 爚 篛 鰯 龠 鶸 瀹 礿 㵸 箬 箹 蘥 㿑 药 哟 约 薬 跃 叒 喲 嵶 楉 洂 渃 纅 藞 鄀 鰙 鸙 㒢 㜰 㰛 䋤 䐞 䖃 䚥 䟑 䠯 䤀 䶳
양 羊 兩 陽 洋 良 養 量 讓 揚 凉 梁 樣 糧 楊 壤 諒 亮 襄 孃 瘍 攘 釀 恙 禳 痒 佯 暘 穰 瀁 敭 煬 癢 驤 漾 颺 徉 鑲 瀼 烊 眻 蘘 輰 譲 様 㨾 样 昜 鍚 懹 勷 䑋 崵 㐮 纕 壌 饟 䉴 垟 欀 瓖 諹 嚷 禓 阦 㔦 扬 镶 阳 养 嬢 旸 杨 炀 疡 让 酿 醸 钖 飏 馕 骧 劷 勨 奍 忀 懩 攁 檨 氜 氧 氱 爙 獽 珜 瓤 礢 穣 羏 羕 蛘 蝆 躟 鐊 霷 鬤 鰑 鴹 鸉 㠤 㦹 㬕 㳱 㺊 䁑 䍩 䑆 䖆 䖹 䬗 䬺 䭐 䭥 䵮 椋
어 魚 語 漁 於 御 禦 圄 瘀 馭 齬 圉 飫 敔 淤 茣 扵 蘓 峿 籞 菸 鋙 渔 语 铻 饫 驭 鱼 龉 仒 唹 棜 秗 箊 篽 蓹 蘌 醧 饇 鯲 鷠 䁩 䏸 䐳 䔡 䗨 䢩 䥏 䰻 䱷 䲣 䘘
억 億 憶 抑 臆 檍 薏 嶷 繶 澺 亿 忆 悥 癔 肊 訲 餩 㫇 䖁 䗷
언 言 焉 彦 諺 堰 偃 讞 嫣 鄢 鼴 匽 鼹 傿 彥 琂 唁 漹 蔫 喭 蝘 讠 谚 谳 嘕 墕 娮 嵃 愝 椻 牪 甗 篶
언 言 焉 彦 諺 堰 偃 讞 嫣 鄢 鼴 匽 鼹 傿 彥 琂 唁 漹 蔫 喭 蝘 讠 谚 谳 嘕 墕 娮 嵃 愝 椻 牪 甗 篶 萒 褗 訁 躽 遃 郾 隁 鰋 鶠 齞 齴 㢇 㫃 㰽 䁙 䇾 䓂 䗡 䞁 䤷
얼 蘖 孼 臬 孽 糱 乻 臲 闑 隉 嵲 糵 枿 㜸 櫱 蠥 镍 巕 槷 鎳 㙞 㮆 㴪 䡾
엄 嚴 掩 奄 儼 俺 淹 广 閹 晻 崦 弇 罨 曮 醃 釅 渰 揜 顩 㤿 㛪 隒 严 俨 厂 腌 觃 阉 剦 厈 厳 噞 孍
엄 嚴 掩 奄 儼 俺 淹 广 閹 晻 崦 弇 罨 曮 醃 釅 渰 揜 顩 㤿 㛪 隒 严 俨 厂 腌 觃 阉 剦 厈 厳 噞 孍 嶖 欕 硽 覎 龑 㚧 㢂 㢛 㭺 䄋 䉷 䕾 䛳 䣍 䲓 䶫 䶮
업 業 嶪 鄴 嶫 业 㡤 澲 邺 殗 餣 驜 鸈 㗼 㡋 㪑 㱉 㸣 䁆 䌜 䎨 䤶 䧨 䱒 䲜
엇 旕
엉 㫈
에 恚 曀 饐 殪 㤬
엔 円
여 女 如 餘 與 余 汝 旅 予 麗 輿 勵 呂 廬 礪 驪 閭 黎 濾 歟 轝 茹 璵 艅 礖 舁 与 欤 畬 洳 旟 䂊 蕷
역 力 亦 易 逆 歷 役 驛 譯 曆 域 疫 繹 轢 斁 嶧 懌 鬩 閾 淢 埸 蜮 晹 㴒 湙 帟 棫 燡 睪 鶂 坄 歝 琙
연 年 然 連 煙 練 硏 鍊 宴 延 緣 演 憐 戀 聯 蓮 燃 軟 淵 燕 沿 鉛 咽 衍 捐 硯 漣 姸 筵 輦 懦 椽 鳶
여 女 如 餘 與 余 汝 旅 予 麗 輿 勵 呂 廬 礪 驪 閭 黎 濾 歟 轝 茹 璵 艅 礖 舁 与 欤 畬 洳 旟 䂊 蕷 鴽 伃 妤 澦 籹 馀 滪 畲 舆 蓣 铷 侞 嬩 忬 悆 懙 桇 櫲 狳 玙 畭 硢 礜 穥 筎 籅 肗 蕠 邚 銣 雓 鮽 鸒 㒜 㦛 㶛 㺞 㼂 㾒 䂛 䑂
역 力 亦 易 逆 歷 役 驛 譯 曆 域 疫 繹 轢 斁 嶧 懌 鬩 閾 淢 埸 蜮 晹 㴒 湙 帟 棫 燡 睪 鶂 坄 歝 琙 魊 峄 怿 绎 訳 译 阈 駅 驿 鹝 鹟 伇 圛 垼 墿 屰 戫 曎 棭 炈 痬 硛 緎 縌 罭 虉 豛 醳 鈠 鯣 鶃 鷊 㑊 㘁 㚜 㣂 㴁 㶠 㻛 㽣 䍞 䓈 䓪 䖌 䡛 䦴 䧕 䭞 䮙 䰥 䳬
연 年 然 連 煙 練 硏 鍊 宴 延 緣 演 憐 戀 聯 蓮 燃 軟 淵 燕 沿 鉛 咽 衍 捐 硯 漣 姸 筵 輦 懦 椽 鳶 撚 煉 烟 涓 璉 沇 娟 涎 堧 嚥 挻 秊 縯 讌 臙 莚 吮 掾 悁 兗 埏 渷 蠕 鷰 嬿 囦 曣 櫞 蜵 葕 研 兖 缘 壖 蜒 輭 鈆 碝 㮒 愞 耎 綖 鋋 妍 珚 縁 㳂 肰 蜎 蝝 㳙 撋 抁 灁 瑌 瓀 礝 醼 䂩 橼 渊 砚 软 铅 鸢 剈 嘫 噮 姢 娫 嬊 嬽 揅 昖 棩 椼 樮 橪 渁 渆 渕 溎 烻 狿 緛 繎 肙 胭 蔅 裫 觾 蹨 轋 郔 酀 驠
열 說 熱 列 悅 烈 劣 裂 閱 咽 涅 爇 噎 拽 揑 潱 閲 恱 湼 阅 悦 热 蠮 㖶 䆕 䆢 䊦 䭇
염 念 炎 鹽 染 廉 厭 閻 焰 簾 艶 殮 苒 髥 琰 剡 捻 枏 錟 塩 冉 灎 饜 魘 灩 靨 黶 扊 檿 焱 懕 檶 釅
엽 葉 獵 燁 曄 叶 揲 楪 魘 爗 熀 靨 曅 饁 瞱 曗 擛 晔 烨 靥 馌 偞 僷 嚈 擪 擫 枼 枽 殜 皣 瞸 鐷 㒯
염 念 炎 鹽 染 廉 厭 閻 焰 簾 艶 殮 苒 髥 琰 剡 捻 枏 錟 塩 冉 灎 饜 魘 灩 靨 黶 扊 檿 焱 懕 檶 釅 燄 髯 曕 猒 㷔 袡 豔 灧 偣 冄 厴 呥 嬮 蚦 䀋 艳 厌 厣 恹 殓 滟 盐 酽 闫 阎 餍 魇 鲶 黡 乵 壛 夵 姌 媣 懨 抩 敥 棪 櫩 灔 焔 珃 艷 蒅 蚺 衻 豓 酓 醶 閆 鯰 㚩 㦔 㱘 㶄 㷋 㷺 㿕 䅧 䌪 䎃 䎦 䒣 䛁 䣸 䤡 䦲 䫇 䶲
엽 葉 獵 燁 曄 叶 揲 楪 魘 爗 熀 靨 曅 饁 瞱 曗 擛 晔 烨 靥 馌 偞 僷 嚈 擪 擫 枼 枽 殜 皣 瞸 鐷 㒯 㩎 㷸 䈎 䭎 䭟
엿 㖲 㖳
영 永 英 令 領 榮 迎 營 寧 靈 嶺 影 詠 映 泳 零 盈 塋 鈴 瑛 玲 囹 暎 穎 纓 瑩 聆 怜 楹 瀛 潁 嶸 霙
예 禮 例 藝 豫 譽 銳 隷 預 睿 泄 醴 濊 芮 詣 曳 裔 穢 洩 刈 乂 倪 霓 猊 汭 蘂 叡 隸 翳 繄 睨 蕊 鯢
오 五 午 惡 吾 誤 烏 悟 吳 嗚 汚 娛 傲 奧 梧 墺 伍 寤 懊 禑 澳 鰲 鼇 晤 敖 塢 熬 獒 筽 蜈 俉 旿 忤
영 永 英 令 領 榮 迎 營 寧 靈 嶺 影 詠 映 泳 零 盈 塋 鈴 瑛 玲 囹 暎 穎 纓 瑩 聆 怜 楹 瀛 潁 嶸 霙 獰 濚 渶 瀯 羚 煐 瓔 鍈 嬰 咏 贏 縈 嬴 郢 癭 韺 浧 濴 碤 蠑 营 営 嵘 栐 禜 攖 頴 灜 覮 籝 籯 偀 媖 溋 濙 瀴 蘡 婴 撄 栄 狞 璎 瘿 绬 缨 萦 蝾 赢 锳 颍 颕 颖 嬫 孆 孾 巆 巊 廮 怺 愥 摬 攍 攚 朠 柍 柡 梬 楧 櫿 溁 爃 璄 盁 矨 礯 緓 萾 蝧 蠳 謍 賏 醟 鐛 鑍 鶧 㘇 㜲 㡕 㢍 㨕 㮠 㲟 㵬 㶈 㿘 䀴
예 禮 例 藝 豫 譽 銳 隷 預 睿 泄 醴 濊 芮 詣 曳 裔 穢 洩 刈 乂 倪 霓 猊 汭 蘂 叡 隸 翳 繄 睨 蕊 鯢 獩 曀 蛻 枘 拽 蜺 薉 鷖 蕋 蚋 囈 瞖 麑 嫛 惢 掜 苅 瘞 羿 勩 蜹 曵 䓲 䛖 輗 蓺 貎 䃜 埶 堄 晲 㡼 枍 瑿 蘙 觬 鋭 㙯 誉 锐 勚 呓 瘗 秽 艺 诣 预 鲵 鹥 兿 呭 壡 婗 嫕 寱 帠 怈 悘 捙 栧 棿 槸 橤 殹 淣 玴 瘱 秇 繠 蘃 袣 褹 襼 詍 讛 跇 踅 郳 鏏 靾 饖 黳 齯 㖂 㙠 㙪 㛳 㜒 㝣 㡫 㤤 㨅 㪒 㪫 㲊 㲼
오 五 午 惡 吾 誤 烏 悟 吳 嗚 汚 娛 傲 奧 梧 墺 伍 寤 懊 禑 澳 鰲 鼇 晤 敖 塢 熬 獒 筽 蜈 俉 旿 忤 嗷 媪 汙 遨 鏖 襖 迃 驁 媼 窹 迕 鼯 聱 唔 慠 謷 隩 捂 鏊 仵 俣 圬 噁 嫯 茣 杇 悞 䫨 吴 污 牾 呉 洿 厫 噢 鄔 嗸 扷 浯 螯 廒 璈 㹳 圫 嵨 嶅 擙 珸 祦 郚 鏕 䎸 磝 鋘 隖 鷔 㥿 鳌 乌 呜 坞 奥 娱 岙 悮 袄 误 钨 骜 乄 啎 埡 奡 娪 娯 嶴 弙 忢 摀 摮 歍 汻 洖 溩 滶 澚 焐 熓 爊 獓 玝 瑦 痦 窏 繺 肟
옥 玉 屋 獄 沃 鈺 鋈 狱 莹 钰 剭 媉 砡 箼 軉 鳿 㓇 䑁 䞝
온 溫 穩 蘊 縕 瘟 瑥 醞 慍 氳 韞 媼 昷 薀 轀 熅 饂 鰮 温 愠 藴 蕰 氲 醖 馧 鞰 榲 蒕 塭 瘒 鎾 蕴 酝
온 溫 穩 蘊 縕 瘟 瑥 醞 慍 氳 韞 媼 昷 薀 轀 熅 饂 鰮 温 愠 藴 蕰 氲 醖 馧 鞰 榲 蒕 塭 瘒 鎾 蕴 酝 揾 榅 煴 缊 辒 韫 鳁 搵 桽 殟 緼 蒀 褞 豱 輼 闦 鰛 㝧 㬈 㼔 䅱 䦟 䦷 䭓 媪 稳
올 兀 膃 嗢 腽 杌 卼 屼 扤 矹 㐏 乯 䪲 淴 瞃 阢 靰 鼿 齀 㐚 㐳 㽾 䑢 䦍
옹 翁 擁 雍 壅 甕 邕 瓮 饔 癰 顒 喁 蓊 雝 滃 罋 禺 廱 癕 痈 臃 䩺 㙲 㝘 拥 颙 勜 嗈 嗡 噰 塕 嵡 暡
와 臥 瓦 訛 蝸 渦 蛙 窩 鍋 窪 娃 洼 哇 媧 涴 譌 萵 囮 枙 窊 婐 猧 卧 鼃 吪 䨟 捰 漥 鈋 涡 窝 莴 蜗
옹 翁 擁 雍 壅 甕 邕 瓮 饔 癰 顒 喁 蓊 雝 滃 罋 禺 廱 癕 痈 臃 䩺 㙲 㝘 拥 颙 勜 嗈 嗡 噰 塕 嵡 暡 澭 灉 瞈 聬 蕹 螉 郺 鎓 鰅 鶲 齆 㘢 㜉 㢕 㨣 㮬 㴩 㷏 㺋 㻾 㽫 㿈 䈵 䐥 䔨 䗸 䱵
와 臥 瓦 訛 蝸 渦 蛙 窩 鍋 窪 娃 洼 哇 媧 涴 譌 萵 囮 枙 窊 婐 猧 卧 鼃 吪 䨟 捰 漥 鈋 涡 窝 莴 蜗 讹 佤 咓 唩 搲 攨 涹 溛 畖 砙 迗 邷 頋 㘥 㛂 㧚 㹻 㼘 䂺 䆧 䠚 䰀 䵷
왁 䢲
완 完 緩 莞 頑 宛 婉 阮 琓 玩 浣 腕 翫 椀 琬 碗 梡 脘 豌 惋 岏 蜿 盌 刓 涴 忨 垸 妧 薍 剜 抏 輐 园
완 完 緩 莞 頑 宛 婉 阮 琓 玩 浣 腕 翫 椀 琬 碗 梡 脘 豌 惋 岏 蜿 盌 刓 涴 忨 垸 妧 薍 剜 抏 輐 园 缓 顽 唍 啘 埦 婠 嵈 帵 捖 捥 烷 睕 羱 萖 貦 贃 㘤 㛡 㝴 㣪 㱧 㻨 㼝 䖾 䛃 䝹 䯈 䯛 鋺 杬
왈 曰 刖 嗗 聉 䚴
왕 王 往 旺 汪 枉 尫 迬 瀇 徃 尢 仼 㲿 尣 尩 尪 彺 忹 暀 蚟 㑌 㒬 㞷 㳝 㳹 㴏 䤑 䶭
왜 倭 矮 娃 歪 媧 騧 娲 劸 緺 躷 㗏 㰪 䶐
외 外 畏 猥 巍 嵬 聵 煨 隗 磈 偎 崴 碨 嵔 渨 隈 廆 桅 椳 詴 懀 揋 腲 聩 鳂 峞 徻 愄 溾 瘣 葨 鍡 鮠
외 外 畏 猥 巍 嵬 聵 煨 隗 磈 偎 崴 碨 嵔 渨 隈 廆 桅 椳 詴 懀 揋 腲 聩 鳂 峞 徻 愄 溾 瘣 葨 鍡 鮠 鰃 㙗 㛱 㞇 㟴 㠕 䃬 䋿 䠿 䫥
욋 夞
요 要 樂 料 了 謠 僚 遙 陶 搖 腰 遼 堯 妖 耀 療 曜 姚 尿 擾 撓 饒 邀 夭 寮 僥 燎 窈 咬 凹 拗 窯 寥
요 要 樂 料 了 謠 僚 遙 陶 搖 腰 遼 堯 妖 耀 療 曜 姚 尿 擾 撓 饒 邀 夭 寮 僥 燎 窈 咬 凹 拗 窯 寥 繞 瑤 蓼 嶢 繇 橈 燿 蟯 幺 徭 澆 徼 殀 軺 蕘 遶 窅 嚙 坳 鷂 祅 穾 銚 墝 偠 喓 嬈 么 揺 謡 摇 窑 瑶 窰 颻 㴭 尭 傜 宎 袎 溔 騕 䙅 窔 䁏 䆗 嶤 珧 芺 葽 訞 㨱 䁘 䢣 䯚 谣 侥 娆 尧 峣 挠 桡 浇 绕 荛 蛲 遥 飖 饶 鳐 鹞 仸 吆 嗂 垇 垚 垰 婹 媱 嫑 岆 崾 徺 愮 憿 抝 暚 枖 柪 柼 梎 楆 榚 榣 烑 熎
욕 欲 浴 辱 慾 褥 縟 溽 蓐 鵒 鄏 昙 缛 鹆 嗕 媷 峪 輍 鋊 㦺
용 用 容 勇 龍 庸 踊 鏞 茸 鎔 傭 溶 瑢 熔 聳 湧 蓉 涌 冗 墉 俑 甬 慂 榕 埇 舂 慵 宂 踴 憃 嵱 筩 硧
우 右 牛 雨 友 又 宇 于 尤 憂 遇 愚 優 羽 郵 偶 佑 祐 禹 迂 虞 寓 隅 禑 雩 紆 瑀 盂 旴 亐 芋 藕 玗
용 用 容 勇 龍 庸 踊 鏞 茸 鎔 傭 溶 瑢 熔 聳 湧 蓉 涌 冗 墉 俑 甬 慂 榕 埇 舂 慵 宂 踴 憃 嵱 筩 硧 蛹 傛 槦 佣 鄘 㼸 牗 摏 牅 髶 蹖 䢇 耸 镕 镛 鲬 鳙 傇 勈 塎 媶 嫆 嫞 彮 恿 悀 愑 愹 搈 搑 榵 氄 滽 砽 穁 穃 縙 苚 褣 軵 鯒 鰫 鱅 鴪 鷛 㐯 㛚 㝐 㟾 㣑 㦶 㦷 㫪 㯴 㲝 㲨 㶲 㺎 䄾 䇀 䇯 䈶 䗤 䞻 䠜 䡆 䡥 䢆 䤊 䧡 䩸
우 右 牛 雨 友 又 宇 于 尤 憂 遇 愚 優 羽 郵 偶 佑 祐 禹 迂 虞 寓 隅 禑 雩 紆 瑀 盂 旴 亐 芋 藕 玗 嵎 釪 吁 亏 堣 耦 踽 盱 喁 竽 俁 迃 疣 耰 禺 麀 麌 庽 杅 鍝 俣 亴 偊 吽 謣 齲 訏 圩 穻 㝢 塸 櫌 苃 嚘 懮 訧 㘾 㚥 䁱 噳 扜 楀 瀀 纋 肬 腢 㤑 㷒 䋅 䨞 叹 优 忧 怄 扰 犹 纡 邮 鱿 龋 吘 媀 峟 怣 慪 扝 挧 桙 櫙 歶 汼 沋 湡 澞 牜 獶 盓 祤 翢 芌 荢 萭 蕅 虶 衧 訦 邘 鄅 鄾 酑 鏂 陓 駀 骬 髃 魷
욱 旭 郁 煜 頊 昱 勖 澳 彧 栯 稶 燠 隩 勗 䤋 薁 稢 㮋 顼 喐 旮 朂 礇 㤢 㦽 㰲 䉛 䜡
운 雲 運 云 韻 芸 暈 隕 殞 耘 蕓 澐 橒 熉 紜 韵 惲 沄 霣 篔 餫 鄆 鄖 溳 夽 愪 抎 縜 韗 䩵 殒 恽 筼
운 雲 運 云 韻 芸 暈 隕 殞 耘 蕓 澐 橒 熉 紜 韵 惲 沄 霣 篔 餫 鄆 鄖 溳 夽 愪 抎 縜 韗 䩵 殒 恽 筼 纭 运 郓 郧 陨 喗 囩 妘 枟 涢 眃 磒 秐 繧 耺 腪 蒷 蝹 賱 齳 㚃 㛣 㜏 㟦 㩈 㻒 䆬 䇖 䉙 䚋 䞫 䢵 䨶 䫟 䲰 員
울 鬱 蔚 亐 熨 欝 㭗 菀 鬰 爩 黦 䁌 欎 灪 㐛 㠨 䖇 䵥
웅 雄 熊 䧺 僌 㞲 㷱
원 元 原 遠 園 願 圓 怨 院 員 源 援 苑 瑗 袁 媛 寃 猿 鴛 阮 轅 垣 爰 愿 琬 湲 沅 嫄 洹 円 冤 圜 鵷
월 月 越 鉞 粤 刖 粵 樾 軏 戉 抈 狘 钺 钥 仴 岄 怴 枂 泧 蚎 蚏 跀 鈅 㜧 㞽 㳉 䄴 䋐 䎳 䞲 䟠 䡇 䢁
위 位 危 爲 威 偉 謂 衛 委 慰 圍 僞 胃 違 倭 緯 尉 魏 蔚 渭 韋 萎 瑋 葦 蝟 暐 褘 蔿 為 諉 闈 衞 逶
유 有 流 由 油 留 猶 柳 遺 遊 酉 唯 幼 柔 惟 類 裕 悠 誘 乳 儒 維 愈 幽 劉 兪 踰 庾 楡 紐 癒 諭 游
원 元 原 遠 園 願 圓 怨 院 員 源 援 苑 瑗 袁 媛 寃 猿 鴛 阮 轅 垣 爰 愿 琬 湲 沅 嫄 洹 円 冤 圜 鵷 蜿 黿 謜 猨 杬 楥 綩 芫 薗 鋺 騵 貟 逺 畹 晼 圎 榞 㤪 㥳 䲶 夘 眢 踠 夗 溒 茒 薳 邍 鶢 䩊 䲮 员 圆 诨 贠 辕 远 鸳 鹓 鼋 䓕 倇 傆 傊 厡 厵 塬 妴 媴 惌 朊 榬 獂 盶 禐 笎 箢 葾 蒝 蒬 蚖 蝯 螈 衏 褑 褤 諢 豲 邧 酛 鈨 鎱 闧 顐 駌 魭 鶰 㐾 㝨 㟲 㟶 㠾 㥐 㨬 㷧 㹉 㽜 䅈 䈠 䏍 䑱 䕂 䖠 䖤 䗕 䘼
월 月 越 鉞 粤 刖 粵 樾 軏 戉 抈 狘 钺 钥 仴 岄 怴 枂 泧 蚎 蚏 跀 鈅 㜧 㞽 㳉 䄴 䋐 䎳 䞲 䟠 䡇 䢁 䤦 䬂
위 位 危 爲 威 偉 謂 衛 委 慰 圍 僞 胃 違 倭 緯 尉 魏 蔚 渭 韋 萎 瑋 葦 蝟 暐 褘 蔿 為 諉 闈 衞 逶 喟 韙 痿 葳 撝 煒 幃 熨 韡 餧 潙 骪 喂 猬 偽 㙔 蘤 骫 罻 蝛 躗 㗸 徫 湋 硊 縅 蜲 䦱 嶎 愇 藯 躛 頠 骩 䘙 为 伪 伟 卫 囲 围 帏 涠 炜 玮 纬 苇 袆 诿 谓 违 闱 韦 韪 儰 叞 喡 喴 噅 噕 墛 媁 媙 媦 孬 寪 峗 崣 楲 洈 潿 煟 熭 犚 犩 緭 蒍 蘶 螱 蟡 褽 覣 讆 讏 贀 踒 鄬 鍏 隇 霨 韑 韚 颹 餵 鰄 鳚
유 有 流 由 油 留 猶 柳 遺 遊 酉 唯 幼 柔 惟 類 裕 悠 誘 乳 儒 維 愈 幽 劉 兪 踰 庾 楡 紐 癒 諭 游 宥 喩 溜 諛 鍮 琉 愉 柚 蹂 揄 攸 猷 綏 逾 濡 孺 臾 瑜 侑 硫 杻 繇 萸 洧 楢 釉 籲 帷 牖 壝 囿 腴 逌 莠 糅 揉 蕕 蕤 窬 黝 瘐 緌 窳 讉 呦 鮪 蠕 褕 泑 冘 蚴 蝤 鼬 斿 瘉 蚰 鞣 龥 襦 俞 覦 喻 嚅 嵛 㓜 褎 輶 蝣 褏 婾 曘 蜼 㳺 浟 㽔 䛕 擩 槱 羑 毹 狖 薷 逰 濰 隃 媮 滺 㽥 卣 痏 婑 楺 渪 甤 㺄 䘱
육 六 肉 育 陸 戮 堉 毓 儥 淯 錥 哊 唷 喅 宍 棛 焴 蒮 蘛 蜟 袬 逳 㣃 㥔 㻙 䋭 䘻
윤 倫 輪 尹 允 潤 閏 崙 胤 鈗 淪 贇 勻 畇 奫 玧 昀 鋆 酳 閠 狁 蝡 㣧 阭 润 赟 闰 橍 膶 荺 蜳 馻 㠈
윤 倫 輪 尹 允 潤 閏 崙 胤 鈗 淪 贇 勻 畇 奫 玧 昀 鋆 酳 閠 狁 蝡 㣧 阭 润 赟 闰 橍 膶 荺 蜳 馻 㠈 㽙 䤞 䦞 䪳 沇
율 律 率 栗 慄 聿 潏 矞 茟 䢖 欥 鴥 噊 燏 繘 銉 驈 鱊 鴧 㐕 㐠 㯨 䋖 䫻 䮇 汩
융 隆 融 戎 絨 瀜 狨 羢 漋 娀 㭜 肜 駥 绒 曧 毧 茙 螎 䘬
은 銀 恩 隱 殷 誾 垠 圻 慇 訔 听 憖 嚚 狺 溵 檼 癮 垽 鄞 圁 珢 齗 齦 隠 隐 穏 稳 垦 㤙 檃 㥯 櫽 泿
은 銀 恩 隱 殷 誾 垠 圻 慇 訔 听 憖 嚚 狺 溵 檼 癮 垽 鄞 圁 珢 齗 齦 隠 隐 穏 稳 垦 㤙 檃 㥯 櫽 泿 慭 濦 磤 㶏 憗 懚 煾 犾 蒑 瘾 訚 银 龂 乚 嗯 峎 峾 嶾 摁 斦 檭 猌 蒽 蘟 讔 㐆 㒚 㕶 㙬 㡥 㥼 㦩 㴈 㹜 㹞 䅰 䇵 䌥 䓄 䖐 䖜 䨸 䭡 䴦 訢
을 乙 鳦 圪 钇 釔 䎲
음 音 飮 陰 吟 淫 蔭 喑 愔 霪 崟 廕 隂 䕃 飲 瘖 婬 霠 阥 霒 荫 阴 饮 乑 侌 噖 噾 崯 摿 淾 烎 癊 碒
음 音 飮 陰 吟 淫 蔭 喑 愔 霪 崟 廕 隂 䕃 飲 瘖 婬 霠 阥 霒 荫 阴 饮 乑 侌 噖 噾 崯 摿 淾 烎 癊 碒 窨 苂 荶 訡 趛 鈝 韾 鷣 㕂 㖗 㞤 㱃 㸒 䚿 䜾 䤃 䨙 䨧 䪩
읍 邑 泣 揖 挹 悒 浥 裛 湆 䭂 俋 唈 歞 湇 㘊 㱞 㴔 㵫 䇼 䉗 䓃 䔱
응 應 凝 鷹 膺 譍 应 応 鹰 噟 軈 㒣 㣹 㶐 㶝 䧹
의 衣 意 義 依 醫 矣 議 宜 儀 疑 誼 擬 毅 椅 縊 倚 懿 蟻 薏 艤 錡 猗 嶷 欹 漪 儗 劓 螘 饐 凒 礒 医
이 二 耳 里 利 理 異 以 而 李 已 移 易 吏 離 履 裏 梨 伊 夷 泥 珥 弛 貳 怡 罹 餌 裡 痍 爾 痢 姨 貽
익 益 翼 翊 溺 翌 匿 謚 瀷 弋 鷁 熤 榏 釴 杙 嗌 艗 黓 䄩 谥 鹢 浳 熼 獈 穓 膉 芅 蛡 隿 霬 骮 齸 㔴
인 人 仁 因 引 印 寅 忍 認 鄰 隣 姻 咽 麟 刃 湮 鱗 吝 燐 蚓 靭 璘 茵 絪 靷 藺 堙 仞 禋 夤 氤 裀 婣
일 一 日 逸 壹 鎰 佾 溢 佚 馹 軼 泆 釰 皼 衵 壱 轶 镒 驲 劮 呹 囸 夁 嬄 弌 浂 燚 辷 鈤 鴩 鷧 㳑 䒤
임 林 壬 任 臨 賃 姙 淋 稔 荏 恁 妊 衽 飪 絍 銋 袵 餁 紝 棯 腍 鈓 㶵 䄒 临 纴 赁 饪 栠 栣 祍 秹 菍
의 衣 意 義 依 醫 矣 議 宜 儀 疑 誼 擬 毅 椅 縊 倚 懿 蟻 薏 艤 錡 猗 嶷 欹 漪 儗 劓 螘 饐 凒 礒 医 义 冝 扆 顗 宐 檥 毉 竩 㼁 禕 旖 犄 薿 㠜 㦤 譩 㩘 䝘 䴊 仪 拟 祎 蚁 议 谊 铱 镱 饻 亄 偯 吚 嬑 嬟 孴 寲 嶬 庡 撎 旑 椬 檹 燱 狋 畩 稦 籎 羛 萓 藙 螠 衤 觺 譺 豙 輢 轙 逘 郼 醷 鈘 銥 鐿 顡 餏 鷾 鸃 㓷 㕈 㕒 㘈 㛄 㝖 㠖 㢊 㥋 㦉 㫊 㬾 㱅 㱲 㳖 㽈 㾨 㿲 䉝 䉨 䒾 䓹 䝝 䣡 䧇 䧧 䪰 䫑 䫯 䬥
이 二 耳 里 利 理 異 以 而 李 已 移 易 吏 離 履 裏 梨 伊 夷 泥 珥 弛 貳 怡 罹 餌 裡 痍 爾 痢 姨 貽 邇 彛 肄 苡 飴 荑 彝 迤 訑 詑 洟 咿 珆 栮 尔 隶 鮧 坨 頉 頤 羡 尒 异 圯 薾 迩 儞 匜 貮 詒 迆 洱 眤 輀 杝 敡 眙 貤 轜 尓 暆 柂 駬 彜 洏 彞 栭 酏 頥 羠 苢 蛦 黟 佁 刵 崺 弍 扅 攺 樲 潩 眱 眲 綛 胹 蛜 衈 謻 趰 迻 髵 㑆 㕥 㠯 䔬 弐 诒 贰 贻 铒 颐 饴 饵 鲕 鸸 乁 伿 佴 侇 侕 咡 咦 媐 宧 峏 峓
익 益 翼 翊 溺 翌 匿 謚 瀷 弋 鷁 熤 榏 釴 杙 嗌 艗 黓 䄩 谥 鹢 浳 熼 獈 穓 膉 芅 蛡 隿 霬 骮 齸 㔴 㚤 㜋 㢞 䋚 䌻 䘝 䣧 䯆 䴬
인 人 仁 因 引 印 寅 忍 認 鄰 隣 姻 咽 麟 刃 湮 鱗 吝 燐 蚓 靭 璘 茵 絪 靷 藺 堙 仞 禋 夤 氤 裀 婣 洇 扨 籾 芢 訒 軔 儿 囙 紉 闉 戭 牣 諲 韌 鏔 駰 䄄 垔 湚 釼 陻 㣼 仭 欭 歅 璌 筃 螾 纫 认 讱 轫 铟 韧 骃 亻 凐 刄 囜 屻 岃 廴 忈 忎 朄 朲 杒 梕 殥 洕 濥 秂 秵 粌 緸 肕 茚 荵 蔩 躵 鈏 銦 靱 鞇 魜 鮣 㘻 㝙 㠴 㧈 㧢 㪦 㲽 㸾 㽼 䀔 䀕 䃌 䇙 䍰 䏖 䏰 䑒 䒡 䓰 䧣 䲟
일 一 日 逸 壹 鎰 佾 溢 佚 馹 軼 泆 釰 皼 衵 壱 轶 镒 驲 劮 呹 囸 夁 嬄 弌 浂 燚 辷 鈤 鴩 鷧 㳑 䒤 䭿 䳀
임 林 壬 任 臨 賃 姙 淋 稔 荏 恁 妊 衽 飪 絍 銋 袵 餁 紝 棯 腍 鈓 㶵 䄒 临 纴 赁 饪 栠 栣 祍 秹 菍 軠 鵀 㤛 䇮 䋕 䏕 䚾 䛘 䭃
입 入 立 笠 粒 卄 廿 込 圦 扖 杁 魞 鳰
잇 㗡
잉 剩 孕 仍 芿 媵 剰 賸 礽 陾 䞉 扔 膡 艿 辸 㑞 㚺 㞌 㭁 㺱 䄧 䚮 䵴
자 子 自 者 字 姉 慈 資 姿 玆 紫 恣 刺 滋 疵 雌 磁 炙 諮 藉 煮 仔 蔗 瓷 炸 咨 孜 茨 薺 茲 貲 眦 孶
작 作 昨 爵 酌 杓 灼 雀 鵲 綽 勺 嚼 芍 醋 炸 斫 酢 怍 焯 碏 柞 岝 汋 犳 斱 妁 鷟 婥 爝 皭 圴 扚 禚
잔 殘 盞 棧 孱 潺 戔 剗 驏 残 醆 僝 刬 戋 栈 盏 骣 嶘 拃 桟 潹 琖 菚 虥 虦 輚 轏 㟞 㥇 㮍 㱚 㻵 䎒
자 子 自 者 字 姉 慈 資 姿 玆 紫 恣 刺 滋 疵 雌 磁 炙 諮 藉 煮 仔 蔗 瓷 炸 咨 孜 茨 薺 茲 貲 眦 孶 訾 赭 粢 髭 觜 泚 柘 耔 鷓 眥 鶿 牸 胾 呰 鎡 莿 鮓 嬨 孖 茈 虸 蚱 頿 兹 姊 扻 煑 趦 朿 胔 趑 餈 孳 嵫 庛 訿 澬 㧗 榨 杍 啫 栥 秭 稵 㹀 䵭 呲 姕 橴 笫 骴 䄍 䏑 䳄 鹚 蚝 谘 赀 资 锌 镃 鲊 鲝 鹧 刾 吇 啙 嗞 垐 塖 宱 樜 毑 溂 溠 濨 熫 玼 珁 甆 痄 矷 礠 禌 秄 秶 籽 糍 緕 纃 胏 芓 茊 茡 薋 蛓
작 作 昨 爵 酌 杓 灼 雀 鵲 綽 勺 嚼 芍 醋 炸 斫 酢 怍 焯 碏 柞 岝 汋 犳 斱 妁 鷟 婥 爝 皭 圴 扚 禚 筰 䱜 猎 绰 鹊 仢 妰 岞 烵 焳 熦 爑 皵 秨 稓 穱 繛 苲 莋 謶 趞 鈼 鐯 靍 飵 㑅 㘀 㤰 㩱 㪕 㬭 㸲 㹱 䅵 䇎 䋏 䎞 䎰 䝫 䞢 䦃 䧿 䲵 舃
잔 殘 盞 棧 孱 潺 戔 剗 驏 残 醆 僝 刬 戋 栈 盏 骣 嶘 拃 桟 潹 琖 菚 虥 虦 輚 轏 㟞 㥇 㮍 㱚 㻵 䎒 䏼 䗃 䙁 䝳 䩆 䱠 䴼
잘 囐 乽
잠 潛 暫 蠶 箴 簪 湛 岑 潜 蚕 涔 梣 笒 濳 蹔 蘸 埁 簮 熸 篸 賺 撏 蠺 挦 暂 臜 赚 兂 喒 寁 揝 撍 昝
잡 雜 閘 襍 磼 霅 卡 囃 眨 匝 迊 雑 帀 啑 煠 咂 币 杂 咔 嘁 抸 拤 沞 疀 砸 譗 鉔 雥 韴 㧜 㬁 䕹 䙄
잠 潛 暫 蠶 箴 簪 湛 岑 潜 蚕 涔 梣 笒 濳 蹔 蘸 埁 簮 熸 篸 賺 撏 蠺 挦 暂 臜 赚 兂 喒 寁 揝 撍 昝 硶 臢 蝅 譧 趈 鐕 㔆 㞥 㟛 㣅 㺘 㻸 䁮 䃡 䅾 䍼 䐶 䗝 䗞 䘉 䟅 䣟 䣠 䤐 䫈 䭕 䰼 䲋 䳥
잡 雜 閘 襍 磼 霅 卡 囃 眨 匝 迊 雑 帀 啑 煠 咂 币 杂 咔 嘁 抸 拤 沞 疀 砸 譗 鉔 雥 韴 㧜 㬁 䕹 䙄 䞙 䨿 䪞 䵽
잣 㗯
장 長 場 將 章 壯 狀 張 掌 丈 腸 帳 障 獎 葬 藏 莊 墻 裝 粧 臟 匠 庄 璋 蔣 樟 杖 仗 獐 醬 漿 檣 薔
재 在 才 材 再 財 哉 栽 載 災 裁 宰 滓 齋 梓 縡 渽 齎 灾 纔 菑 榟 夈 扗 崽 賫 㢤 䝴 賷 㦲 捚 烖 賳
쟁 爭 錚 諍 箏 崢 鎗 瞠 鐺 琤 猙 争 峥 筝 掁 崝 掙 䋫 挣 狰 诤 铮 噌 崡 棦 碀 趟 鏳 鏿 饓 鬇 㓌 㬹
저 低 貯 諸 著 底 抵 沮 躇 猪 邸 咀 箸 詛 狙 苧 儲 楮 佇 這 渚 紵 杵 疽 雎 樗 菹 姐 觝 藷 齟 詆 氐
적 赤 的 敵 適 賊 積 籍 跡 績 摘 寂 滴 蹟 笛 炙 迹 嫡 藉 謫 狄 吊 迪 勣 翟 鏑 荻 糴 逖 覿 幘 磧 樀
전 田 前 全 電 戰 展 傳 典 錢 專 轉 殿 顚 甸 悛 奠 箭 銓 箋 塡 餞 篆 纏 剪 煎 廛 氈 輾 顫 澱 癲 栓
절 節 絶 切 折 竊 截 浙 癤 窃 軼 晢 岊 卩 晣 蕝 嶻 絕 㔾 绝 棁 疖 节 卪 尐 幯 撧 擮 棳 楶 沏 聺 苆
점 店 點 占 漸 粘 霑 点 拈 岾 鮎 玷 覘 佔 簟 苫 墊 黏 颭 蔪 笘 蛅 坫 阽 痁 扂 蒧 㸃 磹 鲇 垫 渐 觇
장 長 場 將 章 壯 狀 張 掌 丈 腸 帳 障 獎 葬 藏 莊 墻 裝 粧 臟 匠 庄 璋 蔣 樟 杖 仗 獐 醬 漿 檣 薔 奬 臧 贓 牆 欌 暲 将 装 戕 瘴 嶂 鏘 糚 漳 萇 奘 賬 麞 牂 妝 嬙 廧 橦 鄣 傽 羘 餦 壮 墙 蔵 荘 膓 奨 臓 贜 塲 髒 駔 槳 慞 嶈 螿 扙 斨 粻 樁 蘠 醤 摪 賍 蹡 奖 脏 赃 场 妆 嫱 帐 张 桨 桩 樯 浆 肠 苌 蒋 蔷 螀 账 酱 锵 长 驵 鳉 丬 仉 仧 兏 匞 匨 塟 墇 墏 壵 娤 嫜 幛 幥 弉 梉 焋 爿 牄 瓺 痮 瘬
재 在 才 材 再 財 哉 栽 載 災 裁 宰 滓 齋 梓 縡 渽 齎 灾 纔 菑 榟 夈 扗 崽 賫 㢤 䝴 賷 㦲 捚 烖 賳 㦳 赍 斋 财 载 龇 侢 傤 儎 喍 斎 洅 溨 睵 酨 釮 鉙 齜 㒲 㗍 㘽 㧳 㩟 㪰 㱰 䍉 䏁 䬩 䮨 䴭 䵧
쟁 爭 錚 諍 箏 崢 鎗 瞠 鐺 琤 猙 争 峥 筝 掁 崝 掙 䋫 挣 狰 诤 铮 噌 崡 棦 碀 趟 鏳 鏿 饓 鬇 㓌 㬹 䍵 䟫 䦛 䦶 䱢
저 低 貯 諸 著 底 抵 沮 躇 猪 邸 咀 箸 詛 狙 苧 儲 楮 佇 這 渚 紵 杵 疽 雎 樗 菹 姐 觝 藷 齟 詆 氐 宁 柢 瀦 苴 杼 袛 潴 牴 蛆 羝 褚 豬 罝 岨 芧 陼 伹 筯 泞 葅 趄 翥 竚 滁 䃴 眡 櫧 篨 詝 袓 怚 抯 摴 疷 砠 禇 㡳 㫝 呧 弤 疧 茋 㤖 伫 储 柠 槠 橥 纻 苎 诅 诋 贮 这 龃 仾 劯 唨 坥 坾 墸 奃 她 媎 庅 拞 掋 櫡 櫫 濐 眝 羜 聜 菧 蕏 藸 蝫 蠩 豠 趆 跙 軧 阺 骶 鯳 鴡 㑏 㓳 㝉 㡹 㪆 㫂 㭬 㭽 㯉 㵭
적 赤 的 敵 適 賊 積 籍 跡 績 摘 寂 滴 蹟 笛 炙 迹 嫡 藉 謫 狄 吊 迪 勣 翟 鏑 荻 糴 逖 覿 幘 磧 樀 菂 馰 踖 啇 篴 靮 廸 頔 苖 逷 耤 籊 擿 讁 墑 䟱 嚁 趯 㰅 㺓 庴 樍 歒 甋 硳 襀 鸐 积 碛 籴 绩 觌 谪 贼 镝 唙 啲 嘀 廭 戝 旳 梑 浾 漃 灻 烾 熵 玓 瓋 癪 聻 肑 葃 蔋 蔐 藡 蠈 豴 躤 鰿 㒀 㠃 㢩 㣙 㭙 㯖 㹍 䁤 䊞 䊮 䚍 䚐 䢰 䣢 䤲 䨀 䨤 䮰 䯼 䰹 䳭 䴞 䵂 䵠
전 田 前 全 電 戰 展 傳 典 錢 專 轉 殿 顚 甸 悛 奠 箭 銓 箋 塡 餞 篆 纏 剪 煎 廛 氈 輾 顫 澱 癲 栓 鐫 詮 雋 佺 佃 鈿 琠 淀 筌 塼 畑 靦 牋 畋 腆 顓 翦 戔 瑱 痊 湔 縳 旃 躔 巓 羶 荃 磚 戩 鸇 吮 甎 邅 囀 飦 澶 揃 栴 屇 餰 鱣 槇 錪 畠 籛 膞 輇 嫥 癜 鄽 鋑 靛 鬋 銭 烇 竱 瑑 戦 跧 戬 笺 闐 剸 填 氊 饘 㙉 橏 倎 纒 膻 譾 顛 淟 沺 搌 瘨 縓 謭 槙 滇 傎 瀍 牷 拴 瑼 硂 窴 篿 䇳 搷 晪 朘 瑔 㕓 㫋
절 節 絶 切 折 竊 截 浙 癤 窃 軼 晢 岊 卩 晣 蕝 嶻 絕 㔾 绝 棁 疖 节 卪 尐 幯 撧 擮 棳 楶 沏 聺 苆 蠘 蠞 蠽 趃 镻 㐉 㑜 㔃 㔢 㗫 㦢 㪿 㮞 㸅 㸞 䁀 䏳 䓆 䕙 䗻 䘁 䟙 䩢 䪼 䫕 䲙
점 店 點 占 漸 粘 霑 点 拈 岾 鮎 玷 覘 佔 簟 苫 墊 黏 颭 蔪 笘 蛅 坫 阽 痁 扂 蒧 㸃 磹 鲇 垫 渐 觇 飐 唸 嚸 奌 惦 掂 煔 秥 胋 螹 袩 誗 踮 鏩 頕 㓠 㝪 㶘 㶣 㼭 䀡 䍄 䘂 䘋 䛸 䟋 䤔 䩇
접 接 蝶 渫 摺 揲 楪 鰈 慴 檝 蹀 跕 椄 蜨 艓 褋 䐑 鲽 挕 擑 聑 菨 襵 㑙 㢎 䐲 䝃 䝕 䪓
정 正 定 丁 政 情 庭 停 精 靜 貞 井 淨 頂 廷 征 程 亭 整 鄭 訂 晶 呈 鼎 旌 挺 禎 偵 汀 楨 艇 珽 靖
제 弟 第 題 諸 祭 除 帝 製 制 濟 提 際 齊 堤 劑 悌 蹄 梯 啼 霽 臍 薺 醍 荑 齎 儕 躋 擠 隄 娣 禔 睇
조 朝 祖 助 鳥 調 造 早 兆 條 照 操 租 弔 潮 組 跳 趙 曺 祚 燥 措 遭 釣 彫 曹 詔 阻 粗 漕 凋 稠 藻
정 正 定 丁 政 情 庭 停 精 靜 貞 井 淨 頂 廷 征 程 亭 整 鄭 訂 晶 呈 鼎 旌 挺 禎 偵 汀 楨 艇 珽 靖 釘 幀 錠 碇 穽 町 酊 睛 炡 霆 菁 渟 淀 晸 綎 鉦 瀞 玎 諪 鯖 檉 姃 柾 湞 鋌 証 灯 筳 靚 酲 梃 怔 叮 棖 莛 婷 琤 珵 疔 鋥 桯 佂 婧 掟 遉 静 净 㝎 鞓 浄 泟 㫌 埥 頲 矴 侹 涏 飣 凈 阱 琔 赬 仃 頳 揁 裎 烶 脡 蜓 愸 睜 誔 㵾 旍 椗 濎 竧 耵 鼮 㘫 㲂 䄇 䅍 䵺 证 侦 帧 枨 柽 桢 浈 睁 祯 蛏 订 贞
제 弟 第 題 諸 祭 除 帝 製 制 濟 提 際 齊 堤 劑 悌 蹄 梯 啼 霽 臍 薺 醍 荑 齎 儕 躋 擠 隄 娣 禔 睇 稊 瑅 鯷 緹 踶 猘 蹏 韲 鍗 鮧 苐 剤 褆 滛 斉 隮 磾 癠 穧 虀 穄 綈 鶗 嚌 淛 傺 鵜 渧 鞮 嗁 懠 僀 泲 漈 璾 偍 偙 徲 摕 蒢 鑇 騠 㫼 䀸 侪 剂 哜 挤 济 済 绨 缇 脐 荠 蛴 诸 跻 锑 际 霁 题 鲚 鳀 鹈 齐 齑 亝 俤 厗 埞 姼 媂 媞 怟 惿 慸 憏 斊 栬 梊 櫅 漽 焍 狾 珶 睼 碮 碲 禵 罤 腣 艩 萕 蕛 蝭 蠐
조 朝 祖 助 鳥 調 造 早 兆 條 照 操 租 弔 潮 組 跳 趙 曺 祚 燥 措 遭 釣 彫 曹 詔 阻 粗 漕 凋 稠 藻 肇 嘲 爪 眺 躁 棗 糟 詛 繰 雕 吊 俎 炤 窕 蚤 槽 晁 璪 噪 皁 祧 徂 胙 糶 竈 厝 刁 絛 譟 鵰 澡 蜩 殂 鼂 臊 嘈 佻 琱 蔦 銚 糙 找 洮 誂 傮 嬥 懆 笊 絩 艚 鈟 鋽 鯛 条 阼 皂 慥 脁 粜 罩 旐 朓 啁 爼 窵 鯈 窱 螬 曌 沠 珇 瞾 縧 蓧 垗 恌 莇 藋 釂 駋 䆴 晀 碉 竃 竨 罺 艁 趮 醩 䝖 䧂 绦 枣 灶 窎 组
족 足 族 簇 鏃 蔟 瘯 哫 镞 踿 㞺 㵀
존 存 尊 拵 鐏 燇 橂 袸 銌
졸 卒 拙 猝 倅 脺 捽 㐒 稡 椊 踤 卆 炪 箤 㐍 㰵 䂐 䘚 䚝 䯿 䱣
종 終 宗 種 從 鐘 縱 腫 琮 綜 踵 踪 慫 鍾 淙 悰 棕 倧 蹤 瘇 螽 椶 蓯 粽 伀 慒 柊 樅 瑽 従 縦 嵸 騣
종 終 宗 種 從 鐘 縱 腫 琮 綜 踵 踪 慫 鍾 淙 悰 棕 倧 蹤 瘇 螽 椶 蓯 粽 伀 慒 柊 樅 瑽 従 縦 嵸 騣 蓗 鬃 賨 踨 徖 忪 倊 尰 朡 籦 糉 緃 緵 衳 鍐 钟 从 怂 枞 疭 纵 终 综 肿 苁 锺 骔 骙 傱 刣 喠 堫 堹 妐 婃 孮 嵏 嵕 嵷 幒 徔 徸 昮 暰 歱 汷 泈 潀 炂 煄 熧 猔 猣 瘲 瞛 碂 磫 稯 糭 緟 翪 腙 艐 葼 蔠 蝬 誴 諥 豵 賩 蹱 銿 錝 騌 騤 鬉 鬷 鯮 鯼 鴤 鶎 鼨 㗰 㙡 㚇 㜡 㡖 㢔 㣫 㣭 㥖 㨑 㯶 㹣 㻜 䁓
좌 左 坐 佐 座 挫 髽 剉 莝 痤 㘴 脞 袏 锉 侳 咗 夎 屮 睉 矬 蓌 蓙 趖 遳 銼 㔫 㘸 㛗 㝾 㟇 㭫 䂳 䟶
죄 罪 辠 嶵 檌 㠑 㪓
주 主 住 走 注 晝 朱 宙 酒 州 周 舟 柱 洲 珠 株 奏 鑄 駐 疇 週 躊 誅 註 紬 做 稠 廚 紂 嗾 呪 輳 冑
주 主 住 走 注 晝 朱 宙 酒 州 周 舟 柱 洲 珠 株 奏 鑄 駐 疇 週 躊 誅 註 紬 做 稠 廚 紂 嗾 呪 輳 冑 胄 籌 湊 綢 澍 炷 蛛 姝 酎 侏 枓 齟 霔 肘 儔 拄 裯 遒 賙 輈 侜 幬 腠 迬 霌 硃 蔟 尌 籒 蛀 詋 丟 趎 鉒 鼄 厨 蹰 躕 譸 籀 黈 㴻 邾 紸 䛆 麈 伷 噣 淍 咮 晭 跦 㕑 椆 珘 唒 妵 揍 㕀 怞 楱 疰 秼 絑 薵 軴 馵 㕏 㡡 㴤 䌧 昼 丢 俦 凑 帱 橱 畴 筹 纣 绸 诛 诪 赒 踌 辀 辏 鋳 铥 铸 驻 鸼 䌷 厾 咒
죽 竹 粥 鬻 俼
준 準 俊 遵 准 浚 峻 駿 埈 濬 晙 樽 竣 蠢 儁 雋 畯 逡 寯 焌 罇 蹲 隼 餕 撙 惷 綧 噂 踆 皴 埻 墫 鐏
준 準 俊 遵 准 浚 峻 駿 埈 濬 晙 樽 竣 蠢 儁 雋 畯 逡 寯 焌 罇 蹲 隼 餕 撙 惷 綧 噂 踆 皴 埻 墫 鐏 鱒 鵔 凖 睃 㕙 䥴 䞭 踳 㑺 㻐 捘 殾 陖 鵕 㡒 馂 骏 鳟 偆 僔 壿 夋 宒 嶟 懏 稕 竴 箺 繜 葰 訰 譐 迿 鎨 鶽 鷷 㑓 㒞 㝦 㷪 㻪 㼱 䐏 䔿 䣩 䮞 純 䜭
줄 茁 乼 崒 崪 窋 笜 㤕
줏 㗟
중 中 重 衆 仲 眾 褈 众 妕 媑 狆 筗 舯 茽 蚛 蝩 衶 迚 鈡 隀 㐺 㑖 㣡 㲴 㴢 䌬 䝦 䦿 䳯
@@ -426,66 +426,66 @@
즐 櫛 騭 喞 瀄 堲 栉 骘 擳 楖 㘉
즘 怎
즙 輯 汁 葺 楫 檝 蕺 濈 湒 霵
증 曾 增 證 贈 症 憎 蒸 拯 烝 甑 繒 証 嶒 矰 罾 曽 増 璔 橧 鄫 熷 驓 䎖 缯 赠 囎 撜 潧 磳 竲 篜 譄
지 地 知 至 志 之 止 紙 持 指 識 支 只 枝 智 池 誌 遲 旨 址 芝 脂 肢 祉 咫 摯 枳 祗 趾 祇 砥 贄 漬
증 曾 增 證 贈 症 憎 蒸 拯 烝 甑 繒 証 嶒 矰 罾 曽 増 璔 橧 鄫 熷 驓 䎖 缯 赠 囎 撜 潧 磳 竲 篜 譄 鱛 㣒 㷥 㽪 䇰 䉕 䒱 䕄 䗀 䙢 䡕 䥌 䥭 䰝
지 地 知 至 志 之 止 紙 持 指 識 支 只 枝 智 池 誌 遲 旨 址 芝 脂 肢 祉 咫 摯 枳 祗 趾 祇 砥 贄 漬 沚 芷 蜘 墀 秪 躓 箎 舐 踟 篪 鷙 榰 坻 扺 軹 泜 阯 痣 鮨 遅 㫖 秖 祬 䘭 輊 胝 鳷 帋 䑛 搘 蚳 厎 墬 鋕 驇 彽 恉 抧 遟 䙙 䞇 䶵 挚 渍 纸 贽 轵 轾 迟 鸷 俧 倁 凪 劧 吱 哋 坁 坔 坘 埊 娡 岻 嶳 忯 杫 栺 梽 椥 汥 汦 洔 淽 潪 疻 砋 秓 竾 筂 筫 箈 綕 胑 舓 衼 覟 觗 訨 謘 貾 赿 酯 馶 鴲 鼅 㕄 㞢
직 直 職 織 稷 稙 禝 溭 㮨 聀 犆 樴 嬂 织 职 膱 蘵 蟙 軄 㞋 㹄 䐈
진 眞 進 盡 辰 陳 振 震 陣 珍 鎭 晉 塵 津 秦 診 嗔 疹 賑 晋 軫 唇 搢 臻 殄 縉 榛 袗 瑨 畛 璡 縝 瞋
질 質 疾 秩 姪 迭 跌 窒 叱 嫉 帙 佚 桎 膣 侄 蛭 瓆 絰 軼 躓 騭 垤 郅 鑕 蒺 咥 耋 晊 抶 銍 袠 瓞 袟
진 眞 進 盡 辰 陳 振 震 陣 珍 鎭 晉 塵 津 秦 診 嗔 疹 賑 晋 軫 唇 搢 臻 殄 縉 榛 袗 瑨 畛 璡 縝 瞋 溱 桭 蔯 趁 儘 瑱 稹 蓁 珒 鬒 侲 鉁 螴 真 禛 鎮 趂 疢 眹 胗 䫃 溍 珎 姫 敶 榗 眕 紾 䀌 搸 濜 薼 螓 轃 辴 㐱 䀼 尽 尘 纼 缙 缜 诊 赈 轸 进 镇 阵 陈 侭 嗪 嚍 堻 塦 壗 嫀 嫃 嬧 屒 帪 弫 抮 挋 敐 昣 枃 樄 樼 殝 浕 獉 琎 瑧 籈 紖 絼 縥 聄 臸 蒖 薽 蜄 裖 誫 謓 軙 迧 錱 靕 駗 黰 㖘 㣀 㥲 㬐 㬜
질 質 疾 秩 姪 迭 跌 窒 叱 嫉 帙 佚 桎 膣 侄 蛭 瓆 絰 軼 躓 騭 垤 郅 鑕 蒺 咥 耋 晊 抶 銍 袠 瓞 袟 礩 祑 昳 翐 挃 妷 峌 恎 柣 耊 㗌 绖 质 铚 锧 儨 劕 厔 庢 愱 戜 槉 櫍 洷 潌 眣 眰 秷 紩 胅 腟 臷 苵 螏 螲 詄 豑 豒 貭 㑵 㗧 㘍 㜱 㜼 㩫 㲳 㲺 䏄 䑇 䜠 䟈 䬹 䱃
짐 朕 斟 鴆 酖 酙 㯢 鸩 栚 㘰 㪸 㮳 㴨 㼉 䧵 䲴
집 集 執 輯 什 緝 潗 鏶 戢 咠 諿 縶 㠍 慹 㙫 㠎 䌖 蓻 䐕 执 絷 缉 辑 亼 偮 卙 嶯 瓡 艥 謺 鍓 雦 雧
집 集 執 輯 什 緝 潗 鏶 戢 咠 諿 縶 㠍 慹 㙫 㠎 䌖 蓻 䐕 执 絷 缉 辑 亼 偮 卙 嶯 瓡 艥 謺 鍓 雦 雧 㗊 㗱 㣬 㱷 㴕 䁒 䅤 䉅 䩰 䮶 楫
짓 嗭
징 徵 懲 澄 澂 瀓 瞪 癥 徴 憕 惩 㠞
차 車 次 此 且 借 差 茶 叉 遮 嗟 蹉 箚 嵯 釵 磋 侘 槎 苴 瑳 佽 奓 硨 岔 偖 徣 姹 䂨 劄 汊 杈 莗 唶
착 着 著 捉 錯 躇 搾 鑿 窄 齪 斲 戳 擉 斵 斮 諑 娖 浞 笮 娕 珿 凿 诼 错 龊 丵 剒 撯 擆 灂 穛 篧 簎
찬 贊 讚 餐 瓚 燦 鑽 璨 撰 饌 纂 簒 竄 纘 粲 飡 澯 攢 爨 賛 欑 巑 趲 儧 儹 劗 湌 纉 㸑 攅 讃 酇 篡
찰 察 刹 札 擦 紮 扎 拶 咱 哳 巀 剎 檫 礸 詧 鑔 铡 镲 乲 偺 嚓 攃 桚 櫒 礤 糌 紥 耫 蚻 蠿 鍘 㞉 㦫
참 參 慘 慙 斬 站 讒 僭 讖 塹 懺 叅 慚 驂 譖 黲 巉 嶄 槧 鑱 憯 摻 攙 饞 欃 儳 毚 鏨 参 惨 僣 㕘 叁
창 唱 窓 昌 倉 創 蒼 暢 彰 滄 敞 昶 倡 漲 槍 瘡 愴 脹 娼 猖 菖 廠 艙 悵 搶 鬯 刱 倀 蹌 鎗 閶 氅 惝
채 採 菜 采 彩 債 蔡 埰 寨 綵 釵 寀 砦 茝 棌 瘵 蠆 瘥 倸 囆 债 虿 钗 䌽 婇 榸 睬 縩 踩 靫 㥒 㦅 㳗
책 責 冊 策 柵 翟 簀 嘖 磔 幘 笧 蚱 栅 册 筞 䇿 㥽 舴 茦 謮 啧 帻 箦 责 厇 嫧 憡 拺 敇 皟 瞔 箣 籷
차 車 次 此 且 借 差 茶 叉 遮 嗟 蹉 箚 嵯 釵 磋 侘 槎 苴 瑳 佽 奓 硨 岔 偖 徣 姹 䂨 劄 汊 杈 莗 唶 扯 撦 鹺 挓 訍 跐 踷 醝 㨋 砗 舣 车 鹾 佌 偧 偨 厏 唓 嗏 嗻 奲 嫅 嵳 扠 搓 搽 暛 栨 笡 紁 絘 肞 艖 芆 蒫 蔖 虘 蛼 衩 褨 譇 銟 鎈 齇 齹 㗬 㞖 㢒 㣾 㦋 㷢 㸙 㼮 㽨 㾝 䁟 䂘 䃎 䊬 䐒 䐤 䑘 䑡 䒲 䖕 䖳 䞣 䟕 䠡 䡨 䣜 䦈 䯸 䰈 䰩 䱹 䳐 䴾 䵙 䶥
착 着 著 捉 錯 躇 搾 鑿 窄 齪 斲 戳 擉 斵 斮 諑 娖 浞 笮 娕 珿 凿 诼 错 龊 丵 剒 撯 擆 灂 穛 篧 簎 籱 糳 縒 莡 辵 辶 逪 鋜 鑡 㒂 㓸 㚟 㲋 䇥 䓎 䕴 䥘 䥣
찬 贊 讚 餐 瓚 燦 鑽 璨 撰 饌 纂 簒 竄 纘 粲 飡 澯 攢 爨 賛 欑 巑 趲 儧 儹 劗 湌 纉 㸑 攅 讃 酇 篡 濽 穳 鑚 瓉 禶 攛 灒 籫 躥 丳 櫕 襸 鄼 赞 撺 攒 灿 瓒 窜 缵 趱 蹿 躜 酂 镩 馔 儏 囋 殩 熶 爘 篹 籑 繤 羼 薒 躦 鑹 饡 㛑 㜺 㠝 㣓 㦌 㬄 㸇 㹽 㺗 㻮 䂎 䉔 䉵 䌣 䞼 䟎 䡽 䬤 䬸 䰖 䱗
찰 察 刹 札 擦 紮 扎 拶 咱 哳 巀 剎 檫 礸 詧 鑔 铡 镲 乲 偺 嚓 攃 桚 櫒 礤 糌 紥 耫 蚻 蠿 鍘 㞉 㦫 㱜 㳐 㳨 䃰 䓭 䕓 䥷 䶪
참 參 慘 慙 斬 站 讒 僭 讖 塹 懺 叅 慚 驂 譖 黲 巉 嶄 槧 鑱 憯 摻 攙 饞 欃 儳 毚 鏨 参 惨 僣 㕘 叁 叄 旵 䜛 墋 謲 懴 毶 詀 醦 䜟 劖 噆 嵾 嶊 朁 瀺 覱 趻 堑 崭 惭 搀 斩 椠 瘆 碜 谗 谮 谶 錾 镵 馋 骖 黪 傪 嚵 壍 嬠 嬱 嶃 摲 獑 瘮 磛 磣 艬 譛 酁 魙 㜗 㜞 㟥 㟻 㠁 㦧 㨻 㰫 㸥 㺥 㽩 㿊 䁪 䂁 䑎 䟃 䤘 䤫 䧯 䪌 䱿 䳻 䶨
창 唱 窓 昌 倉 創 蒼 暢 彰 滄 敞 昶 倡 漲 槍 瘡 愴 脹 娼 猖 菖 廠 艙 悵 搶 鬯 刱 倀 蹌 鎗 閶 氅 惝 窻 憃 傖 鏦 窗 瑲 鋹 鶬 凔 戧 錆 椙 囱 牕 淐 摐 剏 剙 牎 畼 瑒 篬 厰 磢 謒 錩 枪 仓 伥 伧 创 呛 怅 怆 戗 抢 沧 涨 炝 玚 玱 畅 疮 胀 舱 苍 跄 锖 锠 阊 鲳 鸧 仺 僘 刅 嗆 囪 嵢 惐 摤 晿 淌 濸 焻 熗 獊 琩 螥 裮 誯 賶 鯧 鼚 㒉 㘟 㧿 㫤 㼽 䄝 䅛 䅮 䆫 䎫 䗉 䚎 䞎 䠀 䢢 䤌 䥊 䩨 䮖 䱽 䲝
채 採 菜 采 彩 債 蔡 埰 寨 綵 釵 寀 砦 茝 棌 瘵 蠆 瘥 倸 囆 债 虿 钗 䌽 婇 榸 睬 縩 踩 靫 㥒 㦅 㳗 䃀 䌨 䐆 䐱 䘍 䞗 䠕 䣋 䰂 琗 責
책 責 冊 策 柵 翟 簀 嘖 磔 幘 笧 蚱 栅 册 筞 䇿 㥽 舴 茦 謮 啧 帻 箦 责 厇 嫧 憡 拺 敇 皟 瞔 箣 籷 粣 萗 蓛 蔶 虴 諎 迮 銏 鏼 頙 㖽 㟙 㨲 㩍 㩞 㳻 㿭 䜺 䞰 䟄 䯔 䶦
처 處 妻 悽 凄 萋 淒 覷 褄 郪 䖏 处 処 覰 觑 刞 竌 紪 緀 耝 覻 霋 鶈 㜘 䁦
척 尺 拓 斥 戚 刺 隻 陟 滌 擲 瘠 脊 倜 慽 剔 蹠 惕 跖 摭 躑 俶 蜴 塉 捗 呎 坧 褁 慼 蹐 掦 鶺 隲 蹢
천 天 千 川 泉 淺 遷 賤 薦 踐 串 釧 擅 穿 喘 闡 阡 舛 韆 仟 玔 蚕 荐 倩 濺 洊 蕆 蒨 茜 芊 俴 儃 辿
철 鐵 哲 徹 撤 澈 喆 轍 綴 凸 輟 銕 掇 啜 惙 餮 歠 剟 埑 錣 飻 鉄 腏 鐡 畷 䮕 醊 㯙 悊 㬚 彻 缀 辍
첨 添 尖 瞻 僉 諂 籤 沾 詹 簽 甛 忝 簷 憸 檐 槧 襜 栝 惉 瀸 幨 櫼 甜 韂 籖 悿 湉 掭 沗 裧 冿 噡 婖
첩 妾 諜 帖 牒 疊 貼 捷 輒 堞 睫 褶 喋 鰈 怗 褺 倢 呫 輙 㡇 疂 倿 氎 耴 䴴 婕 牃 疉 穕 䩞 钻 叠 畳
청 靑 淸 請 聽 晴 廳 菁 鯖 倩 听 凊 圊 婧 蜻 鶄 青 清 㕔 聼 聴 廰 厅 庁 请 鲭 䴖 厛 夝 掅 暒 氰 甠
체 體 切 遞 滯 替 逮 締 涕 綴 諦 剃 体 軆 砌 棣 彘 泚 蕞 蔕 薙 蒂 殢 靆 嚏 髰 遆 滞 逓 禘 掣 遰 杕
초 草 初 招 超 礎 抄 肖 秒 哨 楚 焦 礁 稍 樵 貂 梢 炒 憔 硝 蕉 醋 醮 椒 酢 剿 艸 湫 苕 迢 誚 勦 悄
촉 觸 屬 燭 促 蜀 囑 矗 鏃 躅 矚 髑 爥 曯 薥 嘱 瞩 斸 瘃 蠋 劚 歜 灟 触 烛 㔉 亍 孎 斶 欘 竐 脨 臅
척 尺 拓 斥 戚 刺 隻 陟 滌 擲 瘠 脊 倜 慽 剔 蹠 惕 跖 摭 躑 俶 蜴 塉 捗 呎 坧 褁 慼 蹐 掦 鶺 隲 蹢 踢 墄 墌 鼜 掷 涤 踯 鹡 傶 叺 嵴 彳 徏 悐 惖 硩 磩 粎 膌 蚇 鏚 齣 㘮 㞝 㡿 㥻 㽚 䋇 䑾 䗩 䞠 䞶 䯜
천 天 千 川 泉 淺 遷 賤 薦 踐 串 釧 擅 穿 喘 闡 阡 舛 韆 仟 玔 蚕 荐 倩 濺 洊 蕆 蒨 茜 芊 俴 儃 辿 祆 僢 瓩 粁 臶 靝 迁 遄 刋 燀 賎 栫 韉 拪 剶 奷 荈 梴 棈 洤 湶 綪 闎 韀 浅 践 钎 冁 忏 扦 溅 蒇 贱 钏 阐 鞯 鳈 侟 兛 兲 囅 圌 圱 圲 夼 巛 幝 揼 曻 杄 楾 櫏 歂 氚 汌 汘 瀳 灛 灥 牮 猭 珔 瑏 皘 硟 竏 篅 篟 繟 腨 葲 蔳 譂 谸 輤 輲 釺 閳 靔 鰁 㐪 㚈 㟫 㣤 㯌 㱛 䀒 䀖 䄹 䆤 䆥 䉦 䍎 䑶 䒶 䚶
철 鐵 哲 徹 撤 澈 喆 轍 綴 凸 輟 銕 掇 啜 惙 餮 歠 剟 埑 錣 飻 鉄 腏 鐡 畷 䮕 醊 㯙 悊 㬚 彻 缀 辍 辙 铁 锇 勶 叕 啠 嚞 嚽 敠 敪 涰 烲 焎 爡 瞮 砓 罬 聅 蛈 蜇 裰 諁 酫 醛 鋨 驖 㙍 䄌 䒆 䚢 䞵 䟾 䥫 䫎
첨 添 尖 瞻 僉 諂 籤 沾 詹 簽 甛 忝 簷 憸 檐 槧 襜 栝 惉 瀸 幨 櫼 甜 韂 籖 悿 湉 掭 沗 裧 冿 噡 婖 燂 鑯 㤁 䪜 签 佥 裣 谄 锘 厃 嶦 敁 瀐 灊 籡 舔 舚 菾 虃 襝 讇 酟 鋓 鍩 鐱 閚 餂 黇 㐁 㑒 㔐 㖭 㙴 㚲 㜬 㡨 㤐 㦰 㬲 㮇 㵇 㶺 㾆 䄡 䄼 䇝 䋬 䏦 䑚 䞌 䠨 䦓
첩 妾 諜 帖 牒 疊 貼 捷 輒 堞 睫 褶 喋 鰈 怗 褺 倢 呫 輙 㡇 疂 倿 氎 耴 䴴 婕 牃 疉 穕 䩞 钻 叠 畳 谍 贴 辄 偼 媫 崨 惵 曡 淁 緁 脻 萜 蓵 誱 踕 踥 迠 鉆 鮿 鯜 㤴 㥈 㨗 㨩 㩸 㩹 㫸 㬪 㭯 㱌 㲲 㳧 䌌 䑖 䕈 䠟 䥡 䧪 䴑 䵿
청 靑 淸 請 聽 晴 廳 菁 鯖 倩 听 凊 圊 婧 蜻 鶄 青 清 㕔 聼 聴 廰 厅 庁 请 鲭 䴖 厛 夝 掅 暒 氰 甠 碃 郬 㵙 䝼 䞍 䨝
체 體 切 遞 滯 替 逮 締 涕 綴 諦 剃 体 軆 砌 棣 彘 泚 蕞 蔕 薙 蒂 殢 靆 嚏 髰 遆 滞 逓 禘 掣 遰 杕 躰 髢 疐 蝃 䙗 嵽 螮 餟 屜 懘 栘 褅 揥 摰 㚄 䪆 屉 缔 谛 达 递 䗖 䴘 叇 嚔 墆 崹 挮 搋 摖 楴 璏 痸 皉 矵 磜 祶 笍 迏 迖 釱 鉪 銐 鐟 霴 骵 鬀 鬄 鷈 鷉 㓹 㔸 㡗 㬱 㿃 䀙 䄟 䇧 䌡 䎮 䏅 䐭 䟷 䠠 䶏 諟 玼
초 草 初 招 超 礎 抄 肖 秒 哨 楚 焦 礁 稍 樵 貂 梢 炒 憔 硝 蕉 醋 醮 椒 酢 剿 艸 湫 苕 迢 誚 勦 悄 綃 鈔 軺 峭 怊 愀 髫 鞘 譙 杪 噍 岧 踔 燋 鷦 顦 劭 俏 趠 僬 嶕 鍬 偢 隹 嫶 耖 鍫 齠 椘 䎐 憷 岹 弨 撨 眧 檚 膲 芀 蟭 鉊 䘯 绡 艹 诌 诮 谯 轺 钞 锹 鹪 龆 仦 仯 僺 儊 劁 劋 吵 妱 屌 嶣 巐 帩 愺 摷 櫵 欩 潐 濋 焣 煍 燞 璴 瘄 癄 睄 瞧 祒 茮 荹 菬 萔 蕱 藮 觘 訬 謅 趭 轈 釥 鍣 鐎 陗 鞩 韒 騲
촉 觸 屬 燭 促 蜀 囑 矗 鏃 躅 矚 髑 爥 曯 薥 嘱 瞩 斸 瘃 蠋 劚 歜 灟 触 烛 㔉 亍 孎 斶 欘 竐 脨 臅 蠾 襡 襩 趗 踀 钃 鸀 㙇 㯮 㻿 䃚 䇍 䌵 䎌 䕽 䙱 䛤 䟉 䟟 䠱 䪅
촌 寸 村 忖 邨 吋 刌 屗 澊 籿
총 總 聰 銃 寵 叢 塚 摠 悤 蔥 憁 葱 驄 蓯 鏦 騘 冢 捴 総 揔 匆 偬 漎 怱 緫 聦 傯 棇 潨 聡 忩 璁 藂
총 總 聰 銃 寵 叢 塚 摠 悤 蔥 憁 葱 驄 蓯 鏦 騘 冢 捴 総 揔 匆 偬 漎 怱 緫 聦 傯 棇 潨 聡 忩 璁 藂 惣 漗 篵 㷓 䓗 䤸 丛 宠 总 聪 铳 骢 埫 愡 搃 樬 樷 欉 潈 濍 灇 焧 熜 燪 爜 縂 繱 茐 蟌 謥 鍯 鏓 㞱 㹅 䆹 䈡 䉥 䐋 䕺 䗓 䡯 䧭 䰌
촬 撮 娺 攥 窡 繓 襊 㔍 㭮 㵶 䆯 䵵
쵀 啐 啛 祽 錊 䦤
최 最 催 崔 摧 蕞 淬 榱 璀 漼 縗 脧 嘬 磪 㝡 嗺 墔 慛 槯 綷 缞 凗 樶 欼 熣 獕 皠 穝 紣 羧 膗 鋷 鏙
추 秋 推 丑 追 抽 醜 趨 鄒 楸 椎 樞 墜 酋 芻 錐 槌 錘 鎚 鰍 藪 雛 騶 皺 諏 湫 杻 萩 麤 蒭 瘳 惆 簉
축 祝 丑 築 逐 縮 畜 蓄 軸 蹴 柚 蹙 筑 竺 舳 蹜 豖 槭 妯 鼀 踧 柷 滀 拀 閦 笁 蓫 䠞 缩 轴 丒 傗 嘼
최 最 催 崔 摧 蕞 淬 榱 璀 漼 縗 脧 嘬 磪 㝡 嗺 墔 慛 槯 綷 缞 凗 樶 欼 熣 獕 皠 穝 紣 羧 膗 鋷 鏙 㜠 㝮 㵏 䊫 䔴 䘒 䙑 䧽 䴝
추 秋 推 丑 追 抽 醜 趨 鄒 楸 椎 樞 墜 酋 芻 錐 槌 錘 鎚 鰍 藪 雛 騶 皺 諏 湫 杻 萩 麤 蒭 瘳 惆 簉 陬 箠 啾 愀 箒 搥 帚 鞦 僦 甃 捶 縋 魋 娵 騅 鰌 縐 鍬 鶖 鶵 妯 蝤 隹 揫 鵻 麄 龝 趍 篘 鯫 麁 萑 崷 橻 鄹 㨨 寉 棰 菆 揂 棷 煼 緅 諈 貙 顀 鬌 䨨 枢 锤 刍 坠 尴 皱 绉 缒 诹 趋 邹 锥 雏 驺 骓 鲰 鳅 鹙 䝙 侴 僽 吜 埾 媝 媰 尷 憱 掫 揪 搊 棸 榋 殠 沝 渞 湬 湭 溴 煪 犓 甀 睭 瞅 矁 硾 礈 秌 穐
축 祝 丑 築 逐 縮 畜 蓄 軸 蹴 柚 蹙 筑 竺 舳 蹜 豖 槭 妯 鼀 踧 柷 滀 拀 閦 笁 蓫 䠞 缩 轴 丒 傗 嘼 噈 敊 斣 殧 稸 篫 縬 茿 蹵 鄐 顣 鱁 㗤 㛩 㜅 㰗 㾥 䙒 䙘 䙯 䥮 䮱
춘 春 椿 瑃 櫄 杶 橁 䞐 堾 媋 旾 暙 杽 湷 睶 萅 萶 蝽 賰 鰆 鶞 㖺 㫩 㿤 䞺 䡅 䦮 䲠
출 出 黜 朮 秫 术 怵 絀 岀 础 绌 怷 樎 欪 泏 炢 莍 黢 㑁 㔘 䘤 䟣 䢺
충 忠 充 蟲 衝 衷 沖 冲 虫 忡 珫 艟 种 䖝 翀 盅 揰 浺 漴 茺 㤝 茧 嘃 爞 痋 祌 㓍 㥙 㧤 㮔 㳘 㳞 䂌
충 忠 充 蟲 衝 衷 沖 冲 虫 忡 珫 艟 种 䖝 翀 盅 揰 浺 漴 茺 㤝 茧 嘃 爞 痋 祌 㓍 㥙 㧤 㮔 㳘 㳞 䂌 䆔 䘪 䝑 䟲 䡴
췌 悴 萃 贅 膵 瘁 揣 惴 顇 疩 赘 忰
취 取 就 吹 趣 臭 醉 聚 炊 娶 翠 脆 驟 鷲 嘴 揣 觜 毳 冣 橇 酔 竁 翆 骤 鹫 噿 嶉 槜 檇 欈 濢 璻 脃
취 取 就 吹 趣 臭 醉 聚 炊 娶 翠 脆 驟 鷲 嘴 揣 觜 毳 冣 橇 酔 竁 翆 骤 鹫 噿 嶉 槜 檇 欈 濢 璻 脃 膬 臎 臰 酻 龡 㪜 㯔 㯜 㱖 䕜 䶴
측 測 側 惻 仄 厠 廁 昃 稄 厕 侧 恻 测 庂 捑 昗 汄 畟 㳁 䈟
츤 襯 櫬 藽 齔 儭
츰 闖
층 層 蹭 层 䁬
치 治 致 齒 置 恥 値 徵 雉 痴 稚 峙 馳 侈 熾 幟 嗤 緻 痔 癡 緇 蚩 穉 嵯 埴 梔 淄 輜 寘 鴟 螭 廁 巵
치 治 致 齒 置 恥 値 徵 雉 痴 稚 峙 馳 侈 熾 幟 嗤 緻 痔 癡 緇 蚩 穉 嵯 埴 梔 淄 輜 寘 鴟 螭 廁 巵 菑 錙 褫 豸 絺 薙 哆 畤 卮 鵄 跱 阤 黐 痓 鯔 鴙 耻 歯 懥 䐉 媸 夂 偫 眵 稺 觶 乿 廌 值 椔 歭 袳 陊 騺 㨖 㱀 庤 滍 瓻 糦 胵 裭 郗 㢋 㣥 䀿 栀 帜 炽 缁 觯 辎 锱 驰 鲻 鸱 齿 俿 傂 卶 垁 垑 妛 崰 崻 徝 恀 憄 懫 拸 搱 旘 杘 湽 甾 秲 紎 耛 芖 茌 茬 荎 葘 藢 袮 襧 訵 誃 誺 跮 輺 鉹 鍿 駤 鶅 黹
칙 則 勅 飭 敕 恜 侙 慗 遫 鶒 鷘 则 饬 淔 趩 鉓 䳵
친 親 襯 櫬 齔 藽 嚫 瀙 䞋 亲 榇 衬 龀 儬 媇 寴 澵 齓 䠳 䠴
칠 七 漆 柒 㓒 㭍 桼 㐂 㓼 㓿 㔑 㯃 䜉 䣛
침 針 沈 侵 寢 浸 枕 鍼 砧 湛 琛 諶 忱 沉 駸 鋟 寖 梣 椹 郴 祲 碪 伔 寑 抌 踸 揕 枮 梫 賝 䆮 䑣 寝
침 針 沈 侵 寢 浸 枕 鍼 砧 湛 琛 諶 忱 沉 駸 鋟 寖 梣 椹 郴 祲 碪 伔 寑 抌 踸 揕 枮 梫 賝 䆮 䑣 寝 针 锓 骎 兓 唚 埐 夦 嶜 抋 濅 綅 綝 莐 萙 葴 誛 諃 鈂 鍖 霃 鮼 鱵 㑴 㓄 㓎 㕴 㝲 㪛 㴆 㴴 㶩 㾛 䈜 䒞 䘲 䚀 䚁 䜷 䤟 䥠 䪴 䫖 䫬 䭙 棽
칩 蟄 馽 蛰 漐 釞 㙷 㞏 㞚 㮑
칭 稱 秤 称 穪 偁 爯 㛵 䕝
카 㻔 佧 胩
쾌 快 獪 夬 噲 儈 侩 哙 叏 筷
타 他 打 妥 墮 隋 惰 唾 陀 駝 舵 楕 馱 朶 咤 拖 迤 駄 躱 沱 詑 跎 詫 佗 鼉 駞 柁 拕 橢 鮀 垞 阤 坨
탁 度 濁 拓 托 卓 濯 託 琢 擢 鐸 坼 啄 倬 柝 晫 琸 拆 橐 涿 逴 籜 踔 趠 沰 矺 蘀 乇 槖 椓 侂 跅 魠
탄 歎 彈 炭 誕 灘 綻 憚 呑 坦 嘆 殫 癱 攤 憻 驒 惮 殚 弹 吞 瘓 撣 僤 嘽 掸 啴 弾 摊 滩 痪 瘫 绽 诞
타 他 打 妥 墮 隋 惰 唾 陀 駝 舵 楕 馱 朶 咤 拖 迤 駄 躱 沱 詑 跎 詫 佗 鼉 駞 柁 拕 橢 鮀 垞 阤 坨 驒 鴕 堕 陁 它 吒 酡 嚲 㙐 垜 咜 奼 沲 媠 嶞 朵 陏 㓃 亸 椭 诧 锗 驮 驼 鸵 鼍 刴 剁 咃 咑 哚 垛 埵 堶 墯 嫷 尮 岮 崜 彵 憜 扡 挅 挆 撱 桗 椯 毤 毻 涶 灹 炧 炨 牠 痑 砣 砤 碢 秅 紽 綞 袉 趓 跥 跺 躲 軃 鍺 鰖 鵎 鼧 㛆 㛊 㟎 㢉 㥩 㨊 㯐 㰐 㸰 㸱 㻧 㼠 㾃 䂝 䅜 䆛 䍫 䑨 䑮 䒳 䙃 䙤 䜏 䠤 䡐
탁 度 濁 拓 托 卓 濯 託 琢 擢 鐸 坼 啄 倬 柝 晫 琸 拆 橐 涿 逴 籜 踔 趠 沰 矺 蘀 乇 槖 椓 侂 跅 魠 仛 斀 杔 㤞 㧻 浊 箨 讬 铎 镯 饦 伲 凙 剢 剫 劅 啅 庹 桌 槕 汑 硺 窧 萚 蠗 袥 襗 踱 鈬 鐲 飥 馲 驝 鵫 㔬 㪬 㹿 㺟 䐾 䓬 䮓
탄 歎 彈 炭 誕 灘 綻 憚 呑 坦 嘆 殫 癱 攤 憻 驒 惮 殚 弹 吞 瘓 撣 僤 嘽 掸 啴 弾 摊 滩 痪 瘫 绽 诞 怹 擹 暺 氽 汆 湠 疃 碳 羰 譠 騨 㖔 㛶 㧷 㨏 䋎 䘺
탈 脫 奪 侻 梲 敓 夺 捝 脱 敚 毲 痥 莌 鮵 鵽 㣞 䫄
탐 探 貪 耽 眈 酖 嗿 忐 醓 撢 躭 贪 赕 僋 沊 賧 㤾 㴷 㵅 䏙 䏥
탑 塔 搭 榻 鰈 搨 塌 傝 闒 嗒 撘 漯 㲮 鑉 鞜 鞳 㗳 阘 鳎 嚃 墖 毾 溻 狧 瘩 禢 耷 遢 錔 鎑 鮙 鰨 㛥
탑 塔 搭 榻 鰈 搨 塌 傝 闒 嗒 撘 漯 㲮 鑉 鞜 鞳 㗳 阘 鳎 嚃 墖 毾 溻 狧 瘩 禢 耷 遢 錔 鎑 鮙 鰨 㛥 㯓 㲩 㹺 䈳 䌈 䑜 䑽
탕 糖 湯 蕩 宕 帑 盪 碭 蘯 燙 逿 簜 蝪 踼 璗 鐋 荡 汤 烫 砀 铴 偒 圵 婸 摥 菪 薚 趤 雼 㼒 䑗 䦒
태 太 泰 態 殆 怠 台 胎 兌 跆 颱 汰 苔 笞 邰 呆 駄 迨 蛻 駘 埭 娧 孡 抬 兑 紿 蜕 䭾 鮐 忕 呔 兊 忲
태 太 泰 態 殆 怠 台 胎 兌 跆 颱 汰 苔 笞 邰 呆 駄 迨 蛻 駘 埭 娧 孡 抬 兑 紿 蜕 䭾 鮐 忕 呔 兊 忲 㤗 溙 㥭 态 绐 钛 骀 鲐 䌼 傣 冭 囼 夳 戻 捸 斄 曃 炱 炲 燤 粏 綐 緿 肽 舦 菭 軩 酞 鈦 颰 駾 㑀 㑷 㙂 㟋 㣍 㣖 㳲 䈚 䬈 珆 脫
택 宅 擇 澤 択 择 沢 泽 伬 烢 礋 蠌 鸅 㡯 㭦 䕉 䕪 垞
탱 撑 撐 牚 竀 樘 橖 橕 㯑
터 攄 摅
@@ -493,81 +493,81 @@
톤 噸 噋 瓲 啍 畽 䵯
톨 㐋
통 通 洞 統 痛 慟 筒 桶 恫 侗 筩 樋 㷁 憅 熥 綂 恸 统 嗵 捅 痌 蓪 㣚 㪌 㳆
퇴 退 椎 頹 堆 槌 腿 鎚 褪 焞 搥 隤 魋 頽 僓 蓷 痽 頺 俀 尵 䫋 颓 垖 塠 嵟 弚 桘 煺 磓 穨 脮 藬 蘈
퇴 退 椎 頹 堆 槌 腿 鎚 褪 焞 搥 隤 魋 頽 僓 蓷 痽 頺 俀 尵 䫋 颓 垖 塠 嵟 弚 桘 煺 磓 穨 脮 藬 蘈 螁 蹆 蹪 陮 頧 骽 鴭 㕍 㞂 㞜 㢈 㥆 㨃 㱣 㷟 㾼 㿉 㿗 䀃 䇏 䊚 䏨 䜃 䨺 䭔
투 投 鬪 透 套 鍮 妬 偸 渝 妒 骰 偷 闘 鬭 哣 敨 牏 秺 緰 蘣 鬦 鬬 㕻 㖣 㚐 㢏 㪗 䛠 䞬 䟝 䤅
퉁 佟
특 特 慝 忒 螣 蟘 貣 铽 忑 脦 蚮 鋱 鴏 㥂 㧹
틈 闖 闯
파 破 波 罷 播 派 頗 把 坡 巴 婆 跛 琶 芭 爬 擺 陂 杷 霸 叵 怕 葩 皤 帕 簸 灞 玻 笆 鄱 妑 岥 爸 耙
파 破 波 罷 播 派 頗 把 坡 巴 婆 跛 琶 芭 爬 擺 陂 杷 霸 叵 怕 葩 皤 帕 簸 灞 玻 笆 鄱 妑 岥 爸 耙 菠 壩 罢 疤 帊 靶 䆉 嶓 垻 吧 欛 鈀 摆 坝 钯 钷 颇 啵 嘙 夿 尀 岜 弝 櫇 汖 潖 皅 碆 笸 筢 箥 粑 紦 羓 舥 蒎 蔢 蚆 蚾 袙 譒 豝 趴 跁 鉕 錃 鎃 駊 鲃 㗞 㜑 㝿 㞎 㨇 㩯 㭛 㸭 㿬 䃻 䎬 䎱 䔤 䝛 䥯 䯲 䰾 䶕
판 判 板 版 販 阪 辦 坂 瓣 鈑 汴 辧 昄 舨 蝂 办 贩 钣 岅 瓪 畈 眅 粄 鵥 㤆
팔 八 叭 捌 扒 朳 汃 仈 哵 玐 釟 㭭
팟 巼
팡 乓
패 貝 敗 牌 悖 覇 佩 沛 稗 唄 狽 浿 霈 霸 孛 旆 珮 犻 誖 邶 棑 捭 㶚 斾 矲 郥 呗 狈 贝 败 钡 伂 姵
팽 庄 彭 硼 膨 澎 烹 祊 砰 蟚 蟛 伻 閍 碰 嘭 嵭 憉 掽 梈 甏 皏 硑 磞 稝 軯 輣 錋 闏 騯 㑟 㤣 㧸 㱶
패 貝 敗 牌 悖 覇 佩 沛 稗 唄 狽 浿 霈 霸 孛 旆 珮 犻 誖 邶 棑 捭 㶚 斾 矲 郥 呗 狈 贝 败 钡 伂 姵 昁 梖 牬 猈 珼 笩 篺 簰 粺 苝 茷 蛽 贁 鄁 鋇 馷 㔥 㗗 㛝 㤄 㧩 㫲 㳈 㸬 㸽 䊃 䖰 䟺 䢙 䩗 䩻 䰽 䱝
팽 庄 彭 硼 膨 澎 烹 祊 砰 蟚 蟛 伻 閍 碰 嘭 嵭 憉 掽 梈 甏 皏 硑 磞 稝 軯 輣 錋 闏 騯 㑟 㤣 㧸 㱶 㼞 䄘 䑫 䙀 䥋
퍅 愎
편 便 篇 片 遍 偏 編 扁 鞭 騙 翩 徧 褊 艑 蝙 匾 諞 惼 緶 萹 煸 蹁 楩 楄 鯾 鯿 缏 编 谝 骗 鳊 囨 媥
편 便 篇 片 遍 偏 編 扁 鞭 騙 翩 徧 褊 艑 蝙 匾 諞 惼 緶 萹 煸 蹁 楩 楄 鯾 鯿 缏 编 谝 骗 鳊 囨 媥 揙 牑 犏 猵 碥 箯 覑 貵 鍽 頨 騗 魸 鶣 㓲 㴜 㸤 㼐 㾫 䏒 䡢 䭏
폄 貶 窆 砭 贬
평 平 評 坪 萍 枰 苹 抨 怦 泙 蓱 鮃 玶 呯 䦕 评 鲆 匉 姘 岼 拼 胓 蚲 閛 駍 㛁 㺸 䍬 䓑
폐 閉 廢 肺 弊 蔽 幣 陛 斃 吠 嬖 獘 敝 癈 狴 廃 杮 梐 㢢 幤 废 毙 箅 闭 櫠 獙 砩 蕟 蜌 鄨 鐾 閇 鼣
포 布 抱 暴 捕 浦 胞 包 飽 砲 抛 鋪 逋 怖 鮑 泡 葡 褒 袍 蒲 圃 脯 哺 曝 匍 咆 疱 苞 佈 匏 庖 炮 晡
폐 閉 廢 肺 弊 蔽 幣 陛 斃 吠 嬖 獘 敝 癈 狴 廃 杮 梐 㢢 幤 废 毙 箅 闭 櫠 獙 砩 蕟 蜌 鄨 鐾 閇 鼣 㙄 㡀 㯇 㾱 䉬 䕠 䠘 䯗
포 布 抱 暴 捕 浦 胞 包 飽 砲 抛 鋪 逋 怖 鮑 泡 葡 褒 袍 蒲 圃 脯 哺 曝 匍 咆 疱 苞 佈 匏 庖 炮 晡 餔 舖 炰 誧 儤 鞄 鯆 曓 鉋 拋 酺 宲 礮 枹 襃 虣 圑 蒱 麅 笣 怉 脬 舗 跑 麃 㳍 刨 钸 铇 铺 饱 鲍 龅 佨 勹 咘 垉 埔 奅 孢 峬 庯 忁 悑 抪 柨 爮 狍 瓝 皰 礟 菢 萢 蚫 蜅 袌 裦 褜 謈 軳 鈽 鑤 闁 陠 靤 髱 鵏 麭 齙 㘵 㚴 㚿 㫧 㬥 㬧 㯡 㲒 䈬 䈻 䊇 䍖 䎂 䔕 䛌 䝵 䤖 䩝 䫽 䭋 䮒 䶌
폭 暴 爆 幅 瀑 曝 輻 曓 䋹 䌿
폿 喸
표 表 標 票 漂 杓 豹 飄 剽 慓 瓢 俵 彪 驃 飇 縹 摽 殍 鑣 裱 僄 嫖 勡 嘌 熛 鏢 髟 鰾 飆 颷 醥 淲 藨
표 表 標 票 漂 杓 豹 飄 剽 慓 瓢 俵 彪 驃 飇 縹 摽 殍 鑣 裱 僄 嫖 勡 嘌 熛 鏢 髟 鰾 飆 颷 醥 淲 藨 飈 飊 彯 爂 猋 穮 膘 蔈 薸 褾 謤 驫 魒 㼼 䅺 标 缥 镖 镳 飘 飙 飚 骉 骠 鳔 儦 墂 婊 幖 徱 旚 檦 滮 瀌 燢 犥 瘭 皫 瞟 磦 篻 翲 脿 臕 螵 諘 贆 錶 闝 顠 颩 飃 㘐 㟽 㠒 㧼 㬓 㯱 㯹 㲏 㵱 㶾 㹾 䁃 䁭 䏇 䔸 䕯 䙳 䞄 䮽 䱪 䴩 䶂
푸 䬌
품 品 稟 禀 榀
풍 風 豊 楓 諷 馮 丰 葑 豐 瘋 酆 鄷 渢 偑 凮 灃 蘴 冯 枫 沣 疯 砜 讽 风 僼 凨 凬 堸 寷 栤 檒 沨 煈
피 皮 彼 被 避 疲 披 跛 陂 辟 詖 鞁 屁 髲 柀 帔 骳 狓 鈹 佊 秛 㱟 诐 铍 鲏 䙓 怶 旇 氕 犤 紴 翍 耚
풍 風 豊 楓 諷 馮 丰 葑 豐 瘋 酆 鄷 渢 偑 凮 灃 蘴 冯 枫 沣 疯 砜 讽 风 僼 凨 凬 堸 寷 栤 檒 沨 煈 猦 碸 霻 靊 颪 飌 麷 㐽 㒥 㠦 䏎 䵄
피 皮 彼 被 避 疲 披 跛 陂 辟 詖 鞁 屁 髲 柀 帔 骳 狓 鈹 佊 秛 㱟 诐 铍 鲏 䙓 怶 旇 氕 犤 紴 翍 耚 藣 襬 貏 貱 銔 鮍 㓟 㗪 㢰 㨢 㯅 䏢 䏶
픽 腷 煏
필 必 筆 匹 畢 弼 泌 疋 苾 珌 馝 蹕 蓽 韠 篳 觱 佖 滭 駜 鵯 咇 罼 鞸 驆 弻 榓 㻶 熚 胇 袐 飶 䟆 哔
필 必 筆 匹 畢 弼 泌 疋 苾 珌 馝 蹕 蓽 韠 篳 觱 佖 滭 駜 鵯 咇 罼 鞸 驆 弻 榓 㻶 熚 胇 袐 飶 䟆 哔 毕 滗 笔 筚 荜 虑 跸 铋 鹎 吡 嗶 妼 彃 怭 潷 縪 苉 襅 邲 鉍 鏎 饆 魓 鮅 鴄 鷝 㓖 㢶 㢸 㪤 㮿 㳼 㻫 䄶 䏘 䖩 䩛 䫾 䬛 䮡
핍 乏 逼 偪 愊 姂 鴔
하 下 夏 河 何 賀 荷 瑕 遐 霞 蝦 厦 廈 鰕 昰 嚇 罅 呀 鍜 煆 岈 懗 瘕 嘏 縀 睱 谺 騢 鏬 哧 抲 菏 㙈
학 學 鶴 虐 壑 謔 瘧 确 涸 郝 狢 熇 鷽 皬 学 翯 觷 嗃 澩 矐 㕰 䳽 疟 谑 鸴 鹤 嗀 嚛 嚯 壆 峃 嶨 斈
한 韓 漢 寒 限 閑 恨 旱 汗 翰 邯 罕 悍 澣 閒 瀚 桿 捍 閈 暵 鼾 犴 僩 嫺 熯 嫻 駻 鷳 扞 狠 僴 覵 瞷
할 割 轄 蝎 黠 瞎 鶡 蠍 傄 劼 縖 舝 䫘 辖 乤 嗐 搳 硈 磍 螛 鎋 韔 鶷 㔛 㔠 㝬 㪴 㮫 㿣 䕣 䕸 䦖 䦪
함 咸 陷 含 函 艦 喊 涵 銜 緘 檻 鹹 啣 諴 菡 頷 嗛 闞 憨 莟 轞 䧟 淊 凾 醎 顑 圅 㴠 䓿 衘 谽 馠 䘖
합 合 陜 蛤 盒 閤 哈 闔 盍 溘 榼 郃 柙 匌 嗑 呷 詥 鴿 篕 㝓 峆 欱 烚 盇 鮯 䢔 陕 阖 颌 鸽 佮 峇 廅
항 行 恒 降 項 抗 巷 港 航 沆 亢 缸 肛 杭 伉 姮 桁 嫦 恆 頏 炕 夯 缿 骯 吭 閌 衖 佭 斻 闀 肮 钪 闶
해 海 害 解 亥 該 奚 偕 駭 骸 諧 楷 懈 咳 邂 孩 垓 瀣 蟹 醢 廨 薤 獬 瑎 欬 咍 嶰 痎 鮭 頦 觧 澥 晐
하 下 夏 河 何 賀 荷 瑕 遐 霞 蝦 厦 廈 鰕 昰 嚇 罅 呀 鍜 煆 岈 懗 瘕 嘏 縀 睱 谺 騢 鏬 哧 抲 菏 㙈 虾 贺 丅 丷 哬 嗬 圷 夓 疜 碋 碬 芐 蕸 袔 諕 赮 鎼 閕 閜 鞐 颬 㗇 㗿 㙤 㰤 㰺 㵑 䠍 䪗 䫗
학 學 鶴 虐 壑 謔 瘧 确 涸 郝 狢 熇 鷽 皬 学 翯 觷 嗃 澩 矐 㕰 䳽 疟 谑 鸴 鹤 嗀 嚛 嚯 壆 峃 嶨 斈 癋 硸 蠚 謞 貈 雤 靏 鴬 鶮 鸖 㕡 㰒 㶅 㿥 䅂 䖈 䖋 䤕 䨋 䮤
한 韓 漢 寒 限 閑 恨 旱 汗 翰 邯 罕 悍 澣 閒 瀚 桿 捍 閈 暵 鼾 犴 僩 嫺 熯 嫻 駻 鷳 扞 狠 僴 覵 瞷 猂 邗 骭 撊 鷴 垾 憪 橌 㸁 䛞 䦘 闲 鹇 娴 汉 焊 闬 韩 仠 佷 傼 兯 哻 娨 嫨 屽 晘 橺 浫 澖 皔 蓒 蔊 蛝 螒 豻 貋 釬 銲 雗 鶾 鷼 麲 㒏 㛠 㡾 㢨 㪋 㯗 㲦 㵄 䁂 䂅 䈨 䉯 䍐 䍑 䎯 䏷 䓍 䓳 䕿 䗙 䥜 䦥 䮧 忓
할 割 轄 蝎 黠 瞎 鶡 蠍 傄 劼 縖 舝 䫘 辖 乤 嗐 搳 硈 磍 螛 鎋 韔 鶷 㔛 㔠 㝬 㪴 㮫 㿣 䕣 䕸 䦖 䦪
함 咸 陷 含 函 艦 喊 涵 銜 緘 檻 鹹 啣 諴 菡 頷 嗛 闞 憨 莟 轞 䧟 淊 凾 醎 顑 圅 㴠 䓿 衘 谽 馠 䘖 唅 浛 筨 琀 甉 魽 㨔 㺝 䤴 䶢 衔 槛 缄 舰 阚 馅 壏 娢 晗 梒 欦 澏 焓 糮 臽 蛿 蜬 蜭 譀 豃 豏 輱 鋡 錎 陥 顄 餡 鬫 鰔 㓧 㖤 㘅 㘎 㘚 㛾 㟏 㟔 㤷 㮭 㯺 㰹 㶰 㺖 㼨 㽉 䈄 䎏 䐄 䕔 䖔 䘶 䟰 䣻 䥁 䨡 䩂 䫲 䱤 䲗 䶃 䶟 䶠
합 合 陜 蛤 盒 閤 哈 闔 盍 溘 榼 郃 柙 匌 嗑 呷 詥 鴿 篕 㝓 峆 欱 烚 盇 鮯 䢔 陕 阖 颌 鸽 佮 峇 廅 敮 熆 珨 秴 粭 翈 耠 頜 魻 㘡 㥺 㭘 㰰 䆟 䖎 䖖 䛅 䧻 䶎
항 行 恒 降 項 抗 巷 港 航 沆 亢 缸 肛 杭 伉 姮 桁 嫦 恆 頏 炕 夯 缿 骯 吭 閌 衖 佭 斻 闀 肮 钪 闶 项 颃 塂 岲 栙 笐 罁 苀 蚢 貥 跭 迒 邟 酐 鈧 闂 魧 㔰 㟟 㤚 㰠 䀪 䁰 䘕 䜶 䟘 䢽 䣈 䲳
해 海 害 解 亥 該 奚 偕 駭 骸 諧 楷 懈 咳 邂 孩 垓 瀣 蟹 醢 廨 薤 獬 瑎 欬 咍 嶰 痎 鮭 頦 觧 澥 晐 陔 荄 蠏 侅 駴 㤥 畡 賅 峐 檞 膎 韰 㧡 䪥 该 谐 赅 颏 骇 鲑 嗨 塰 妎 姟 慀 氦 絯 繲 胲 薢 觟 豥 賌 輆 郋 酼 餀 饚 龤 㒠 㗨 㙰 㚊 㜾 㞒 㰡 㰧 㰩 㱼 㾂 㾬 䀭 䇋 䉏 䚸 䠹 䠽 䡡 䦏 䯐 䱺 䲒 祄
핵 核 劾 覈 翮 栶 势 䃒
행 行 幸 杏 倖 荇 悻 婞 涬 筕 㼬 绗 鸻 哘 啈 垳 堼 洐 烆 絎 緈 胻 莕 裄 鴴 鵆 﨨 㓑 䁄 䂔 䓷 䛭 䯒
향 向 鄕 香 享 響 饗 嚮 餉 珦 薌 鄊 晑 曏 郷 蠁 膷 鄉 銄 麘 䦳 乡 响 芗 飨 饷 亯 姠 楿 稥 萫 蚃 鱜
행 行 幸 杏 倖 荇 悻 婞 涬 筕 㼬 绗 鸻 哘 啈 垳 堼 洐 烆 絎 緈 胻 莕 裄 鴴 鵆 﨨 㓑 䁄 䂔 䓷 䛭 䯒
향 向 鄕 香 享 響 饗 嚮 餉 珦 薌 鄊 晑 曏 郷 蠁 膷 鄉 銄 麘 䦳 乡 响 芗 飨 饷 亯 姠 楿 稥 萫 蚃 鱜 黁 㕿 㗽 㴡 㿝 䅨 䊑 䖮 䦭
허 許 虛 墟 噓 詡 歔 虚 栩 虗 㠊 魖 嘘 浒 许 憈 鄦 驉 魼 㞰 䔓
헌 憲 獻 軒 櫶 掀 献 巚 幰 攇 巘 瀗 宪 轩 仚 佡 搟 㦥 䆭 䘆 䜢 䧮 䡣 昍
헐 歇 蝎 蠍 滊
험 險 驗 憸 嶮 玁 獫 硷 崄 猃 险 険 験 验 姭 忺 杴 榃 礆 譣 騐 㷿 㸝 㿌
혁 革 赫 爀 奕 嚇 洫 鬩 弈 焱 衋 虩 侐 焃 殈 赩 㷜 吓 阋 伵 捇 煂 盢 瞁 覤 赥 趘 㤸 㦦 㬨 㮝 㷦 䓇
현 見 現 賢 顯 玄 縣 懸 絃 弦 鉉 峴 炫 洵 眩 衒 絢 晛 俔 玹 泫 睍 舷 儇 嬛 昡 誢 琄 眴 繯 痃 筧 県
혈 血 穴 孑 頁 絜 趐 泬 蝢 緳 䆷 䦧 页 吷 坹 奊 娎 岤 桖 烕 疦 茓 裇 㐖 䆝 䋉 䐼 䒸 䕵 䙽 䛎 䦑 䩤
혁 革 赫 爀 奕 嚇 洫 鬩 弈 焱 衋 虩 侐 焃 殈 赩 㷜 吓 阋 伵 捇 煂 盢 瞁 覤 赥 趘 㤸 㦦 㬨 㮝 㷦 䓇 䚂 䚫 䦗 䱛 䵱
현 見 現 賢 顯 玄 縣 懸 絃 弦 鉉 峴 炫 洵 眩 衒 絢 晛 俔 玹 泫 睍 舷 儇 嬛 昡 誢 琄 眴 繯 痃 筧 県 祆 翾 蜆 鋗 駽 顕 袨 灦 敻 礥 贒 垷 莧 㬎 哯 埍 胘 蚿 蠉 鋧 鑦 䧎 伣 县 岘 悬 显 现 绚 缳 苋 蚬 贤 铉 伭 呟 咞 妶 娊 娹 婱 弲 怰 惤 梋 矎 臔 臤 虤 訮 詪 詽 誸 譞 讂 贙 鞙 韅 㘋 㡉 㢺 㧋 㧦 㫫 㭹 㹡 㻹 䀏 䍗 䏹 䗾 䚯 䛹 䝨 䝮 䥪 䧋 䩙 䮄 䲻
혈 血 穴 孑 頁 絜 趐 泬 蝢 緳 䆷 䦧 页 吷 坹 奊 娎 岤 桖 烕 疦 茓 裇 㐖 䆝 䋉 䐼 䒸 䕵 䙽 䛎 䦑 䩤 䩧 䫼
혐 嫌 鼸 馦 㾾 㺌 㽐 䵌
협 協 脅 狹 峽 陜 挾 俠 頰 夾 浹 脇 鋏 莢 叶 恊 愜 篋 悏 匧 埉 祫 峡 惬 䝱 筴 蛺 劦 冾 勰 脥 硤 陿
형 兄 形 刑 亨 螢 衡 炯 馨 邢 瀅 型 荊 熒 瑩 泂 珩 逈 烱 桁 滎 鎣 灐 迥 詗 夐 陘 娙 荆 鉶 硎 浻 蘅
혜 惠 慧 兮 彗 醯 鞋 蹊 蕙 暳 盻 徯 槥 傒 嘒 寭 謑 譿 恵 嵇 鼷 鞵 潓 嵆 橞 蟪 譓 豀 嚖 憓 㜎 䚷 䧥
호 好 戶 號 湖 乎 呼 虎 胡 護 毫 浩 豪 互 扈 鎬 皓 糊 昊 濠 祜 晧 澔 壕 狐 瑚 弧 琥 壺 槁 蒿 濩 芦
협 協 脅 狹 峽 陜 挾 俠 頰 夾 浹 脇 鋏 莢 叶 恊 愜 篋 悏 匧 埉 祫 峡 惬 䝱 筴 蛺 劦 冾 勰 脥 硤 陿 拹 狭 侠 协 夹 挟 浃 硖 箧 胁 荚 蛱 铗 铪 颊 饸 䇲 嗋 垥 愶 旪 梜 熁 燲 綊 脋 鉿 鞈 頬 餄 㙝 㛍 㢵 㤲 㥦 㥷 㮉 㶸 㼪 㽠 㾜 㿓 䁋 䏩 䏮 䕛 䬅 䶝
형 兄 形 刑 亨 螢 衡 炯 馨 邢 瀅 型 荊 熒 瑩 泂 珩 逈 烱 桁 滎 鎣 灐 迥 詗 夐 陘 娙 荆 鉶 硎 浻 蘅 郉 脝 㢠 侀 涥 㺾 㼆 泶 滢 潆 荥 荧 萤 蓥 诇 铏 陉 哼 坓 嵤 悙 擤 瀠 藀 蛍 蛵 褮 賯 鋞 鶑 㐩 㓝 㚾 㣜 㭢 㯏 㼛 䅽 䚘 䢛 䣆 䤯 䤰 䳙
혜 惠 慧 兮 彗 醯 鞋 蹊 蕙 暳 盻 徯 槥 傒 嘒 寭 謑 譿 恵 嵇 鼷 鞵 潓 嵆 橞 蟪 譓 豀 嚖 憓 㜎 䚷 䧥 僡 匸 嚡 忚 懳 榽 橀 櫘 璤 蒵 螇 豯 鏸 韢 騱 㚛 㥣 㨙 㩨 㬩 㰥 㰿 㿽 䎚 䒊 䙎 䛊 䜁 䤈 䫣 䲪
호 好 戶 號 湖 乎 呼 虎 胡 護 毫 浩 豪 互 扈 鎬 皓 糊 昊 濠 祜 晧 澔 壕 狐 瑚 弧 琥 壺 槁 蒿 濩 芦 灝 瓠 縞 顥 岵 蝴 滸 頀 淏 葫 犒 怙 沍 箎 餬 皞 醐 嘷 滈 冱 嫮 滬 皜 儫 嫭 猢 聕 鬍 号 户 㦿 昈 皥 乕 䎁 蠔 鄗 唬 皡 壷 暭 虖 鄠 猲 嘑 楜 苸 䕶 䚽 帍 暤 曍 枑 滹 籇 萀 謼 譹 錿 䊀 壶 护 沪 灏 缟 轷 镐 颢 鹕 乊 傐 冴 哠 喖 嗥 噑 嚎 垀 夰 媩 峼 弖 恏 悎 戸 戽 摢 昦 枦 椃 槴 歑 泘 瀥 烀 煳
혹 或 惑 酷 鵠 豰 掝 鍙 閄 頶 嗀 㦯 㷤 㺉 㽇 䞱 熇
혼 混 婚 昏 魂 渾 煇 琿 溷 閽 睨 惛 焜 湣 圂 惽 睯 掍 鯶 鼲 䰟 挥 浑 珲 阍 馄 倱 堚 婫 忶 昬 棔 殙
혼 混 婚 昏 魂 渾 煇 琿 溷 閽 睨 惛 焜 湣 圂 惽 睯 掍 鯶 鼲 䰟 挥 浑 珲 阍 馄 倱 堚 婫 忶 昬 棔 殙 涽 焝 睧 緷 繉 觨 餛 㑮 㖧 㛰 㮯 㱪 䅙 䊐 䎜 䐊 䚠 䛰 䡣 䧰 䮝 䴷 俒 䫟
홀 忽 笏 惚 囫 絗 曶 芴 㧾 䬍 锪 乥 匢 匫 唿 啒 寣 鍃 㖴 㧮 㨡 㳷 㺀 䁫 䓤 䝆 䨚 䩐 䴯
홍 紅 洪 弘 鴻 泓 訌 虹 哄 烘 汞 鬨 澒 晎 篊 鉷 洚 閧 葒 妅 唝 红 荭 讧 鸿 仜 叿 嗊 垬 娂 屸 渱 潂
화 火 花 化 話 和 畵 華 貨 禍 禾 靴 樺 嬅 譁 樗 畫 夥 擭 嘩 驊 俰 婲 龢 画 澕 槬 墷 鞾 咊 鏵 伙 楇
확 確 擴 穫 廓 濩 攫 碻 鑊 确 涸 擭 矍 礭 矡 䨥 镬 雘 矱 蠖 彍 戄 彉 攉 玃 臛 艧 靃 彠 扩 拡 鹱 劐
환 患 歡 還 換 丸 環 喚 煥 桓 幻 宦 鰥 驩 渙 紈 奐 晥 懽 圜 寰 豢 鬟 擐 轘 瓛 睆 鍰 絙 鐶 繯 垸 皖
홍 紅 洪 弘 鴻 泓 訌 虹 哄 烘 汞 鬨 澒 晎 篊 鉷 洚 閧 葒 妅 唝 红 荭 讧 鸿 仜 叿 嗊 垬 娂 屸 渱 潂 灴 焢 硔 粠 苰 葓 蕻 谼 谾 銾 霐 霟 魟 㖓 㙆 㬴 㶹 䀧 䂫 䃔 䆪 䉺 䜫 䞑 䧆 䨎 䪦 䫹 䲨
화 火 花 化 話 和 畵 華 貨 禍 禾 靴 樺 嬅 譁 樗 畫 夥 擭 嘩 驊 俰 婲 龢 画 澕 槬 墷 鞾 咊 鏵 伙 楇 璍 訸 㠏 划 华 哗 桦 祸 话 货 钬 铧 骅 鳠 吙 啝 埖 夻 姀 崈 崋 惒 摦 旤 杹 枠 柇 椛 沎 灬 盉 硴 竵 糀 舙 芲 萂 蕐 誮 譮 邩 釫 鈥 鉌 錵 魤 鱯 鷨 㕦 㗾 㟆 㦊 㭉 㳸 㶡 䄀 䅿 䒩 䔢 䛡
확 確 擴 穫 廓 濩 攫 碻 鑊 确 涸 擭 矍 礭 矡 䨥 镬 雘 矱 蠖 彍 戄 彉 攉 玃 臛 艧 靃 彠 扩 拡 鹱 劐 嬳 彏 彟 挄 曤 檴 爴 瓁 矆 篗 籆 籰 耯 臒 蒦 貜 霩 鸌 㠛 㦜 㨯 㬑 㬦 㸕 㺢 䁨 䂄 䈅 䉟
환 患 歡 還 換 丸 環 喚 煥 桓 幻 宦 鰥 驩 渙 紈 奐 晥 懽 圜 寰 豢 鬟 擐 轘 瓛 睆 鍰 絙 鐶 繯 垸 皖 逭 癏 寏 瑍 䆠 讙 鱞 漶 㬊 汍 闤 澴 弮 芄 㬇 綄 䚪 䝔 唤 奂 换 欢 歓 涣 焕 纨 还 锾 镮 阛 鲩 鳏 喛 峘 愌 懁 攌 暅 梙 槵 烉 犱 獾 睅 瞣 笂 糫 羦 肒 荁 萈 萟 藧 貛 酄 釻 鋎 雈 鯇 鰀 鴅 鵍 鹮 㕕 㛟 㞍 㡲 㪱 㶎 㹕 㹖 㿪 䀓 䁵 䍺 䒛 䒯 䗭 䜨 䝠 䠉 䦡 䭴 䯘 䴉 䴋 䴟 洹
활 活 闊 滑 猾 豁 佸 蛞 濶 䀨 敌 阔 奯 姡 眓 磆 秮 秳 萿 螖 趏 㓉 㕲 㵈 䄆 䄑 䦢 䱻 䴳
황 黃 皇 況 荒 滉 慌 徨 晃 惶 遑 煌 凰 潢 璜 蝗 榥 隍 篁 愰 簧 恍 幌 湟 晄 怳 貺 肓 喤 鎤 熀 瑝 媓
황 黃 皇 況 荒 滉 慌 徨 晃 惶 遑 煌 凰 潢 璜 蝗 榥 隍 篁 愰 簧 恍 幌 湟 晄 怳 貺 肓 喤 鎤 熀 瑝 媓 况 黄 䌙 皝 熿 偟 艎 謊 㡛 癀 穔 縨 墴 揘 衁 詤 䁜 谎 贶 鳇 兤 堭 塃 奛 宺 崲 巟 曂 朚 楻 櫎 炾 獚 皩 眖 葟 蟥 諻 趪 軦 韹 餭 騜 鰉 鱑 鷬 㠩 㠵 㡃 㡆 㣴 㤺 㨪 㬻 㵁 㶂 㾮 䀮 䄓 䅣 䊗 䊣 䍿 䐠 䐵 䑟 䞹 䪄 䮲 䳨
홰 罫 噦 翽 哕 噧 澅 翙
회 回 會 悔 懷 淮 恢 灰 廻 檜 膾 濊 晦 徊 誨 賄 繪 蛔 澮 匯 獪 茴 迴 燹 洄 詼 鱠 佪 繢 滙 闠 頮 沬
회 回 會 悔 懷 淮 恢 灰 廻 檜 膾 濊 晦 徊 誨 賄 繪 蛔 澮 匯 獪 茴 迴 燹 洄 詼 鱠 佪 繢 滙 闠 頮 沬 盔 栃 湏 会 懐 囬 禬 㑹 薈 豗 囘 逥 瀤 恛 璯 蛕 螝 褢 鄶 刽 桧 浍 烩 狯 絵 绘 脍 荟 诙 诲 贿 郐 颒 鲙 㧟 劊 咴 嬒 屷 廽 拻 擓 敼 杤 櫰 洃 烣 烸 燴 痐 瞺 硘 耲 藱 蘹 蚘 蜖 褱 詯 諙 鐬 靧 鮰 㒑 㜳 㞀 㞧 㠢 㣛 㨤 㱱 㷄 㷇 㻅 㾯 䕇 䖶 䛛 䜋 䝅 䝇 䢈 䤧 䩈 䫭 䭝 䴜 䵳
획 畵 獲 劃 硅 畫 擭 嚄 謢 撶 繣 㗲 获 婳 咟 嚿 嫿 湱 砉 窢 謋 讗 韄 騞 㓰 㖪 㦎 㩇 䐸 䦝 䪝 䬉 画
횡 橫 薨 宖 鐄 黌 鈜 澋 横 鋐 紭 竑 黉 吰 嚝 峵 彋 揈 撔 谹 輷 鑅 㢬 㶇 䍔 䎕 䫺 䬖 䬝
효 孝 效 曉 酵 爻 嚆 哮 梟 肴 淆 驍 斅 涍 囂 嘐 傚 殽 崤 熇 皛 虓 餚 効 暁 嘵 枵 鴞 歊 斆 憢 毊 嘋
후 後 厚 侯 后 候 喉 吼 朽 逅 嗅 煦 帿 珝 詬 涸 垕 詡 酗 猴 堠 喣 餱 篌 佝 譃 吽 糇 齅 姁 睺 矦 郈
훈 訓 薰 熏 勳 壎 暈 塤 勛 焄 燻 曛 纁 葷 獯 鑂 爋 勲 醺 坃 薫 矄 埙 勋 晕 荤 训 嚑 壦 瞓 臐 蔒 蘍
효 孝 效 曉 酵 爻 嚆 哮 梟 肴 淆 驍 斅 涍 囂 嘐 傚 殽 崤 熇 皛 虓 餚 効 暁 嘵 枵 鴞 歊 斆 憢 毊 嘋 獢 筊 髇 哓 嚣 晓 枭 胶 骁 鸮 侾 俲 倄 呺 嚻 婋 宯 庨 撬 敩 洨 滧 灱 灲 猇 痚 皢 硣 穘 窙 笅 膮 藠 虈 訤 詨 誟 誵 郩 顤 髐 﨧 㕺 㚠 㤊 㩭 㫴 㭳 㮁 㵿 㹲 㺧 䂚 䉰 䋂 䒝 䓔 䕧 䖀 䥵 䫞 䬘 謼 烋
후 後 厚 侯 后 候 喉 吼 朽 逅 嗅 煦 帿 珝 詬 涸 垕 詡 酗 猴 堠 喣 餱 篌 佝 譃 吽 糇 齅 姁 睺 矦 郈 鍭 珛 齁 鱟 㗋 䣱 诩 鲎 鲘 冔 呍 洉 犼 猤 疞 瘊 翭 翵 葔 螑 豞 鄇 銗 骺 鮜 鯸 㖃 㗜 㢿 㤧 㫗 㬋 㮢 㰭 㱙 㴟 㷞 㸸 㺅 㻈 㽳 䂉 䏏 䖉 䗔 䙈 䞀 䞧 䣴 䧁 䫛 䳧 芋
훈 訓 薰 熏 勳 壎 暈 塤 勛 焄 燻 曛 纁 葷 獯 鑂 爋 勲 醺 坃 薫 矄 埙 勋 晕 荤 训 嚑 壦 瞓 臐 蔒 蘍 䌲 䗼 䙧 䠝 䵫 煇
훌 欻 歘 烼 魆 㗵
훙 薨 顭
훤 喧 萱 暄 煊 諼 諠 烜 翧 晅 楦 藼 箮 䁔 咺 愋 蝖 貆 谖 吅 嚾 媗 昍 狟 睻 禤 萲 蕿 蘐 㓩 䚙 䚭 愃
훨 䎀
훼 毁 喙 卉 虫 虺 燬 芔 毀 毇 檓 譭 顪 餯 㑰 㩓 㷐 䂕 䃣 䛼
휘 揮 輝 徽 諱 麾 彙 暉 煇 褘 撝 翬 彚 椲 禈 䘗 幑 楎 汇 晖 讳 辉 㧑 媈 泋 瀈 灳 翚 鰴 㩣 㫎 㭏 㹆
휘 揮 輝 徽 諱 麾 彙 暉 煇 褘 撝 翬 彚 椲 禈 䘗 幑 楎 汇 晖 讳 辉 㧑 媈 泋 瀈 灳 翚 鰴 㩣 㫎 㭏 㹆 㺔 煒
휭 遤
휴 休 携 墮 烋 虧 畦 隳 亏 睢 鵂 咻 擕 髹 庥 鑴 貅 攜 巂 瓗 眭 茠 觿 雟 飍 鸺 倠 儶 婎 孈 烌 睳 蘳
휴 休 携 墮 烋 虧 畦 隳 亏 睢 鵂 咻 擕 髹 庥 鑴 貅 攜 巂 瓗 眭 茠 觿 雟 飍 鸺 倠 儶 婎 孈 烌 睳 蘳 蠵 觹 觽 貕 酅 銝 隓 驨 髤 鮴 㔒 㥟 㩗 㩦 㱗 㳜 㵻 㹯 㽯 䏫 䝗 䮌 䰍
휵 畜 慉 搐 槒 䛙
휼 恤 譎 鷸 卹 潏 霱 遹 鐍 䘏 獝 䎉 谲 鹬 憰 烅 瞲 肷 賉 㤜 㳚 䢕 䬄
흉 凶 胸 匈 兇 洶 胷 恟 汹 㐫 忷 訩 讻 哅 詾 㓙 㕳 㕼 䠗
@@ -577,6 +577,6 @@
흠 欽 欠 歆 廞 揿 钦 噷 嬜 撳 鑫 㐸 䜗
흡 吸 洽 恰 扱 翕 潝 歙 噏 翖 熻 闟 㩉 㒆 㡊 㪧 㬛 㲸 㽏 䁯 䞩 䨐 䶋
흥 興 兴 嬹 臖 馫 㒷 䕟
희 希 喜 稀 戱 嬉 熙 噫 羲 禧 犧 姬 熹 憙 愾 僖 曦 凞 晞 憘 熺 囍 戲 嘻 餼 燹 欷 唏 暿 咥 悕 豨 爔
희 希 喜 稀 戱 嬉 熙 噫 羲 禧 犧 姬 熹 憙 愾 僖 曦 凞 晞 憘 熺 囍 戲 嘻 餼 燹 欷 唏 暿 咥 悕 豨 爔 黖 戯 煕 餙 犠 俙 巇 屭 狶 㶊 饎 浠 嚱 橲 熈 睎 蟢 誒 㬢 呬 桸 琋 繥 霼 㱆 屃 戏 牺 诶 饩 屓 忥 怬 歖 烯 焁 焈 熂 燨 犔 瞦 礂 莃 譆 豷 隵 鯑 鱚 鵗 齂 㑶 㙿 㚀 㚦 㜯 㝆 㠻 㩬 㰕 㰨 㷗 㷩 㸍 㹷 㺣 䂀 䊠 䐅 䖒 䖷 䛥 䨳 䮎 姫
히 屎 忾 㕧 䐖 䦙
힐 詰 黠 頡 纈 犵 襭 肹 擷 肸 撷 缬 诘 颉 恄 欯 翓 㩪

View File

@@ -42,10 +42,12 @@ so そ
zo ぞ
ta た
da だ
di ぢ
ti ち
chi ち
cha ちゃ
chu ちゅ
che ちぇ
tyu ちゅ
cho ちょ
ji じ
@@ -83,6 +85,10 @@ pyu ぴゅ
pyo ぴょ
hu ふ
fu ふ
fa ふぁ
fi ふぃ
fe ふぇ
fo ふぉ
bu ぶ
vu ぶ
pu ぷ
@@ -124,47 +130,23 @@ xi ぃ
xu ぅ
xe ぇ
xo ぉ
kka
kki
kkya っきゃ
kkyu っきゅ
kkyo っきょ
kku
kke
kko
ssa
ssi
sshi
ssha っしゃ
sshu っしゅ
sshe っしぇ
ssho っしょ
ssyo っしょ
ssu
sse
sso っそ
tta った
dda っだ
tti っち
cchi っち
ccha っちゃ
cchu っちゅ
ttyu っちゅ
ccho っちょ
ttu っつ
ttsu っつ
ddu っづ
tte って
dde っで
tto っと
ddo っど
ppa っぱ
ppi っぴ
ppya っぴゃ
ppyu っぴゅ
ppyo っぴょ
ppu っぷ
ppe っぺ
ppo っぽ
kk っ
ss
tt っ
pp っ
cc っ
gg
zz
jj
dd
bb
hh
ff っ
mm っ
yy っ
rr っ
ww っ
vv
tch
. 。
, 、

View File

@@ -128,6 +128,17 @@ pp ッ
tt ッ
tch ッ
ss ッ
gg ッ
zz ッ
jj ッ
bb ッ
hh ッ
ff ッ
mm ッ
yy ッ
rr ッ
ww ッ
vv ッ
di ディ
fa ファ
fi フィ

View File

@@ -6,7 +6,7 @@ from pathlib import Path
SOURCE = Path(__file__).with_name("hanja.src")
MAXCANDIDATES = 32
MAXCANDIDATES = 128
def ishangul(s):

View File

@@ -55,11 +55,15 @@ def vowel2():
tab = [
("oa", "oa", "a"), ("oe", "oe", "e"), ("ai", "ai", "a"),
("ao", "ao", "a"), ("au", "au", "a"), ("ay", "ay", "a"),
("eu", "eu", "e"), ("iu", "iu", "i"), ("oi", "oi", "o"),
("eo", "eo", "e"), ("eu", "eu", "e"), ("ia", "ia", "i"),
("iu", "iu", "i"), ("oi", "oi", "o"), ("ua", "ua", "u"),
("ui", "ui", "u"), ("uy", "uy", "y"),
("aau", "âu", "â"), ("aay", "ây", "â"), ("eeu", "êu", "ê"),
("ooi", "ôi", "ô"), ("owi", "ơi", "ơ"), ("uwi", "ưi", "ư"),
("uwu", "ưu", "ư"), ("uwa", "ưa", "ư"), ("uee", "", "ê"),
("iee", "", "ê"), ("yee", "", "ê"), ("uoo", "", "ô"),
("uow", "ươ", "ơ"), ("uaa", "", "â"), ("oaw", "", "ă"),
("uwa", "ưa", "a"), ("uwow", "ươ", "ơ"),
("uwow", "ươ", "ơ"),
]
for i, o, v in tab:
emit(i, o)
@@ -68,21 +72,21 @@ def vowel2():
emit("ie", "ie")
emit("ye", "ye")
emit("uo", "uo")
emit("ua", "ua")
def vowel3():
# input, output, vowel
tab = [
("ieeu", "iêu", "ê"), ("yeeu", "yêu", "ê"),
("uooi", "uôi", "ô"), ("uowi", "ươi", "ơ"),
("oai", "oai", "a"), ("oay", "oay", "a"),
("uyee", "uyê", "ê"),
("uowu", "ươu", "ơ"), ("uwowi", "ươi", "ơ"),
("uwowu", "ươu", "ơ"), ("uaay", "uây", "â"),
("oai", "oai", "a"), ("oay", "oay", "a"), ("oeo", "oeo", "e"),
("uya", "uya", "y"), ("uyu", "uyu", "y"), ("uyee", "uyê", "ê"),
]
for i, o, v in tab:
emit(i, o)
for t in tone:
emit(i+t, o.replace(v, addtone(v, t), 1))
emit("uya", "uya")
def modvowels():
for i, o in modvowel:
@@ -132,10 +136,11 @@ def final():
("aw", "ă", "ă"), ("aa", "â", "â"), ("ee", "ê", "ê"),
("oo", "ô", "ô"), ("ow", "ơ", "ơ"), ("uw", "ư", "ư"),
("iee", "", "ê"), ("yee", "", "ê"), ("uoo", "", "ô"),
("uow", "ươ", "ơ"), ("uaa", "", "â"), ("oaw", "", "ă"),
("uow", "ươ", "ơ"), ("uwow", "ươ", "ơ"), ("uyee", "uyê", "ê"),
("uaa", "", "â"), ("uee", "", "ê"), ("oaw", "", "ă"),
("ai", "ai", "a"), ("ao", "ao", "a"), ("au", "au", "a"),
("ay", "ay", "a"), ("oa", "oa", "a"), ("oi", "oi", "o"),
("ui", "ui", "u"), ("uy", "uy", "y"),
("ay", "ay", "a"), ("oa", "oa", "a"), ("oe", "oe", "e"),
("oi", "oi", "o"), ("ui", "ui", "u"), ("uy", "uy", "y"),
]
for i, o, v in tab:
for c in coda:

View File

@@ -142,6 +142,18 @@ ayx ãy
Ayx Ãy
ayj ạy
Ayj Ạy
eo eo
Eo Eo
eos éo
Eos Éo
eof èo
Eof Èo
eor ẻo
Eor Ẻo
eox ẽo
Eox Ẽo
eoj ẹo
Eoj Ẹo
eu eu
Eu Eu
eus éu
@@ -154,6 +166,18 @@ eux ẽu
Eux Ẽu
euj ẹu
Euj Ẹu
ia ia
Ia Ia
ias ía
Ias Ía
iaf ìa
Iaf Ìa
iar ỉa
Iar Ỉa
iax ĩa
Iax Ĩa
iaj ịa
Iaj Ịa
iu iu
Iu Iu
ius íu
@@ -178,6 +202,18 @@ oix õi
Oix Õi
oij ọi
Oij Ọi
ua ua
Ua Ua
uas úa
Uas Úa
uaf ùa
Uaf Ùa
uar ủa
Uar Ủa
uax ũa
Uax Ũa
uaj ụa
Uaj Ụa
ui ui
Ui Ui
uis úi
@@ -202,6 +238,114 @@ uyx uỹ
Uyx Uỹ
uyj uỵ
Uyj Uỵ
aau âu
Aau Âu
aaus ấu
Aaus Ấu
aauf ầu
Aauf Ầu
aaur ẩu
Aaur Ẩu
aaux ẫu
Aaux Ẫu
aauj ậu
Aauj Ậu
aay ây
Aay Ây
aays ấy
Aays Ấy
aayf ầy
Aayf Ầy
aayr ẩy
Aayr Ẩy
aayx ẫy
Aayx Ẫy
aayj ậy
Aayj Ậy
eeu êu
Eeu Êu
eeus ếu
Eeus Ếu
eeuf ều
Eeuf Ều
eeur ểu
Eeur Ểu
eeux ễu
Eeux Ễu
eeuj ệu
Eeuj Ệu
ooi ôi
Ooi Ôi
oois ối
Oois Ối
ooif ồi
Ooif Ồi
ooir ổi
Ooir Ổi
ooix ỗi
Ooix Ỗi
ooij ội
Ooij Ội
owi ơi
Owi Ơi
owis ới
Owis Ới
owif ời
Owif Ời
owir ởi
Owir Ởi
owix ỡi
Owix Ỡi
owij ợi
Owij Ợi
uwi ưi
Uwi Ưi
uwis ứi
Uwis Ứi
uwif ừi
Uwif Ừi
uwir ửi
Uwir Ửi
uwix ữi
Uwix Ữi
uwij ựi
Uwij Ựi
uwu ưu
Uwu Ưu
uwus ứu
Uwus Ứu
uwuf ừu
Uwuf Ừu
uwur ửu
Uwur Ửu
uwux ữu
Uwux Ữu
uwuj ựu
Uwuj Ựu
uwa ưa
Uwa Ưa
uwas ứa
Uwas Ứa
uwaf ừa
Uwaf Ừa
uwar ửa
Uwar Ửa
uwax ữa
Uwax Ữa
uwaj ựa
Uwaj Ựa
uee uê
Uee Uê
uees uế
Uees Uế
ueef uề
Ueef Uề
ueer uể
Ueer Uể
ueex uễ
Ueex Uễ
ueej uệ
Ueej Uệ
iee iê
Iee Iê
iees iế
@@ -274,18 +418,6 @@ oawx oẵ
Oawx Oẵ
oawj oặ
Oawj Oặ
uwa ưa
Uwa Ưa
uwas ưá
Uwas Ưá
uwaf ưà
Uwaf Ưà
uwar ưả
Uwar Ưả
uwax ưã
Uwax Ưã
uwaj ưạ
Uwaj Ưạ
uwow ươ
Uwow Ươ
uwows ướ
@@ -304,8 +436,6 @@ ye ye
Ye Ye
uo uo
Uo Uo
ua ua
Ua Ua
ieeu iêu
Ieeu Iêu
ieeus iếu
@@ -354,6 +484,54 @@ uowix ưỡi
Uowix Ưỡi
uowij ượi
Uowij Ượi
uowu ươu
Uowu Ươu
uowus ướu
Uowus Ướu
uowuf ườu
Uowuf Ườu
uowur ưởu
Uowur Ưởu
uowux ưỡu
Uowux Ưỡu
uowuj ượu
Uowuj Ượu
uwowi ươi
Uwowi Ươi
uwowis ưới
Uwowis Ưới
uwowif ười
Uwowif Ười
uwowir ưởi
Uwowir Ưởi
uwowix ưỡi
Uwowix Ưỡi
uwowij ượi
Uwowij Ượi
uwowu ươu
Uwowu Ươu
uwowus ướu
Uwowus Ướu
uwowuf ườu
Uwowuf Ườu
uwowur ưởu
Uwowur Ưởu
uwowux ưỡu
Uwowux Ưỡu
uwowuj ượu
Uwowuj Ượu
uaay uây
Uaay Uây
uaays uấy
Uaays Uấy
uaayf uầy
Uaayf Uầy
uaayr uẩy
Uaayr Uẩy
uaayx uẫy
Uaayx Uẫy
uaayj uậy
Uaayj Uậy
oai oai
Oai Oai
oais oái
@@ -378,6 +556,42 @@ oayx oãy
Oayx Oãy
oayj oạy
Oayj Oạy
oeo oeo
Oeo Oeo
oeos oéo
Oeos Oéo
oeof oèo
Oeof Oèo
oeor oẻo
Oeor Oẻo
oeox oẽo
Oeox Oẽo
oeoj oẹo
Oeoj Oẹo
uya uya
Uya Uya
uyas uýa
Uyas Uýa
uyaf uỳa
Uyaf Uỳa
uyar uỷa
Uyar Uỷa
uyax uỹa
Uyax Uỹa
uyaj uỵa
Uyaj Uỵa
uyu uyu
Uyu Uyu
uyus uýu
Uyus Uýu
uyuf uỳu
Uyuf Uỳu
uyur uỷu
Uyur Uỷu
uyux uỹu
Uyux Uỹu
uyuj uỵu
Uyuj Uỵu
uyee uyê
Uyee Uyê
uyees uyế
@@ -390,8 +604,6 @@ uyeex uyễ
Uyeex Uyễ
uyeej uyệ
Uyeej Uyệ
uya uya
Uya Uya
aw ă
Aw Ă
aa â
@@ -2146,6 +2358,198 @@ uownhx ưỡnh
Uownhx Ưỡnh
uownhj ượnh
Uownhj Ượnh
uwowc ươc
Uwowc Ươc
uwowcs ước
Uwowcs Ước
uwowcf ườc
Uwowcf Ườc
uwowcr ưởc
Uwowcr Ưởc
uwowcx ưỡc
Uwowcx Ưỡc
uwowcj ược
Uwowcj Ược
uwowm ươm
Uwowm Ươm
uwowms ướm
Uwowms Ướm
uwowmf ườm
Uwowmf Ườm
uwowmr ưởm
Uwowmr Ưởm
uwowmx ưỡm
Uwowmx Ưỡm
uwowmj ượm
Uwowmj Ượm
uwown ươn
Uwown Ươn
uwowns ướn
Uwowns Ướn
uwownf ườn
Uwownf Ườn
uwownr ưởn
Uwownr Ưởn
uwownx ưỡn
Uwownx Ưỡn
uwownj ượn
Uwownj Ượn
uwowp ươp
Uwowp Ươp
uwowps ướp
Uwowps Ướp
uwowpf ườp
Uwowpf Ườp
uwowpr ưởp
Uwowpr Ưởp
uwowpx ưỡp
Uwowpx Ưỡp
uwowpj ượp
Uwowpj Ượp
uwowt ươt
Uwowt Ươt
uwowts ướt
Uwowts Ướt
uwowtf ườt
Uwowtf Ườt
uwowtr ưởt
Uwowtr Ưởt
uwowtx ưỡt
Uwowtx Ưỡt
uwowtj ượt
Uwowtj Ượt
uwowch ươch
Uwowch Ươch
uwowchs ướch
Uwowchs Ướch
uwowchf ườch
Uwowchf Ườch
uwowchr ưởch
Uwowchr Ưởch
uwowchx ưỡch
Uwowchx Ưỡch
uwowchj ượch
Uwowchj Ượch
uwowng ương
Uwowng Ương
uwowngs ướng
Uwowngs Ướng
uwowngf ường
Uwowngf Ường
uwowngr ưởng
Uwowngr Ưởng
uwowngx ưỡng
Uwowngx Ưỡng
uwowngj ượng
Uwowngj Ượng
uwownh ươnh
Uwownh Ươnh
uwownhs ướnh
Uwownhs Ướnh
uwownhf ườnh
Uwownhf Ườnh
uwownhr ưởnh
Uwownhr Ưởnh
uwownhx ưỡnh
Uwownhx Ưỡnh
uwownhj ượnh
Uwownhj Ượnh
uyeec uyêc
Uyeec Uyêc
uyeecs uyếc
Uyeecs Uyếc
uyeecf uyềc
Uyeecf Uyềc
uyeecr uyểc
Uyeecr Uyểc
uyeecx uyễc
Uyeecx Uyễc
uyeecj uyệc
Uyeecj Uyệc
uyeem uyêm
Uyeem Uyêm
uyeems uyếm
Uyeems Uyếm
uyeemf uyềm
Uyeemf Uyềm
uyeemr uyểm
Uyeemr Uyểm
uyeemx uyễm
Uyeemx Uyễm
uyeemj uyệm
Uyeemj Uyệm
uyeen uyên
Uyeen Uyên
uyeens uyến
Uyeens Uyến
uyeenf uyền
Uyeenf Uyền
uyeenr uyển
Uyeenr Uyển
uyeenx uyễn
Uyeenx Uyễn
uyeenj uyện
Uyeenj Uyện
uyeep uyêp
Uyeep Uyêp
uyeeps uyếp
Uyeeps Uyếp
uyeepf uyềp
Uyeepf Uyềp
uyeepr uyểp
Uyeepr Uyểp
uyeepx uyễp
Uyeepx Uyễp
uyeepj uyệp
Uyeepj Uyệp
uyeet uyêt
Uyeet Uyêt
uyeets uyết
Uyeets Uyết
uyeetf uyềt
Uyeetf Uyềt
uyeetr uyểt
Uyeetr Uyểt
uyeetx uyễt
Uyeetx Uyễt
uyeetj uyệt
Uyeetj Uyệt
uyeech uyêch
Uyeech Uyêch
uyeechs uyếch
Uyeechs Uyếch
uyeechf uyềch
Uyeechf Uyềch
uyeechr uyểch
Uyeechr Uyểch
uyeechx uyễch
Uyeechx Uyễch
uyeechj uyệch
Uyeechj Uyệch
uyeeng uyêng
Uyeeng Uyêng
uyeengs uyếng
Uyeengs Uyếng
uyeengf uyềng
Uyeengf Uyềng
uyeengr uyểng
Uyeengr Uyểng
uyeengx uyễng
Uyeengx Uyễng
uyeengj uyệng
Uyeengj Uyệng
uyeenh uyênh
Uyeenh Uyênh
uyeenhs uyếnh
Uyeenhs Uyếnh
uyeenhf uyềnh
Uyeenhf Uyềnh
uyeenhr uyểnh
Uyeenhr Uyểnh
uyeenhx uyễnh
Uyeenhx Uyễnh
uyeenhj uyệnh
Uyeenhj Uyệnh
uaac uâc
Uaac Uâc
uaacs uấc
@@ -2242,6 +2646,102 @@ uaanhx uẫnh
Uaanhx Uẫnh
uaanhj uậnh
Uaanhj Uậnh
ueec uêc
Ueec Uêc
ueecs uếc
Ueecs Uếc
ueecf uềc
Ueecf Uềc
ueecr uểc
Ueecr Uểc
ueecx uễc
Ueecx Uễc
ueecj uệc
Ueecj Uệc
ueem uêm
Ueem Uêm
ueems uếm
Ueems Uếm
ueemf uềm
Ueemf Uềm
ueemr uểm
Ueemr Uểm
ueemx uễm
Ueemx Uễm
ueemj uệm
Ueemj Uệm
ueen uên
Ueen Uên
ueens uến
Ueens Uến
ueenf uền
Ueenf Uền
ueenr uển
Ueenr Uển
ueenx uễn
Ueenx Uễn
ueenj uện
Ueenj Uện
ueep uêp
Ueep Uêp
ueeps uếp
Ueeps Uếp
ueepf uềp
Ueepf Uềp
ueepr uểp
Ueepr Uểp
ueepx uễp
Ueepx Uễp
ueepj uệp
Ueepj Uệp
ueet uêt
Ueet Uêt
ueets uết
Ueets Uết
ueetf uềt
Ueetf Uềt
ueetr uểt
Ueetr Uểt
ueetx uễt
Ueetx Uễt
ueetj uệt
Ueetj Uệt
ueech uêch
Ueech Uêch
ueechs uếch
Ueechs Uếch
ueechf uềch
Ueechf Uềch
ueechr uểch
Ueechr Uểch
ueechx uễch
Ueechx Uễch
ueechj uệch
Ueechj Uệch
ueeng uêng
Ueeng Uêng
ueengs uếng
Ueengs Uếng
ueengf uềng
Ueengf Uềng
ueengr uểng
Ueengr Uểng
ueengx uễng
Ueengx Uễng
ueengj uệng
Ueengj Uệng
ueenh uênh
Ueenh Uênh
ueenhs uếnh
Ueenhs Uếnh
ueenhf uềnh
Ueenhf Uềnh
ueenhr uểnh
Ueenhr Uểnh
ueenhx uễnh
Ueenhx Uễnh
ueenhj uệnh
Ueenhj Uệnh
oawc oăc
Oawc Oăc
oawcs oắc
@@ -2818,6 +3318,102 @@ oanhx oãnh
Oanhx Oãnh
oanhj oạnh
Oanhj Oạnh
oec oec
Oec Oec
oecs oéc
Oecs Oéc
oecf oèc
Oecf Oèc
oecr oẻc
Oecr Oẻc
oecx oẽc
Oecx Oẽc
oecj oẹc
Oecj Oẹc
oem oem
Oem Oem
oems oém
Oems Oém
oemf oèm
Oemf Oèm
oemr oẻm
Oemr Oẻm
oemx oẽm
Oemx Oẽm
oemj oẹm
Oemj Oẹm
oen oen
Oen Oen
oens oén
Oens Oén
oenf oèn
Oenf Oèn
oenr oẻn
Oenr Oẻn
oenx oẽn
Oenx Oẽn
oenj oẹn
Oenj Oẹn
oep oep
Oep Oep
oeps oép
Oeps Oép
oepf oèp
Oepf Oèp
oepr oẻp
Oepr Oẻp
oepx oẽp
Oepx Oẽp
oepj oẹp
Oepj Oẹp
oet oet
Oet Oet
oets oét
Oets Oét
oetf oèt
Oetf Oèt
oetr oẻt
Oetr Oẻt
oetx oẽt
Oetx Oẽt
oetj oẹt
Oetj Oẹt
oech oech
Oech Oech
oechs oéch
Oechs Oéch
oechf oèch
Oechf Oèch
oechr oẻch
Oechr Oẻch
oechx oẽch
Oechx Oẽch
oechj oẹch
Oechj Oẹch
oeng oeng
Oeng Oeng
oengs oéng
Oengs Oéng
oengf oèng
Oengf Oèng
oengr oẻng
Oengr Oẻng
oengx oẽng
Oengx Oẽng
oengj oẹng
Oengj Oẹng
oenh oenh
Oenh Oenh
oenhs oénh
Oenhs Oénh
oenhf oènh
Oenhf Oènh
oenhr oẻnh
Oenhr Oẻnh
oenhx oẽnh
Oenhx Oẽnh
oenhj oẹnh
Oenhj Oẹnh
oic oic
Oic Oic
oics óic
@@ -3153,10 +3749,18 @@ quay quay
Quay Quay
giay giay
Giay Giay
queo queo
Queo Queo
gieo gieo
Gieo Gieo
queu queu
Queu Queu
gieu gieu
Gieu Gieu
quia quia
Quia Quia
giia giia
Giia Giia
quiu quiu
Quiu Quiu
giiu giiu
@@ -3165,10 +3769,40 @@ quoi quoi
Quoi Quoi
gioi gioi
Gioi Gioi
giua giua
Giua Giua
giui giui
Giui Giui
giuy giuy
Giuy Giuy
quaau quâu
Quaau Quâu
giaau giâu
Giaau Giâu
quaay quây
Quaay Quây
giaay giây
Giaay Giây
queeu quêu
Queeu Quêu
gieeu giêu
Gieeu Giêu
quooi quôi
Quooi Quôi
giooi giôi
Giooi Giôi
quowi quơi
Quowi Quơi
giowi giơi
Giowi Giơi
giuwi giưi
Giuwi Giưi
giuwu giưu
Giuwu Giưu
giuwa giưa
Giuwa Giưa
giuee giuê
Giuee Giuê
quiee quiê
Quiee Quiê
giiee giiê
@@ -3187,8 +3821,6 @@ quoaw quoă
Quoaw Quoă
gioaw gioă
Gioaw Gioă
giuwa giưa
Giuwa Giưa
giuwow giươ
Giuwow Giươ
quie quie
@@ -3201,8 +3833,6 @@ giye giye
Giye Giye
giuo giuo
Giuo Giuo
giua giua
Giua Giua
quieeu quiêu
Quieeu Quiêu
giieeu giiêu
@@ -3215,6 +3845,14 @@ giuooi giuôi
Giuooi Giuôi
giuowi giươi
Giuowi Giươi
giuowu giươu
Giuowu Giươu
giuwowi giươi
Giuwowi Giươi
giuwowu giươu
Giuwowu Giươu
giuaay giuây
Giuaay Giuây
quoai quoai
Quoai Quoai
gioai gioai
@@ -3223,10 +3861,16 @@ quoay quoay
Quoay Quoay
gioay gioay
Gioay Gioay
giuyee giuyê
Giuyee Giuyê
quoeo quoeo
Quoeo Quoeo
gioeo gioeo
Gioeo Gioeo
giuya giuya
Giuya Giuya
giuyu giuyu
Giuyu Giuyu
giuyee giuyê
Giuyee Giuyê
quaw quă
Quaw Quă
giaw giă
@@ -3719,6 +4363,38 @@ giuowng giương
Giuowng Giương
giuownh giươnh
Giuownh Giươnh
giuwowc giươc
Giuwowc Giươc
giuwowm giươm
Giuwowm Giươm
giuwown giươn
Giuwown Giươn
giuwowp giươp
Giuwowp Giươp
giuwowt giươt
Giuwowt Giươt
giuwowch giươch
Giuwowch Giươch
giuwowng giương
Giuwowng Giương
giuwownh giươnh
Giuwownh Giươnh
giuyeec giuyêc
Giuyeec Giuyêc
giuyeem giuyêm
Giuyeem Giuyêm
giuyeen giuyên
Giuyeen Giuyên
giuyeep giuyêp
Giuyeep Giuyêp
giuyeet giuyêt
Giuyeet Giuyêt
giuyeech giuyêch
Giuyeech Giuyêch
giuyeeng giuyêng
Giuyeeng Giuyêng
giuyeenh giuyênh
Giuyeenh Giuyênh
giuaac giuâc
Giuaac Giuâc
giuaam giuâm
@@ -3735,6 +4411,22 @@ giuaang giuâng
Giuaang Giuâng
giuaanh giuânh
Giuaanh Giuânh
giueec giuêc
Giueec Giuêc
giueem giuêm
Giueem Giuêm
giueen giuên
Giueen Giuên
giueep giuêp
Giueep Giuêp
giueet giuêt
Giueet Giuêt
giueech giuêch
Giueech Giuêch
giueeng giuêng
Giueeng Giuêng
giueenh giuênh
Giueenh Giuênh
quoawc quoăc
Quoawc Quoăc
gioawc gioăc
@@ -3927,6 +4619,38 @@ quoanh quoanh
Quoanh Quoanh
gioanh gioanh
Gioanh Gioanh
quoec quoec
Quoec Quoec
gioec gioec
Gioec Gioec
quoem quoem
Quoem Quoem
gioem gioem
Gioem Gioem
quoen quoen
Quoen Quoen
gioen gioen
Gioen Gioen
quoep quoep
Quoep Quoep
gioep gioep
Gioep Gioep
quoet quoet
Quoet Quoet
gioet gioet
Gioet Gioet
quoech quoech
Quoech Quoech
gioech gioech
Gioech Gioech
quoeng quoeng
Quoeng Quoeng
gioeng gioeng
Gioeng Gioeng
quoenh quoenh
Quoenh Quoenh
gioenh gioenh
Gioenh Gioenh
quoic quoic
Quoic Quoic
gioic gioic

View File

@@ -1,33 +1,269 @@
#include "dat.h"
#include "fn.h"
int
popupcells(Rune *r, int n)
{
int i, w;
enum {
Asciitofull = 0xFEE0,
};
int popupscale = 1;
static void
fill(u32int *buf, int n, u32int color)
{
int i;
w = 0;
for(i = 0; i < n; i++)
if(r[i] != 0xfe0e && r[i] != 0xfe0f)
w++;
return w;
buf[i] = color;
}
static void
fillrect(u32int *buf, int w, int h, int x, int y, int rw, int rh,
u32int color)
{
int x0, x1, y0, y1;
x0 = min(max(x, 0), w);
x1 = min(max(x + max(rw, 0), 0), w);
y0 = min(max(y, 0), h);
y1 = min(max(y + max(rh, 0), 0), h);
for(; y0 < y1; y0++)
fill(buf + y0*w + x0, max(x1 - x0, 0), color);
}
static int
validarea(Area *a)
{
return a != nil && a->w > 0 && a->h > 0;
}
static vlong
areadistance(Area *a, int x, int y)
{
vlong dx, dy, right, bottom;
right = (vlong)a->x + a->w;
bottom = (vlong)a->y + a->h;
dx = x < a->x ? (vlong)a->x - x : x >= right ? x - right : 0;
dy = y < a->y ? (vlong)a->y - y : y >= bottom ? y - bottom : 0;
return dx + dy;
}
void
popupposition(Caret *caret, int pointerx, int pointery, int screenw,
int screenh, int w, int h, int *x, int *y)
popuparea(Area *mon, int nmon, Area *work, int x, int y, Area *out)
{
vlong px, py, xmax, ymax;
vlong best, bottom, d, right, x0, x1, y0, y1;
int i, pick;
memset(out, 0, sizeof *out);
pick = -1;
best = (vlong)1 << 62;
for(i = 0; i < nmon; i++){
if(!validarea(&mon[i]))
continue;
right = (vlong)mon[i].x + mon[i].w;
bottom = (vlong)mon[i].y + mon[i].h;
if(x >= mon[i].x && x < right &&
y >= mon[i].y && y < bottom){
pick = i;
break;
}
d = areadistance(&mon[i], x, y);
if(d < best){
best = d;
pick = i;
}
}
if(pick < 0)
return;
*out = mon[pick];
if(!validarea(work))
return;
x0 = max((vlong)out->x, work->x);
y0 = max((vlong)out->y, work->y);
x1 = min((vlong)out->x + out->w, (vlong)work->x + work->w);
y1 = min((vlong)out->y + out->h, (vlong)work->y + work->h);
if(x1 <= x0 || y1 <= y0)
return;
out->x = x0;
out->y = y0;
out->w = x1 - x0;
out->h = y1 - y0;
}
int
pagemarker(char *buf, int nbuf, int first, int shown, int total)
{
if(nbuf <= 0)
return 0;
buf[0] = 0;
if(shown <= 0 || (first == 0 && shown >= total))
return 0;
return snprint(buf, nbuf, "%d-%d/%d", first + 1,
first + shown, total);
}
static int
fitrows(int h, int pre, int mark, int n)
{
int fixed;
fixed = 2*PopupPad + mark*Fontsz;
if(pre)
fixed += Fontsz + (n != 0 ? PopupSep : 0);
if(h <= fixed)
return 0;
return min(n, (h - fixed) / Fontsz);
}
void
popuplayout(Drawcmd *dc, int areaw, int areah, Popup *p)
{
char buf[32];
int first, i, markrow, nall, nmark, npre, pad, total, width, y;
memset(p, 0, sizeof *p);
p->prey = -1;
p->sepy = -1;
p->rowsy = -1;
p->marky = -1;
p->sely = -1;
pad = PopupPad;
nall = min(max(dc->nkouho, 0), Maxdisp);
npre = dc->pre.n != 0;
if((nall == 0 && !npre) || areaw <= 0 || areah <= 0)
return;
/* Rows first; a page marker when they do not all fit; and in a
* sliver of a work area the selected row alone, unpadded. */
total = max(dc->total, dc->first + nall);
markrow = dc->first > 0 || nall < total;
p->n = fitrows(areah, npre, markrow, nall);
if(p->n < nall && !markrow){
markrow = 1;
p->n = fitrows(areah, npre, markrow, nall);
}
if(p->n == 0 && markrow){
markrow = 0;
p->n = fitrows(areah, npre, markrow, nall);
}
if(p->n == 0 && nall != 0){
npre = 0;
pad = 0;
p->n = max(min(nall, areah / Fontsz), 1);
}
if(p->n != 0 && dc->sel >= p->n)
p->row0 = min(dc->sel - p->n + 1, nall - p->n);
first = dc->first + p->row0;
nmark = markrow ? pagemarker(buf, sizeof buf, first, p->n, total) : 0;
if(nmark == 0)
markrow = 0;
width = PopupBasew;
if(npre)
width = max(width, textwidth(&dc->pre) + 2*PopupPad);
for(i = 0; i < p->n; i++)
width = max(width, textwidth(&dc->kouho[p->row0+i]) +
PopupNumw + 2*PopupPad);
if(markrow){
sinit(&p->mark, buf, nmark);
p->markw = textwidth(&p->mark);
width = max(width, p->markw + 2*PopupPad);
}
p->w = min(width, areaw);
y = pad;
if(npre){
p->prey = y;
y += Fontsz;
}
if(npre && p->n != 0){
p->sepy = y;
y += PopupSep;
}
if(p->n != 0){
p->rowsy = y;
y += p->n*Fontsz;
}
if(markrow){
p->marky = y;
y += Fontsz;
}
p->h = min(y + pad, areah);
p->textw = max(p->w - 2*PopupPad - PopupNumw, 0);
if(markrow){
p->markw = min(p->markw, max(p->w - 2*PopupPad, 0));
p->markx = p->w - PopupPad - p->markw;
}
p->selw = max(p->w - 2*PopupPad, 0);
if(dc->sel >= p->row0 && dc->sel < p->row0 + p->n)
p->sely = p->rowsy + (dc->sel - p->row0)*Fontsz;
}
void
popupdraw(u32int *img, Drawcmd *dc, Popup *p)
{
Str num;
u32int color;
int i, j, y;
if(img == nil || p->n < 0 || p->n > Maxdisp ||
p->row0 < 0 || p->row0 + p->n > dc->nkouho ||
p->w <= 0 || p->h <= 0 || p->h > Imgh)
return;
fill(img, p->w*p->h, Colbg);
if(p->prey >= 0)
textdraw(img, p->w, p->h, PopupPad, p->prey,
max(p->w - 2*PopupPad, 0), Colfg, &dc->pre);
if(p->sepy >= 0)
fillrect(img, p->w, p->h, PopupPad, p->sepy,
p->w - 2*PopupPad, PopupSep, Colsep);
if(p->sely >= 0)
fillrect(img, p->w, p->h, PopupPad, p->sely,
p->selw, Fontsz, Colsel);
for(i = 0, y = p->rowsy; i < p->n; i++, y += Fontsz){
j = p->row0 + i;
color = j == dc->sel ? Colselfg : Colfg;
sclear(&num);
sputr(&num, '1' + j + Asciitofull);
textdraw(img, p->w, p->h, PopupPad, y,
min(PopupNumw, max(p->w - 2*PopupPad, 0)), color, &num);
textdraw(img, p->w, p->h, PopupPad + PopupNumw, y, p->textw,
color, &dc->kouho[j]);
}
if(p->marky >= 0)
textdraw(img, p->w, p->h, p->markx, p->marky, p->markw,
Colfg, &p->mark);
}
void
popupposition(Caret *caret, int pointerx, int pointery, Area *area,
int w, int h, int *x, int *y)
{
vlong below, bottom, px, py, right, xmax, ymax;
if(!validarea(area)){
*x = 0;
*y = 0;
return;
}
right = (vlong)area->x + area->w;
bottom = (vlong)area->y + area->h;
if(caret->valid){
px = caret->x;
py = (vlong)caret->y + max(caret->h, 0);
below = (vlong)caret->y + max(caret->h, 0);
if(below >= area->y && below + h <= bottom)
py = below;
else
py = (vlong)caret->y - h;
}else{
px = (vlong)pointerx + 10;
py = (vlong)pointery + 10;
if(py + h > bottom)
py = (vlong)pointery - 10 - h;
}
xmax = max((vlong)screenw - w, 0);
ymax = max((vlong)screenh - h, 0);
*x = max(0, min(px, xmax));
*y = max(0, min(py, ymax));
xmax = max(right - w, area->x);
ymax = max(bottom - h, area->y);
*x = max((vlong)area->x, min(px, xmax));
*y = max((vlong)area->y, min(py, ymax));
}

33
run.sh
View File

@@ -2,48 +2,23 @@
cd "$(dirname "$0")" || exit 1
if test "$#" -eq 0; then
set --
for font in \
/usr/share/fonts/TTF/DejaVuSans.ttf \
/usr/share/fonts/TTF/Jigmo.ttf \
/usr/share/fonts/TTF/Jigmo2.ttf \
/usr/share/fonts/noto/NotoSansCJK-Regular.ttc
do
test -f "$font" && set -- "$@" "$font"
done
if test "$#" -eq 0; then
echo "run.sh: no default font found; pass a font file" >&2
exit 1
fi
if test "$#" -ne 0; then
echo "usage: run.sh" >&2
exit 1
fi
if ! test -x ./strans; then
echo "run.sh: ./strans is not executable; run make first" >&2
exit 1
fi
if test -n "${DISPLAY-}" && ! test -x xim/strans-xim; then
echo "run.sh: xim/strans-xim is not executable; run make first" >&2
exit 1
fi
if ! command -v pkill >/dev/null 2>&1; then
echo "run.sh: pkill is required" >&2
exit 1
fi
uid=$(id -u) || exit 1
pkill -TERM -u "$uid" -x strans-xim 2>/dev/null || :
pkill -TERM -u "$uid" -x strans 2>/dev/null || :
sleep 0.1
pkill -KILL -u "$uid" -x strans-xim 2>/dev/null || :
pkill -KILL -u "$uid" -x strans 2>/dev/null || :
./strans map "$@" </dev/null &
strans_pid=$!
if test -n "${DISPLAY-}"; then
xim/strans-xim </dev/null &
xim_pid=$!
echo "run.sh: started daemon $strans_pid and XIM $xim_pid"
else
echo "run.sh: started daemon $strans_pid; DISPLAY is unset"
fi
./strans map </dev/null &

122
srv.c
View File

@@ -5,56 +5,108 @@
#include <sys/un.h>
static char adir[256];
static char sockpath[sizeof(((struct sockaddr_un*)0)->sun_path)];
static dev_t sockdev;
static ino_t sockino;
static Channel *clientc;
static int
srvreadkey(int fd, Keyreq *kr)
/* Removes the socket only while it is still the one we announced. */
static void
srvunlink(void)
{
uchar req[Ipcreqsz];
u32int ks, mod;
int want;
struct stat st;
if(ipcreadn(fd, req, sizeof req) < 0)
return -1;
ipcunpackreq(req, &want, &mod, &ks);
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
kr->ks = ks;
kr->mod = mod;
memset(&kr->caret, 0, sizeof kr->caret);
return want;
if(sockpath[0] != '\0' && lstat(sockpath, &st) == 0 &&
st.st_dev == sockdev && st.st_ino == sockino)
unlink(sockpath);
sockpath[0] = '\0';
}
static int
srvnote(void *v, char *note)
{
USED(v);
USED(note);
srvunlink();
return 0;
}
static int
srvreadreq(int fd, Keyreq *kr, int *want)
{
uchar req[Ipccaretsz];
int valid;
int32_t x, y, h;
if(ipcreadn(fd, req, Ipcreqsz) < 0)
return -1;
*want = 0;
kr->ks = 0;
kr->mod = 0;
switch(ipcreqtype(req)){
case Ipckey:
ipcunpackreq(req, want, &kr->mod, &kr->ks);
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
return 0;
case Ipccap:
*want = (req[0] & Ipcreqwant) != 0;
kr->op = Keycap;
return 0;
case Ipccaret:
if(ipcreadn(fd, req + Ipcreqsz, Ipccaretsz - Ipcreqsz) < 0 ||
ipcunpackcaret(req, &valid, &x, &y, &h) < 0)
return -1;
kr->op = Keycaret;
kr->caret.valid = valid;
kr->caret.x = x;
kr->caret.y = y;
kr->caret.h = h;
return 0;
}
return -1;
}
/*
* One Keyreq persists for the connection: the negotiated capability and
* the last caret ride along with every request, and the socket closing
* releases the engine.
*/
static void
clientthread(void *arg)
{
Channel *reply;
int fd;
Keyreq kr;
Keyres res;
uchar out[Ipcmaxresp];
uchar out[Ipcmaxresp], token;
char commit[Maxutf], preedit[Maxutf];
int n, ncommit, npreedit, want;
uchar token;
int fd, n, ncommit, npreedit, want;
fd = (int)(uintptr)arg;
threadsetname("client %d", fd);
reply = chancreate(sizeof(Keyres), 0);
kr.reply = reply;
memset(&kr, 0, sizeof kr);
kr.reply = chancreate(sizeof(Keyres), 0);
kr.owner = &fd;
while((want = srvreadkey(fd, &kr)) >= 0){
kr.cap = Cclientpreedit;
while(srvreadreq(fd, &kr, &want) >= 0){
if(kr.op != Keycaret)
kr.cap = want ? Cclientpreedit : 0;
chansend(keyc, &kr);
chanrecv(reply, &res);
chanrecv(kr.reply, &res);
if(kr.op == Keycaret)
continue;
ncommit = stoutf(&res.commit, commit, sizeof commit);
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
n = ipcpackresp(out, sizeof out, res.eaten,
/* A capability reply is always eaten: it marks the extension. */
n = ipcpackresp(out, sizeof out, kr.op == Keycap || res.eaten,
commit, ncommit, preedit, npreedit, want);
if(n < 0 || ipcsend(fd, out, n) < 0)
break;
}
kr.op = Keyrelease;
kr.ks = 0;
kr.mod = 0;
chansend(keyc, &kr);
chanrecv(reply, &res);
chanfree(reply);
chanrecv(kr.reply, &res);
chanfree(kr.reply);
close(fd);
chanrecv(clientc, &token);
}
@@ -62,18 +114,23 @@ clientthread(void *arg)
void
srvinit(void)
{
char *addr, path[sizeof(((struct sockaddr_un*)0)->sun_path)];
struct stat st;
char *addr;
if(ipcpath(path, sizeof path) < 0)
if(ipcpath(sockpath, sizeof sockpath) < 0)
die("IPC path is too long");
addr = smprint("unix!%s", path);
addr = smprint("unix!%s", sockpath);
if(addr == nil)
die("out of memory");
if(announce(addr, adir) < 0)
die("IPC endpoint is already in use: %r");
free(addr);
if(chmod(path, 0600) < 0)
die("can't protect IPC endpoint: %s", path);
if(chmod(sockpath, 0600) < 0 || lstat(sockpath, &st) < 0)
die("can't protect IPC endpoint: %s", sockpath);
sockdev = st.st_dev;
sockino = st.st_ino;
atexit(srvunlink);
threadnotify(srvnote, 1);
}
void
@@ -94,9 +151,6 @@ srvthread(void*)
close(fd);
continue;
}
if(proccreate(clientthread, (void*)(uintptr)fd, 8192) < 0){
chanrecv(clientc, &token);
close(fd);
}
proccreate(clientthread, (void*)(uintptr)fd, 8192);
}
}

26
str.c
View File

@@ -1,24 +1,27 @@
#include "dat.h"
#include "fn.h"
void
/* Fills s from n bytes of UTF-8; whole, valid, and at most Maxrunes. */
int
sinit(Str *s, char *src, int n)
{
Str tmp = {0};
Rune r;
int len;
s->n = 0;
if(n < 0 || (n > 0 && src == nil))
return;
while(n > 0 && s->n < Maxrunes){
if(!fullrune(src, n))
break;
len = chartorune(&s->r[s->n], src);
if(len > n)
break;
s->n++;
while(n > 0){
if(tmp.n >= Maxrunes || !fullrune(src, n))
return 0;
len = chartorune(&r, src);
if((r == Runeerror && len == 1) || (r >= 0xd800 && r <= 0xdfff))
return 0;
tmp.r[tmp.n++] = r;
src += len;
n -= len;
}
*s = tmp;
return 1;
}
void
@@ -66,14 +69,13 @@ scmp(Str *a, Str *b)
return 0;
}
/* UTF-8 of s into buf[sz], NUL-terminated, whole runes only. */
int
stoutf(Str *s, char *buf, int sz)
{
char tmp[UTFmax];
int i, n, len;
if(sz <= 0)
return 0;
n = 0;
for(i = 0; i < s->n; i++){
len = runetochar(tmp, &s->r[i]);

815
strans.c

File diff suppressed because it is too large Load Diff

View File

@@ -1,71 +1,144 @@
CC = 9c
LD = 9l
HOSTCC = cc
CFLAGS = -std=c99 -Wall -Wextra -O2 -g -I.. -I../cutest
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
FT_LIBS = $(shell pkg-config --libs freetype2)
DBUS_CFLAGS = $(shell pkg-config --cflags dbus-1)
DBUS_LIBS = $(shell pkg-config --libs dbus-1)
IBUS_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
IBUS_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
LIBS = -lthread -lbio $(FT_LIBS) $(IBUS_LIBS)
PKG_CONFIG ?= pkg-config
CFLAGS ?= -O2 -g
UNIT_CPPFLAGS = -I.. -I../cutest
UNIT_CFLAGS = -std=c99 -Wall -Wextra
HOST_CFLAGS = -std=c99 -Wall -Wextra
TEXT_CFLAGS = $(shell $(PKG_CONFIG) --cflags pangocairo cairo fontconfig)
TEXT_LIBS = $(shell $(PKG_CONFIG) --libs pangocairo cairo fontconfig)
DBUS_CFLAGS = $(shell $(PKG_CONFIG) --cflags dbus-1)
DBUS_LIBS = $(shell $(PKG_CONFIG) --libs dbus-1)
GTK_CFLAGS = $(shell $(PKG_CONFIG) --cflags gtk+-3.0)
GTK_LIBS = $(shell $(PKG_CONFIG) --libs gtk+-3.0)
IBUS_CFLAGS = $(shell $(PKG_CONFIG) --cflags dbus-1 xkbcommon)
IBUS_LIBS = $(shell $(PKG_CONFIG) --libs dbus-1 xkbcommon)
IBUS_CLIENT_CFLAGS = $(shell $(PKG_CONFIG) --cflags ibus-1.0)
IBUS_CLIENT_LIBS = $(shell $(PKG_CONFIG) --libs ibus-1.0)
X11_CFLAGS = $(shell $(PKG_CONFIG) --cflags x11)
X11_LIBS = $(shell $(PKG_CONFIG) --libs x11)
XIM_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-imdkit xcb-xkb xkbcommon-x11)
XIM_LIBS = $(shell $(PKG_CONFIG) --libs xcb-imdkit xcb-xkb xkbcommon-x11)
UNIT_LDLIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS)
PROG = unit_test
LIVE = ibus_live_test ipc_live_test daemon_collision_test daemon_failure_test \
daemon_restart_test
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
STRESS = stress_test
LIVESRC = live.c live.h
LIVEBUSSRC = livebus.c livebus.h
SMOKE = ibus_live_test ibus_client_smoke gtk_live_test xim_live_test \
ipc_live_test
FAULT = daemon_collision_test daemon_failure_test daemon_restart_test
LIVE = $(SMOKE) $(FAULT)
TESTSRC = test_util.c str_test.c trie_test.c \
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
popup_test.c font_test.c ibus_test.c server_test.c
popup_test.c font_test.c ibus_test.c server_test.c keymap_test.c \
xim_adapter_test.c
TESTOBJ = $(TESTSRC:.c=.o)
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
PARENTSRC = str.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
font.c
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
OBJS = $(TESTOBJ) $(PARENTOBJ)
XIMOBJ = unit_xim_keymap.o
COMMONOBJ = $(TESTOBJ) $(PARENTOBJ) $(XIMOBJ)
OBJS = unit_test.o stress_test.o $(COMMONOBJ)
all: $(PROG) $(LIVE)
all: $(PROG) $(STRESS) $(LIVE)
check test: $(PROG) $(LIVE)
./$(PROG) $(TESTARGS)
./ibus_live_test ../strans ../map
check test: $(PROG)
./$(PROG) $(UNITARGS)
check-live: $(SMOKE) ../strans ../gtk/im-strans.so
./ibus_live_test ../strans ../map ./ibus_client_smoke
./gtk_live_test ../gtk/im-strans.so
./xim_live_test ../strans ../map
./ipc_live_test ../strans ../map
check-stress: $(STRESS) ibus_live_test ipc_live_test $(FAULT) ../strans
./$(STRESS)
./ibus_live_test --capacity ../strans ../map
./ipc_live_test --capacity ../strans ../map
./daemon_collision_test ../strans ../map
./daemon_failure_test ../strans
./daemon_restart_test ../strans ../map
$(PROG): $(OBJS)
$(LD) -o $@ $(OBJS) $(LIBS)
$(PROG): unit_test.o $(COMMONOBJ)
$(LD) $(LDFLAGS) -o $@ unit_test.o $(COMMONOBJ) $(UNIT_LDLIBS) \
$(LDLIBS)
ibus_live_test: ibus_live_test.c
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g $(DBUS_CFLAGS) -o $@ $< $(DBUS_LIBS)
$(STRESS): stress_test.o $(COMMONOBJ)
$(LD) $(LDFLAGS) -o $@ stress_test.o $(COMMONOBJ) $(UNIT_LDLIBS) \
$(LDLIBS)
ipc_live_test: ipc_live_test.c ../ipc.c ../ipc.h
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. -o $@ ipc_live_test.c ../ipc.c
stress_test.o: unit_test.c
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -DSTRESS \
-c -o $@ $<
daemon_collision_test: daemon_collision_test.c ../ipc.c ../ipc.h
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(DBUS_CFLAGS) -o $@ \
daemon_collision_test.c ../ipc.c $(DBUS_LIBS)
ibus_live_test: ibus_live_test.c $(LIVESRC) $(LIVEBUSSRC) ../ipc.c ../ipc.h
$(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
$(LDFLAGS) -o $@ ibus_live_test.c live.c livebus.c ../ipc.c \
$(DBUS_LIBS) $(LDLIBS)
daemon_failure_test: daemon_failure_test.c
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -o $@ $<
ibus_client_smoke: ibus_client_smoke.c
$(HOSTCC) $(CPPFLAGS) $(IBUS_CLIENT_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
$(LDFLAGS) -o $@ $< $(IBUS_CLIENT_LIBS) $(LDLIBS)
daemon_restart_test: daemon_restart_test.c ../ipc.c ../ipc.h
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(DBUS_CFLAGS) -o $@ \
daemon_restart_test.c ../ipc.c $(DBUS_LIBS)
gtk_live_test: gtk_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
$(HOSTCC) $(CPPFLAGS) -I.. $(GTK_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
$(LDFLAGS) -o $@ gtk_live_test.c live.c ../ipc.c $(GTK_LIBS) \
-pthread $(LDLIBS)
$(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h ../cutest/cutest.h
../gtk/im-strans.so: ../gtk/main.c ../ipc.c ../ipc.h
$(MAKE) -C ../gtk
../strans:
$(MAKE) -C .. strans
xim_live_test: xim_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
$(HOSTCC) $(CPPFLAGS) -I.. $(X11_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
$(LDFLAGS) -o $@ xim_live_test.c live.c ../ipc.c $(X11_LIBS) \
$(LDLIBS)
ipc_live_test: ipc_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
$(HOSTCC) $(CPPFLAGS) -I.. $(HOST_CFLAGS) $(CFLAGS) $(LDFLAGS) \
-o $@ ipc_live_test.c live.c ../ipc.c $(LDLIBS)
daemon_collision_test: daemon_collision_test.c $(LIVESRC) $(LIVEBUSSRC) \
../ipc.c ../ipc.h
$(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
$(LDFLAGS) -o $@ daemon_collision_test.c live.c livebus.c \
../ipc.c $(DBUS_LIBS) $(LDLIBS)
daemon_failure_test: daemon_failure_test.c $(LIVESRC) ../ipc.c ../ipc.h
$(HOSTCC) $(CPPFLAGS) -I.. $(HOST_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ \
daemon_failure_test.c live.c ../ipc.c $(LDLIBS)
daemon_restart_test: daemon_restart_test.c $(LIVESRC) $(LIVEBUSSRC) \
../ipc.c ../ipc.h
$(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
$(LDFLAGS) -o $@ daemon_restart_test.c live.c livebus.c ../ipc.c \
$(DBUS_LIBS) $(LDLIBS)
unit_test.o stress_test.o $(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h \
../cutest/cutest.h
engine_test.o: ../strans.c
ibus_test.o: CFLAGS += $(IBUS_CFLAGS)
ibus_test.o: UNIT_CPPFLAGS += $(IBUS_CFLAGS)
ibus_test.o: ../ibus.c
keymap_test.o xim_adapter_test.o: UNIT_CPPFLAGS += $(XIM_CFLAGS)
xim_adapter_test.o: ../xim/xim.c
server_test.o: ../srv.c
unit_font.o: CFLAGS += $(FT_CFLAGS)
unit_font.o: UNIT_CPPFLAGS += $(TEXT_CFLAGS)
unit_xim_keymap.o: ../xim/keymap.c
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(XIM_CFLAGS) $(UNIT_CFLAGS) \
$(CFLAGS) -c -o $@ $<
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
$(CC) $(CFLAGS) -c -o $@ $<
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -c -o $@ $<
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(OBJS) $(PROG) $(LIVE)
rm -f $(OBJS) $(PROG) $(STRESS) $(LIVE)
.PHONY: all check test clean
.PHONY: all check check-live check-stress test clean

File diff suppressed because it is too large Load Diff

View File

@@ -1,635 +1,138 @@
#define _GNU_SOURCE
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "live.h"
enum
{
Failtimeout = 8000,
Failtimeout = 4000,
};
typedef struct Test Test;
struct Test
{
pid_t child;
int errfd;
int notifyfd;
int runtimewd;
int buswd;
int endpointclean;
char root[256];
char runtime[320];
char config[320];
char ibus[384];
char bus[448];
char home[320];
char socket[384];
char badmap[320];
char missingfont[384];
char expected[768];
char err[4096];
size_t nerr;
};
static uint32_t endpointmask = IN_CREATE|IN_MOVED_TO|IN_MOVED_FROM|IN_ATTRIB|
IN_DELETE|IN_MODIFY|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF;
static int
fail(char *fmt, ...)
waitfailed(Daemon *d, char *badmap)
{
va_list ap;
fprintf(stderr, "daemon_failure_test: ");
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
return 0;
}
static int64_t
nowms(void)
{
struct timespec ts;
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
return -1;
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static int
leftms(int64_t deadline)
{
int64_t n;
n = nowms();
if(n < 0)
return -1;
n = deadline - n;
if(n <= 0)
return 0;
if(n > 0x7fffffff)
return 0x7fffffff;
return n;
}
static int
makedir(char *path)
{
if(mkdir(path, 0700) == 0)
return 1;
return fail("mkdir %s: %s", path, strerror(errno));
}
static void
readerrors(Test *t)
{
ssize_t n;
if(t->errfd < 0)
return;
while(t->nerr + 1 < sizeof t->err){
n = read(t->errfd, t->err + t->nerr,
sizeof t->err - t->nerr - 1);
if(n > 0){
t->nerr += n;
continue;
}
if(n < 0 && errno == EINTR)
continue;
break;
}
t->err[t->nerr] = '\0';
}
static void
showerrors(Test *t)
{
readerrors(t);
if(t->nerr != 0)
fprintf(stderr, "daemon_failure_test: daemon stderr:\n%s", t->err);
}
static int
setup(Test *t)
{
memset(t, 0, sizeof *t);
t->child = -1;
t->errfd = -1;
t->notifyfd = -1;
t->runtimewd = -1;
t->buswd = -1;
t->endpointclean = 1;
if(nowms() < 0)
return fail("read monotonic clock: %s", strerror(errno));
snprintf(t->root, sizeof t->root, "/tmp/strans-failure.XXXXXX");
if(mkdtemp(t->root) == NULL){
t->root[0] = '\0';
return fail("mkdtemp: %s", strerror(errno));
}
if(snprintf(t->runtime, sizeof t->runtime, "%s/runtime", t->root)
>= (int)sizeof t->runtime ||
snprintf(t->config, sizeof t->config, "%s/config", t->root)
>= (int)sizeof t->config ||
snprintf(t->ibus, sizeof t->ibus, "%s/ibus", t->config)
>= (int)sizeof t->ibus ||
snprintf(t->bus, sizeof t->bus, "%s/bus", t->ibus)
>= (int)sizeof t->bus ||
snprintf(t->home, sizeof t->home, "%s/home", t->root)
>= (int)sizeof t->home ||
snprintf(t->socket, sizeof t->socket, "%s/strans.sock", t->runtime)
>= (int)sizeof t->socket ||
snprintf(t->badmap, sizeof t->badmap, "%s/missing-map", t->root)
>= (int)sizeof t->badmap ||
snprintf(t->missingfont, sizeof t->missingfont,
"%s/missing-font.ttf", t->root) >= (int)sizeof t->missingfont ||
snprintf(t->expected, sizeof t->expected,
"strans: can't open: %s/hira.map\n", t->badmap)
>= (int)sizeof t->expected)
return fail("temporary path is too long");
if(!makedir(t->runtime) || !makedir(t->config) || !makedir(t->ibus) ||
!makedir(t->bus) || !makedir(t->home))
return 0;
t->notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
if(t->notifyfd < 0)
return fail("inotify_init1: %s", strerror(errno));
t->runtimewd = inotify_add_watch(t->notifyfd, t->runtime, endpointmask);
if(t->runtimewd < 0)
return fail("watch runtime directory: %s", strerror(errno));
t->buswd = inotify_add_watch(t->notifyfd, t->bus, endpointmask);
if(t->buswd < 0)
return fail("watch IBus directory: %s", strerror(errno));
return 1;
}
static int
startchild(Test *t, char *program)
{
int errpipe[2], fd;
long maxfd;
pid_t pid;
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0)
return fail("pipe2: %s", strerror(errno));
pid = fork();
if(pid < 0){
close(errpipe[0]);
close(errpipe[1]);
return fail("fork: %s", strerror(errno));
}
if(pid == 0){
close(errpipe[0]);
if(dup2(errpipe[1], STDERR_FILENO) < 0)
_exit(126);
close(errpipe[1]);
if(close_range(3, UINT_MAX, 0) < 0){
maxfd = sysconf(_SC_OPEN_MAX);
if(maxfd < 0)
maxfd = 1024;
for(fd = 3; fd < maxfd; fd++)
close(fd);
}
if(setenv("XDG_RUNTIME_DIR", t->runtime, 1) < 0 ||
setenv("XDG_CONFIG_HOME", t->config, 1) < 0 ||
setenv("HOME", t->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
unsetenv("IBUS_ADDRESS") < 0){
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
strerror(errno));
_exit(126);
}
execl(program, program, t->badmap, t->missingfont, (char*)0);
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
_exit(127);
}
close(errpipe[1]);
t->child = pid;
t->errfd = errpipe[0];
return 1;
}
static int
drainnotify(Test *t)
{
char buf[4096];
char *where;
struct inotify_event *ev;
ssize_t n;
size_t off;
int ok;
ok = 1;
for(;;){
n = read(t->notifyfd, buf, sizeof buf);
if(n < 0 && errno == EINTR)
continue;
if(n < 0 && errno == EAGAIN)
return ok;
if(n < 0)
return fail("read endpoint watches: %s", strerror(errno));
if(n == 0)
return ok;
for(off = 0; off + sizeof *ev <= (size_t)n;
off += sizeof *ev + ev->len){
ev = (struct inotify_event*)(buf + off);
if(off + sizeof *ev + ev->len > (size_t)n)
return fail("truncated inotify event");
if(ev->mask & IN_Q_OVERFLOW){
t->endpointclean = 0;
fail("endpoint watch queue overflowed");
ok = 0;
continue;
}
if(ev->mask & (IN_IGNORED|IN_UNMOUNT)){
t->endpointclean = 0;
fail("endpoint watch became invalid: %#x", ev->mask);
ok = 0;
continue;
}
if(ev->wd != t->runtimewd && ev->wd != t->buswd)
continue;
if(!(ev->mask & endpointmask))
continue;
where = ev->wd == t->runtimewd ? "runtime" : "IBus";
t->endpointclean = 0;
fail("unexpected %s endpoint event %#x for %s", where,
ev->mask, ev->len != 0 ? ev->name : "directory");
ok = 0;
}
if(off != (size_t)n)
return fail("truncated inotify event buffer");
}
}
static int
killowned(Test *t, int *status)
{
pid_t pid;
int n, ok;
if(t->child <= 0)
return 1;
pid = t->child;
ok = 1;
if(kill(pid, SIGKILL) < 0 && errno != ESRCH){
fail("kill -9 %ld: %s", (long)pid, strerror(errno));
ok = 0;
}
do
n = waitpid(pid, status, 0);
while(n < 0 && errno == EINTR);
if(n != pid){
fail("reap %ld after SIGKILL: %s", (long)pid,
n < 0 ? strerror(errno) : "wrong child");
ok = 0;
}else
t->child = -1;
return ok;
}
static int
waitfailed(Test *t)
{
struct pollfd pfd[2];
pid_t pid;
int n, ok, status, timeout;
struct pollfd pfd;
int n, status;
int64_t deadline;
pid = t->child;
ok = 1;
deadline = nowms();
if(deadline < 0){
fail("read monotonic clock before waiting for daemon: %s",
strerror(errno));
killowned(t, &status);
readerrors(t);
drainnotify(t);
return 0;
}
deadline += Failtimeout;
status = 0;
deadline = nowms() + Failtimeout;
for(;;){
do
n = waitpid(pid, &status, WNOHANG);
while(n < 0 && errno == EINTR);
if(n == pid){
t->child = -1;
readerrors(t);
if(!drainnotify(t))
ok = 0;
n = waitpid(d->pid, &status, WNOHANG);
if(n == d->pid){
d->pid = -1;
readerrors(d);
break;
}
if(n < 0){
fail("waitpid failed daemon %ld: %s", (long)pid,
strerror(errno));
if(errno == ECHILD){
t->child = -1;
readerrors(t);
drainnotify(t);
}else{
killowned(t, &status);
readerrors(t);
drainnotify(t);
}
return 0;
}
timeout = leftms(deadline);
if(timeout < 0){
fail("read monotonic clock while waiting for daemon: %s",
strerror(errno));
killowned(t, &status);
readerrors(t);
drainnotify(t);
return 0;
}
if(timeout == 0){
fail("daemon %ld did not exit after map initialization failure",
(long)pid);
killowned(t, &status);
return 0;
}
pfd[0].fd = t->notifyfd;
pfd[0].events = POLLIN;
pfd[0].revents = 0;
pfd[1].fd = t->errfd;
pfd[1].events = POLLIN|POLLHUP;
pfd[1].revents = 0;
n = poll(pfd, 2, timeout);
if(n < 0 && errno == EINTR)
continue;
if(n < 0){
fail("poll failed daemon: %s", strerror(errno));
killowned(t, &status);
readerrors(t);
drainnotify(t);
return 0;
if(errno == ECHILD)
d->pid = -1;
return fail("waitpid: %s", strerror(errno));
}
if(pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL)){
fail("endpoint watch became unusable: %#x", pfd[0].revents);
killowned(t, &status);
readerrors(t);
drainnotify(t);
return 0;
}
if(pfd[1].revents & (POLLERR|POLLNVAL)){
fail("daemon stderr pipe became unusable: %#x",
pfd[1].revents);
killowned(t, &status);
readerrors(t);
drainnotify(t);
return 0;
}
if((pfd[0].revents & POLLIN) && !drainnotify(t))
ok = 0;
if(pfd[1].revents & (POLLIN|POLLHUP))
readerrors(t);
n = leftms(deadline);
if(n == 0)
return fail("daemon did not exit after map initialization failure");
pfd.fd = d->errfd;
pfd.events = POLLIN|POLLHUP;
pfd.revents = 0;
n = poll(&pfd, 1, n);
if(n < 0 && errno == EINTR)
continue;
if(n < 0)
return fail("poll daemon: %s", strerror(errno));
if(n > 0)
readerrors(d);
}
if(!WIFEXITED(status) || WEXITSTATUS(status) != 1)
if(!WIFEXITED(status) || WEXITSTATUS(status) == 0)
return fail("daemon map failure had wait status %#x", status);
if(strcmp(t->err, t->expected) != 0)
return fail("daemon did not report the expected map initialization failure");
return ok && t->endpointclean;
if(strstr(d->err, "can't open") == NULL || strstr(d->err, badmap) == NULL)
return fail("daemon did not report the missing map directory");
return 1;
}
static int
dirempty(char *path, char *which)
emptydir(char *path)
{
DIR *dir;
struct dirent *de;
int count, ok;
int empty;
dir = opendir(path);
if(dir == NULL)
return fail("open %s directory %s: %s", which, path, strerror(errno));
count = 0;
ok = 1;
return fail("open %s: %s", path, strerror(errno));
empty = 1;
errno = 0;
while((de = readdir(dir)) != NULL){
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
continue;
count++;
fail("unexpected %s directory entry %s", which, de->d_name);
ok = 0;
errno = 0;
}
while((de = readdir(dir)) != NULL)
if(strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0){
fail("unexpected endpoint %s/%s", path, de->d_name);
empty = 0;
}
if(errno != 0){
fail("read %s directory %s: %s", which, path, strerror(errno));
ok = 0;
fail("read %s: %s", path, strerror(errno));
empty = 0;
}
if(closedir(dir) < 0){
fail("close %s directory %s: %s", which, path, strerror(errno));
ok = 0;
fail("close %s: %s", path, strerror(errno));
empty = 0;
}
if(count != 0)
ok = 0;
return ok;
return empty;
}
static int
checkendpoints(Test *t)
checkendpoints(Live *l)
{
struct stat st;
int ok;
ok = 1;
errno = 0;
if(lstat(t->socket, &st) == 0){
fail("IPC endpoint exists after failed startup: %s", t->socket);
ok = 0;
}else if(errno != ENOENT){
fail("lstat failed IPC endpoint: %s", strerror(errno));
ok = 0;
}
if(!dirempty(t->runtime, "runtime"))
ok = 0;
if(!dirempty(t->bus, "IBus"))
ok = 0;
return ok && t->endpointclean;
}
static int clearfd(int);
static int
removeentry(int dirfd, char *name)
{
struct stat st;
int fd, ok;
if(fstatat(dirfd, name, &st, AT_SYMLINK_NOFOLLOW) < 0)
return fail("stat cleanup entry %s: %s", name, strerror(errno));
if(!S_ISDIR(st.st_mode)){
if(unlinkat(dirfd, name, 0) == 0 || errno == ENOENT)
return 1;
return fail("remove cleanup entry %s: %s", name, strerror(errno));
}
fd = openat(dirfd, name, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
if(fd < 0)
return fail("open cleanup directory %s: %s", name, strerror(errno));
ok = clearfd(fd);
if(close(fd) < 0){
fail("close cleanup directory %s: %s", name, strerror(errno));
ok = 0;
}
if(unlinkat(dirfd, name, AT_REMOVEDIR) < 0 && errno != ENOENT){
fail("remove cleanup directory %s: %s", name, strerror(errno));
ok = 0;
}
return ok;
}
static int
clearfd(int fd)
{
DIR *dir;
struct dirent *de;
int copy, ok;
copy = dup(fd);
if(copy < 0)
return fail("duplicate cleanup directory: %s", strerror(errno));
dir = fdopendir(copy);
if(dir == NULL){
close(copy);
return fail("open cleanup directory stream: %s", strerror(errno));
}
ok = 1;
errno = 0;
while((de = readdir(dir)) != NULL){
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
continue;
if(!removeentry(fd, de->d_name))
ok = 0;
errno = 0;
}
if(errno != 0){
fail("read cleanup directory: %s", strerror(errno));
ok = 0;
}
if(closedir(dir) < 0){
fail("close cleanup directory stream: %s", strerror(errno));
ok = 0;
}
return ok;
}
static int
removeroot(Test *t)
{
int fd, ok;
if(t->root[0] == '\0')
return 1;
fd = open(t->root, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
if(fd < 0)
return errno == ENOENT || fail("open cleanup root %s: %s", t->root,
strerror(errno));
ok = clearfd(fd);
if(close(fd) < 0){
fail("close cleanup root %s: %s", t->root, strerror(errno));
ok = 0;
}
if(rmdir(t->root) < 0 && errno != ENOENT){
fail("rmdir %s: %s", t->root, strerror(errno));
ok = 0;
}
return ok;
}
static int
cleanup(Test *t)
{
int n, ok, status;
ok = 1;
if(t->child > 0){
do
n = waitpid(t->child, &status, WNOHANG);
while(n < 0 && errno == EINTR);
if(n == t->child)
t->child = -1;
else if(n == 0){
fail("failed-start daemon still running during cleanup");
ok = 0;
if(!killowned(t, &status))
ok = 0;
}else{
fail("check failed-start daemon during cleanup: %s",
strerror(errno));
ok = 0;
if(errno == ECHILD)
t->child = -1;
else if(!killowned(t, &status))
ok = 0;
}
}
readerrors(t);
if(t->errfd >= 0){
if(close(t->errfd) < 0){
fail("close daemon stderr: %s", strerror(errno));
ok = 0;
}
t->errfd = -1;
}
if(t->notifyfd >= 0){
if(t->runtimewd >= 0 &&
inotify_rm_watch(t->notifyfd, t->runtimewd) < 0 && errno != EINVAL){
fail("remove runtime watch: %s", strerror(errno));
ok = 0;
}
if(t->buswd >= 0 &&
inotify_rm_watch(t->notifyfd, t->buswd) < 0 && errno != EINVAL){
fail("remove IBus watch: %s", strerror(errno));
ok = 0;
}
if(close(t->notifyfd) < 0){
fail("close endpoint watches: %s", strerror(errno));
ok = 0;
}
t->notifyfd = -1;
}
if(!removeroot(t))
ok = 0;
return ok;
if(lstat(l->socket, &st) == 0)
return fail("IPC endpoint exists after failed startup: %s", l->socket);
if(errno != ENOENT)
return fail("lstat IPC endpoint: %s", strerror(errno));
return emptydir(l->runtime) && emptydir(l->bus);
}
int
main(int argc, char **argv)
{
Test test;
Daemon daemon;
Live live;
char badmap[320];
int ok;
testname = "daemon_failure_test";
if(argc != 2){
fprintf(stderr, "usage: daemon_failure_test strans\n");
return 2;
}
ok = setup(&test);
daemoninit(&daemon, "daemon");
ok = livesetup(&live, "failure");
if(ok && snprintf(badmap, sizeof badmap, "%s/missing-map", live.root)
>= (int)sizeof badmap)
ok = fail("temporary path is too long");
if(ok)
ok = startchild(&test, argv[1]);
if(ok){
ok = waitfailed(&test);
if(test.child <= 0 && !checkendpoints(&test))
ok = 0;
}
if(!cleanup(&test))
ok = startdaemon(&live, &daemon, argv[1], badmap);
if(ok)
ok = waitfailed(&daemon, badmap) && checkendpoints(&live);
if(!killdaemon(&daemon, NULL))
ok = 0;
if(!closeerrors(&daemon))
ok = 0;
if(!liveclean(&live))
ok = 0;
if(!ok){
showerrors(&test);
showerrors(&daemon);
return 1;
}
printf("failed daemon startup published no endpoints: ok\n");
printf("failed daemon startup leaves no endpoints: ok\n");
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,35 +1,28 @@
#include "dat.h"
#include "fn.h"
#include "test.h"
void
dictionary_candidates(struct ct *t)
{
char many[512], item[8];
char many[8*(Maxkouho+1)], item[8];
char *p;
Dictreq req;
Dictres res;
Hmap *saved;
Str kouho[Maxkouho], key;
Trie *saved;
Lang *lang;
Str key;
int i;
int i, n;
lang = getlang(LangJP);
saved = lang->dict;
lang->dict = hmapalloc(1);
key = mkstr("かな");
hmapset(&lang->dict, &key, " 候補1 かな 候補2 ",
lang->dict = trienew();
trieput(lang->dict, "かな", strlen("かな"), " 候補1 かな 候補2 ",
strlen(" 候補1 かな 候補2 "));
memset(&req, 0, sizeof req);
req.key = key;
req.pre = mkstr("preedit-one");
req.lang = LangJP;
dictlookup(&req, &res);
if(!CT_EQ_INT(t, 2, res.nkouho))
goto cleanup;
CT_EQ_INT(t, LangJP, res.lang);
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
checkstr(t, "candidate 1", "候補1", &res.kouho[0]);
checkstr(t, "candidate 2", "候補2", &res.kouho[1]);
key = mkstr("key");
key = mkstr("かな");
n = dictlookup(lang, &key, kouho, Maxkouho);
if(CT_EQ_INT(t, 2, n)){
checkstr(t, "candidate 1", "候補1", &kouho[0]);
checkstr(t, "candidate 2", "候補2", &kouho[1]);
}
p = many;
for(i = 0; i < Maxkouho+1; i++){
snprint(item, sizeof item, "c%02d", i);
@@ -39,78 +32,52 @@ dictionary_candidates(struct ct *t)
p += strlen(item);
}
*p = '\0';
hmapset(&lang->dict, &key, many, strlen(many));
req.key = key;
req.pre = mkstr("preedit-two");
dictlookup(&req, &res);
if(!CT_EQ_INT(t, Maxkouho, res.nkouho))
goto cleanup;
CT_EQ_INT(t, LangJP, res.lang);
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
checkstr(t, "first capped candidate", "c00", &res.kouho[0]);
checkstr(t, "last capped candidate", "c31", &res.kouho[31]);
cleanup:
hmapfree(lang->dict);
trieput(lang->dict, "key", 3, many, strlen(many));
key = mkstr("key");
n = dictlookup(lang, &key, kouho, Maxkouho);
if(CT_EQ_INT(t, Maxkouho, n)){
checkstr(t, "first capped candidate", "c00", &kouho[0]);
snprint(item, sizeof item, "c%02d", Maxkouho-1);
checkstr(t, "last capped candidate", item, &kouho[Maxkouho-1]);
}
CT_EQ_INT(t, 3, dictlookup(lang, &key, kouho, 3));
trieclose(lang->dict);
lang->dict = saved;
}
void
dictionary_misses_clear_result(struct ct *t)
dictionary_misses(struct ct *t)
{
static const struct {
char *name;
char *key;
char *pre;
} cases[] = {
{ "empty key", "", "empty-preedit" },
{ "missing key", "missing", "missing-preedit" },
};
Dictreq req;
Dictres res;
Hmap *saved;
Str kouho[Maxkouho], key;
Trie *saved;
Lang *lang;
int i;
lang = getlang(LangJP);
saved = lang->dict;
lang->dict = hmapalloc(1);
for(i = 0; i < nelem(cases); i++){
memset(&res, 0xa5, sizeof res);
req.key = mkstr(cases[i].key);
req.pre = mkstr(cases[i].pre);
req.lang = LangJP;
dictlookup(&req, &res);
if(res.nkouho != 0)
CT_ERRORF(t, "%s: want 0 candidates, got %d",
cases[i].name, res.nkouho);
CT_EQ_INT(t, LangJP, res.lang);
checkstr(t, cases[i].name, cases[i].pre, &res.key);
}
hmapfree(lang->dict);
lang->dict = trienew();
key = mkstr("");
CT_EQ_INT(t, 0, dictlookup(lang, &key, kouho, Maxkouho));
key = mkstr("missing");
CT_EQ_INT(t, 0, dictlookup(lang, &key, kouho, Maxkouho));
CT_EQ_INT(t, 0, dictlookup(getlang(LangKO), &key, kouho, Maxkouho));
trieclose(lang->dict);
lang->dict = saved;
}
void
dictionary_emoji_identity(struct ct *t)
{
Dictreq req;
Dictres res;
Hmap *saved;
Str kouho[Maxkouho], key;
Trie *saved;
Lang *lang;
Str key;
lang = getlang(LangEMOJI);
saved = lang->dict;
lang->dict = hmapalloc(2);
lang->dict = trienew();
trieput(lang->dict, "é", strlen("é"), "é", strlen("é"));
key = mkstr("é");
hmapset(&lang->dict, &key, "é", strlen("é"));
memset(&req, 0, sizeof req);
req.key = key;
req.pre = key;
req.lang = LangEMOJI;
dictlookup(&req, &res);
if(CT_EQ_INT(t, 1, res.nkouho))
checkstr(t, "emoji identity candidate", "é", &res.kouho[0]);
hmapfree(lang->dict);
if(CT_EQ_INT(t, 1, dictlookup(lang, &key, kouho, Maxkouho)))
checkstr(t, "emoji identity candidate", "é", &kouho[0]);
trieclose(lang->dict);
lang->dict = saved;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +1,18 @@
#include "dat.h"
#include "fn.h"
#include "test.h"
enum
{
Guard = 8,
Testw = 3 * Fontsz,
Testh = 3 * Fontsz,
Testn = Testw * Testh,
Missingn = Fontsz * Fontsz,
Rgbmask = 0xffffff,
};
#define Testw (6 * Fontsz)
#define Testh (3 * Fontsz)
#define Testn (Testw * Testh)
static u32int guardcolor = 0x5a5a5a5a;
static char *testfonts[] = {
"/usr/share/fonts/TTF/DejaVuSans.ttf",
"/usr/share/fonts/TTF/Jigmo.ttf",
"/usr/share/fonts/TTF/Jigmo2.ttf",
};
static char *missingfonts[] = {
"/strans-test-no-such-font",
};
static void
fillpixels(u32int *p, int n, u32int color)
@@ -29,38 +24,84 @@ fillpixels(u32int *p, int n, u32int color)
}
static int
hasink(u32int *p, int n)
hasink(u32int *p, int n, u32int bg)
{
int i;
bg &= Rgbmask;
for(i = 0; i < n; i++)
if(p[i] != Colbg)
if((p[i] & Rgbmask) != bg)
return 1;
return 0;
}
static int
renders(Rune r)
hascolors(u32int *p, int n, u32int bg)
{
u32int buf[Fontsz * Fontsz];
u32int c, first;
int b, found, g, i, r;
fillpixels(buf, nelem(buf), Colbg);
putfont(buf, Fontsz, Fontsz, 0, 0, r);
return hasink(buf, nelem(buf));
bg &= Rgbmask;
first = 0;
found = 0;
for(i = 0; i < n; i++){
c = p[i] & Rgbmask;
if(c == bg)
continue;
r = c >> 16 & 0xff;
g = c >> 8 & 0xff;
b = c & 0xff;
if(r == g && g == b)
continue;
if(!found){
first = c;
found = 1;
}else if(c != first)
return 1;
}
return 0;
}
static void
checkguards(struct ct *t, u32int *mem)
{
int i;
for(i = 0; i < Guard; i++){
CT_EQ_UINT(t, guardcolor, mem[i]);
CT_EQ_UINT(t, guardcolor, mem[Guard + Testn + i]);
}
}
static void
checkoutside(struct ct *t, u32int *buf, u32int bg, int row)
{
int x, y, y0, y1;
bg &= Rgbmask;
y0 = max(row, 0);
y1 = min(row + Fontsz, Testh);
for(y = 0; y < Testh; y++){
if(y >= y0 && y < y1)
continue;
for(x = 0; x < Testw; x++)
CT_EQ_UINT(t, bg, buf[y * Testw + x] & Rgbmask);
}
}
static int
inkbounds(u32int *p, int *minx, int *miny, int *maxx, int *maxy)
inkbounds(u32int *p, u32int bg, int *minx, int *miny, int *maxx, int *maxy)
{
int x, y;
bg &= Rgbmask;
*minx = Testw;
*miny = Testh;
*maxx = -1;
*maxy = -1;
for(y = 0; y < Testh; y++){
for(x = 0; x < Testw; x++){
if(p[y * Testw + x] == Colbg)
if((p[y * Testw + x] & Rgbmask) == bg)
continue;
*minx = min(*minx, x);
*miny = min(*miny, y);
@@ -72,86 +113,150 @@ inkbounds(u32int *p, int *minx, int *miny, int *maxx, int *maxy)
}
static void
checkclip(struct ct *t, u32int *ref, int dx, int dy)
checktext(struct ct *t, u32int *buf, char *utf)
{
u32int *buf, *mem, *want;
int i, sx, sy, x, y;
Str s;
int maxx, maxy, minx, miny, w;
s = mkstr(utf);
w = textwidth(&s);
CT_CHECK(t, w > 0);
CT_CHECK(t, w < Testw - Fontsz);
fillpixels(buf, Testn, Colbg);
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &s);
if(!CT_CHECK(t, inkbounds(buf, Colbg, &minx, &miny, &maxx, &maxy)))
return;
CT_CHECK(t, minx >= 0 && miny >= 0);
CT_CHECK(t, maxx < Testw && maxy < Testh);
CT_CHECK(t, maxx - minx + 1 <= w);
}
static void
checkcolor(struct ct *t, u32int *buf, char *utf)
{
Str s;
int w;
s = mkstr(utf);
w = textwidth(&s);
CT_CHECK(t, w > 0);
if(s.n > 1)
CT_CHECK(t, w < s.n * Fontsz);
fillpixels(buf, Testn, Colbg);
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &s);
CT_CHECK(t, hasink(buf, Testn, Colbg));
CT_CHECK(t, hascolors(buf, Testn, Colbg));
}
static void
checkshape(struct ct *t, u32int *buf, char *utf)
{
Str s;
int w;
s = mkstr(utf);
w = textwidth(&s);
CT_CHECK(t, w > 0);
CT_CHECK(t, w < s.n * Fontsz);
fillpixels(buf, Testn, Colbg);
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &s);
CT_CHECK(t, hasink(buf, Testn, Colbg));
}
static void
checkclip(struct ct *t, u32int *mem, int x, int y, Str *s)
{
u32int *buf;
mem = emalloc((Testn + 2 * Guard) * sizeof mem[0]);
want = emalloc(Testn * sizeof want[0]);
fillpixels(mem, Testn + 2 * Guard, guardcolor);
buf = mem + Guard;
fillpixels(buf, Testn, Colbg);
fillpixels(want, Testn, Colbg);
for(y = 0; y < Testh; y++){
for(x = 0; x < Testw; x++){
sx = x - dx;
sy = y - dy;
if(sx >= 0 && sx < Testw && sy >= 0 && sy < Testh)
want[y * Testw + x] = ref[sy * Testw + sx];
}
}
putfont(buf, Testw, Testh, Fontsz + dx, Fontsz + dy, 'A');
CT_CHECK(t, hasink(buf, Testn));
CT_EQ_MEM(t, want, buf, Testn * sizeof buf[0]);
for(i = 0; i < Guard; i++){
CT_EQ_UINT(t, guardcolor, mem[i]);
CT_EQ_UINT(t, guardcolor, mem[Guard + Testn + i]);
}
free(want);
free(mem);
textdraw(buf, Testw, Testh, x, y, Testw, Colfg, s);
CT_CHECK(t, hasink(buf, Testn, Colbg));
checkoutside(t, buf, Colbg, y);
checkguards(t, mem);
}
void
font_render(struct ct *t)
{
u32int *buf, *missing, *ref;
int i, minx, miny, maxx, maxy;
static char *plain[] = { "A", "", "", "", "", "𠀋" };
u32int *buf, *mem;
Str a, heart, longrow, missing;
int i, maxx, maxy, minx, miny, natural, w, x, y;
missing = emalloc((Missingn + 2 * Guard) * sizeof missing[0]);
fillpixels(missing, Missingn + 2 * Guard, guardcolor);
buf = missing + Guard;
fillpixels(buf, Missingn, Colbg);
CT_CHECK(t, !fontinit(missingfonts, nelem(missingfonts)));
putfont(buf, Fontsz, Fontsz, 0, 0, 'A');
for(i = 0; i < Missingn; i++)
CT_EQ_UINT(t, Colbg, buf[i]);
for(i = 0; i < Guard; i++){
CT_EQ_UINT(t, guardcolor, missing[i]);
CT_EQ_UINT(t, guardcolor, missing[Guard + Missingn + i]);
}
if(!CT_CHECK(t, fontinit(testfonts, nelem(testfonts)))){
free(missing);
mem = emalloc((Testn + 2 * Guard) * sizeof mem[0]);
fillpixels(mem, Testn + 2 * Guard, guardcolor);
buf = mem + Guard;
fillpixels(buf, Testn, Colbg);
a = mkstr("A");
if(!CT_CHECK(t, textinit())){
free(mem);
return;
}
CT_CHECK(t, renders('A'));
CT_CHECK(t, renders(0xac00));
CT_CHECK(t, renders(0x4e00));
CT_CHECK(t, renders(0x1f600));
CT_CHECK(t, renders(0x2000b));
fillpixels(missing, Missingn + 2 * Guard, guardcolor);
buf = missing + Guard;
fillpixels(buf, Missingn, Colbg);
putfont(buf, Fontsz, Fontsz, 0, 0, 0x10ffff);
for(i = 0; i < Missingn; i++)
CT_EQ_UINT(t, Colbg, buf[i]);
for(i = 0; i < Guard; i++){
CT_EQ_UINT(t, guardcolor, missing[i]);
CT_EQ_UINT(t, guardcolor, missing[Guard + Missingn + i]);
}
free(missing);
for(i = 0; i < nelem(plain); i++)
checktext(t, buf, plain[i]);
ref = emalloc(Testn * sizeof ref[0]);
fillpixels(ref, Testn, Colbg);
putfont(ref, Testw, Testh, Fontsz, Fontsz, 'A');
if(!CT_CHECK(t, inkbounds(ref, &minx, &miny, &maxx, &maxy))){
free(ref);
return;
checkcolor(t, buf, "😀");
checkcolor(t, buf, "❤️");
checkcolor(t, buf, "☕️");
checkshape(t, buf, "👩‍💻");
checkshape(t, buf, "👍🏽");
checkshape(t, buf, "🇰🇷");
checkshape(t, buf, "☕︎");
heart = mkstr("❤️");
fillpixels(buf, Testn, Colsel);
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &heart);
CT_CHECK(t, hascolors(buf, Testn, Colsel));
CT_EQ_UINT(t, Colsel,
buf[(Fontsz + Fontsz/2) * Testw + Testw - 1] & Rgbmask);
checkoutside(t, buf, Colsel, Fontsz);
checkguards(t, mem);
w = textwidth(&a);
checkclip(t, mem, -w / 2, Fontsz, &a);
checkclip(t, mem, Testw - w / 2, Fontsz, &a);
checkclip(t, mem, Fontsz, -Fontsz / 2, &a);
checkclip(t, mem, Fontsz, Testh - Fontsz / 2, &a);
longrow = mkstr("abcdefghijklmnopqrstuvwxyz");
natural = textwidth(&longrow);
CT_CHECK(t, natural > 2*Fontsz);
fillpixels(buf, Testn, Colbg);
textdraw(buf, Testw, Testh, 2*Fontsz, Fontsz, 2*Fontsz, Colfg,
&longrow);
CT_CHECK(t, hasink(buf, Testn, Colbg));
for(y = Fontsz; y < 2*Fontsz; y++){
for(x = 0; x < 2*Fontsz; x++)
CT_EQ_UINT(t, Colbg, buf[y * Testw + x] & Rgbmask);
for(x = 4*Fontsz; x < Testw; x++)
CT_EQ_UINT(t, Colbg, buf[y * Testw + x] & Rgbmask);
}
checkclip(t, ref, -minx - 1, 0);
checkclip(t, ref, Testw - maxx, 0);
checkclip(t, ref, 0, -miny - 1);
checkclip(t, ref, 0, Testh - maxy);
free(ref);
CT_EQ_INT(t, natural, textwidth(&longrow));
fillpixels(buf, Testn, Colsel);
textdraw(buf, Testw, Testh, 2*Fontsz, Fontsz, 2*Fontsz, Colselfg,
&longrow);
CT_CHECK(t, (Colselfg & Rgbmask) != (Colsel & Rgbmask));
if(CT_CHECK(t, inkbounds(buf, Colsel,
&minx, &miny, &maxx, &maxy))){
CT_CHECK(t, minx >= 2*Fontsz);
CT_CHECK(t, maxx < 4*Fontsz);
CT_CHECK(t, miny >= Fontsz);
CT_CHECK(t, maxy < 2*Fontsz);
}
checkoutside(t, buf, Colsel, Fontsz);
checkguards(t, mem);
CT_EQ_INT(t, natural, textwidth(&longrow));
missing = mkstr("\xF4\x8F\xBF\xBF");
for(i = 0; i < 32; i++){
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &heart);
textdraw(buf, Testw, Testh, 0, 0, Testw, Colfg, &missing);
}
checkguards(t, mem);
free(mem);
textclose();
textclose();
CT_EQ_INT(t, 0, textwidth(&a));
}

940
tests/gtk_live_test.c Normal file
View 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;
}

View File

@@ -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
View 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

View File

@@ -1,13 +1,8 @@
#include "../ibus.c"
#include "../cutest/cutest.h"
#include "ibus.c"
#include "test.h"
#include <errno.h>
void testengineinit(int);
void testenginehandle(Keyreq*);
void* testengineowner(void);
void testenginepreedit(Str*);
void testenginecaret(Caret*);
enum
{
@@ -18,20 +13,17 @@ typedef struct Ibusfix Ibusfix;
struct Ibusfix
{
Channel *oldreply;
Channel *trace;
Channel *stop;
Channel *done;
Pump pump;
DBusConnection *c1;
DBusConnection *c2;
int pumpactive;
};
void
ibus_machine_id_fallback(struct ct *t)
{
char root[] = "/tmp/strans-machine-id.XXXXXX";
char primary[128], fallback[128], got[64];
char *path[2];
char primary[128], fallback[128], override[128], got[512];
char *path[2], *old, *saved;
FILE *fp;
int madeprimary, madefallback;
@@ -61,6 +53,24 @@ ibus_machine_id_fallback(struct ct *t)
path[1] = fallback;
machineidfiles(got, sizeof got, path, nelem(path));
CT_EQ_STR(t, "fallback-machine-id", got);
old = getenv("IBUS_ADDRESS_FILE");
saved = old == nil ? nil : strdup(old);
if(old == nil || CT_CHECK(t, saved != nil)){
if(CT_CHECK(t, snprintf(override, sizeof override, "%s/address", root)
< (int)sizeof override) &&
CT_CHECK(t, setenv("IBUS_ADDRESS_FILE", override, 1) == 0)){
CT_EQ_INT(t, 0, buildaddrpath(got, sizeof got));
CT_EQ_STR(t, override, got);
CT_CHECK(t, setenv("IBUS_ADDRESS_FILE", "", 1) == 0);
CT_EQ_INT(t, -1, buildaddrpath(got, sizeof got));
}
if(saved != nil){
setenv("IBUS_ADDRESS_FILE", saved, 1);
free(saved);
}else
unsetenv("IBUS_ADDRESS_FILE");
}
cleanup:
if(madefallback)
CT_CHECK(t, unlink(fallback) == 0);
@@ -85,6 +95,8 @@ ibus_startup_requires_ownership(struct ct *t)
saved = old == nil ? nil : strdup(old);
if(old != nil && !CT_CHECK(t, saved != nil))
return;
/* An IBus desktop names a live address file; the path must be ours. */
unsetenv("IBUS_ADDRESS_FILE");
if(!CT_CHECK(t, mkdtemp(root) != nil))
goto cleanup;
made = 1;
@@ -146,32 +158,6 @@ cleanup:
}
}
static void
enginepump(void *arg)
{
Ibusfix *f;
Keyreq req;
uchar token;
Alt alts[] = {
{keyc, &req, CHANRCV, nil},
{nil, &token, CHANRCV, nil},
{nil, nil, CHANEND, nil},
};
f = arg;
alts[1].c = f->stop;
for(;;)
switch(alt(alts)){
case 0:
chansend(f->trace, &req);
testenginehandle(&req);
break;
case 1:
chansend(f->done, &token);
return;
}
}
static int
ibusbegin(struct ct *t, Ibusfix *f)
{
@@ -181,41 +167,32 @@ ibusbegin(struct ct *t, Ibusfix *f)
memset(contexts, 0, sizeof contexts);
memset(conns, 0, sizeof conns);
nconns = 0;
preowner = nil;
while(channbrecv(drawc, &dc) > 0)
;
testengineinit(LangEN);
f->oldreply = replyc;
replyc = chancreate(sizeof(Keyres), 0);
f->trace = chancreate(sizeof(Keyreq), Maxcontexts);
f->stop = chancreate(sizeof(uchar), 0);
f->done = chancreate(sizeof(uchar), 0);
f->pumpactive = threadcreate(enginepump, f, 8192) >= 0;
pumpstart(&f->pump, Maxcontexts);
f->c1 = malloc(1);
f->c2 = malloc(1);
return CT_CHECK(t, f->pumpactive && f->c1 != nil && f->c2 != nil);
return CT_CHECK(t, f->c1 != nil && f->c2 != nil);
}
static void
ibusend(Ibusfix *f)
{
Drawcmd dc;
Keyreq req;
uchar token;
int i;
if(f->pumpactive){
for(i = 0; i < nelem(contexts); i++)
if(contexts[i].conn != nil)
dropcontext(&contexts[i]);
while(channbrecv(f->trace, &req) > 0)
;
token = 0;
chansend(f->stop, &token);
chanrecv(f->done, &token);
}
for(i = 0; i < nelem(contexts); i++)
if(contexts[i].conn != nil)
dropcontext(&contexts[i]);
pumpstop(&f->pump);
memset(contexts, 0, sizeof contexts);
memset(conns, 0, sizeof conns);
nconns = 0;
preowner = nil;
testengineinit(LangEN);
while(channbrecv(drawc, &dc) > 0)
;
@@ -223,9 +200,6 @@ ibusend(Ibusfix *f)
free(f->c2);
chanfree(replyc);
replyc = f->oldreply;
chanfree(f->trace);
chanfree(f->stop);
chanfree(f->done);
}
static Keyreq
@@ -234,10 +208,12 @@ nexttrace(struct ct *t, Ibusfix *f, int op, Ictx *ctx)
Keyreq req;
memset(&req, 0, sizeof req);
if(!CT_CHECK(t, channbrecv(f->trace, &req) > 0))
if(!CT_CHECK(t, channbrecv(f->pump.trace, &req) > 0))
return req;
CT_EQ_INT(t, op, req.op);
CT_EQ_PTR(t, ctx, req.owner);
if(ctx != nil)
CT_EQ_INT(t, ctx->cap & 1 ? Cclientpreedit : 0, req.cap);
CT_EQ_PTR(t, replyc, req.reply);
return req;
}
@@ -247,27 +223,7 @@ notrace(struct ct *t, Ibusfix *f)
{
Keyreq req;
CT_CHECK(t, channbrecv(f->trace, &req) <= 0);
}
static void
checkpreedit(struct ct *t, char *want, Keyres *res)
{
char got[Maxutf];
stoutf(&res->preedit, got, sizeof got);
CT_EQ_STR(t, want, got);
}
static void
checkenginepreedit(struct ct *t, char *want)
{
char got[Maxutf];
Str preedit;
testenginepreedit(&preedit);
stoutf(&preedit, got, sizeof got);
CT_EQ_STR(t, want, got);
CT_CHECK(t, channbrecv(f->pump.trace, &req) <= 0);
}
static Keyres
@@ -281,6 +237,108 @@ contextkey(struct ct *t, Ibusfix *f, Ictx *ctx, u32int sym, u32int state)
return res;
}
void
ibus_capability_policy(struct ct *t)
{
Ibusfix f;
Ictx *a, *b;
Keyres res;
if(!ibusbegin(t, &f))
goto cleanup;
a = newcontext(f.c1, "/context/cap-a");
b = newcontext(f.c2, "/context/cap-b");
if(!CT_CHECK(t, a != nil && b != nil))
goto cleanup;
CT_EQ_INT(t, 0, a->cap);
a->focused = 1;
res = contextkey(t, &f, a, 'n', Testctrlmask);
CT_CHECK(t, res.eaten);
res = contextkey(t, &f, a, 'k', 0);
checkstr(t, "preedit", "k", &res.preedit);
CT_EQ_PTR(t, a, testengineowner());
a->cap = Cclientpreedit;
memset(&res, 0, sizeof res);
sendrequest(a, Keycap, 0, 0, &res);
nexttrace(t, &f, Keycap, a);
CT_CHECK(t, res.eaten);
checkstr(t, "preedit", "k", &res.preedit);
b->focused = 1;
b->cap = Cclientpreedit;
memset(&res, 0, sizeof res);
sendrequest(b, Keycap, 0, 0, &res);
nexttrace(t, &f, Keycap, b);
CT_CHECK(t, !res.eaten);
CT_EQ_PTR(t, a, testengineowner());
checkenginepreedit(t, "k");
a->cap = 0;
memset(&res, 0, sizeof res);
sendrequest(a, Keycap, 0, 0, &res);
nexttrace(t, &f, Keycap, a);
CT_CHECK(t, res.eaten);
checkstr(t, "preedit", "k", &res.preedit);
cleanup:
ibusend(&f);
}
void
ibus_private_input_policy(struct ct *t)
{
static const struct { u32int purpose; int want; } cases[] = {
{ 0, 0 },
{ Ibuspurposepassword, 1 },
{ Ibuspurposepin, 1 },
{ 37, 0 },
};
Ibusfix f;
Ictx *ctx;
Keyres res;
int i;
if(!ibusbegin(t, &f))
goto cleanup;
ctx = newcontext(f.c1, "/context/private");
if(!CT_CHECK(t, ctx != nil))
goto cleanup;
for(i = 0; i < nelem(cases); i++){
ctx->purpose = cases[i].purpose;
CT_EQ_INT(t, cases[i].want, hidden(ctx));
}
ctx->focused = 1;
ctx->cap = Cclientpreedit;
ctx->purpose = 0;
contextkey(t, &f, ctx, 'n', Testctrlmask);
res = contextkey(t, &f, ctx, 'k', 0);
checkstr(t, "preedit", "k", &res.preedit);
ctx->purpose = Ibuspurposepassword;
memset(&res, 0, sizeof res);
CT_CHECK(t, !processkey(ctx, 'x', 0, &res));
CT_CHECK(t, !res.eaten);
notrace(t, &f);
checkenginepreedit(t, "k");
ctx->purpose = 0;
res = contextkey(t, &f, ctx, 'a', 0);
checkstr(t, "preedit", "", &res.preedit);
cleanup:
ibusend(&f);
}
/* A second release is silent, and dropping frees the slot. */
static void
checkdrop(struct ct *t, Ibusfix *f, Ictx *ctx, char *path)
{
releasecontext(ctx);
notrace(t, f);
dropcontext(ctx);
notrace(t, f);
CT_EQ_PTR(t, nil, findcontext(f->c1, path));
}
void
ibus_context_lifecycle(struct ct *t)
{
@@ -306,7 +364,7 @@ ibus_context_lifecycle(struct ct *t)
notrace(t, &f);
CT_EQ_PTR(t, nil, testengineowner());
setcursor(a, 10, 20, 14);
setcursor(a, -10, -20, 14);
notrace(t, &f);
CT_CHECK(t, a->caret.valid);
a->focused = 1;
@@ -321,23 +379,23 @@ ibus_context_lifecycle(struct ct *t)
req = nexttrace(t, &f, Keypress, a);
CT_CHECK(t, !res.eaten);
CT_CHECK(t, req.caret.valid);
CT_EQ_INT(t, 10, req.caret.x);
CT_EQ_INT(t, 20, req.caret.y);
CT_EQ_INT(t, -10, req.caret.x);
CT_EQ_INT(t, -20, req.caret.y);
CT_EQ_INT(t, 14, req.caret.h);
CT_EQ_PTR(t, a, testengineowner());
testenginecaret(&at);
CT_CHECK(t, at.valid);
CT_EQ_INT(t, 10, at.x);
CT_EQ_INT(t, 20, at.y);
CT_EQ_INT(t, -10, at.x);
CT_EQ_INT(t, -20, at.y);
res = contextkey(t, &f, a, 'n', Testctrlmask);
CT_CHECK(t, res.eaten);
contextkey(t, &f, a, 'k', 0);
res = contextkey(t, &f, a, 'a', 0);
checkpreedit(t, "", &res);
checkstr(t, "preedit", "", &res.preedit);
b->focused = 1;
res = contextkey(t, &f, b, 'n', 0);
checkpreedit(t, "", &res);
checkstr(t, "preedit", "", &res.preedit);
CT_EQ_PTR(t, b, testengineowner());
setcursor(b, 50, 60, 12);
@@ -351,7 +409,7 @@ ibus_context_lifecycle(struct ct *t)
memset(&res, 0, sizeof res);
sendrequest(a, Keyreset, 0, 0, &res);
nexttrace(t, &f, Keyreset, a);
checkpreedit(t, "", &res);
checkstr(t, "preedit", "", &res.preedit);
checkenginepreedit(t, "");
CT_EQ_PTR(t, b, testengineowner());
@@ -367,11 +425,7 @@ ibus_context_lifecycle(struct ct *t)
CT_CHECK(t, !a->caret.valid);
checkenginepreedit(t, "");
CT_EQ_PTR(t, b, testengineowner());
releasecontext(a);
notrace(t, &f);
dropcontext(a);
notrace(t, &f);
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/one"));
checkdrop(t, &f, a, "/context/one");
reused = newcontext(f.c1, "/context/stale-destroy");
CT_EQ_PTR(t, a, reused);
@@ -419,17 +473,17 @@ ibus_active_release_lifecycle(struct ct *t)
ctx->focused = 1;
contextkey(t, &f, ctx, 'n', Testctrlmask);
res = contextkey(t, &f, ctx, 'n', 0);
checkpreedit(t, "", &res);
checkstr(t, "preedit", "", &res.preedit);
memset(&res, 0, sizeof res);
sendrequest(ctx, Keyreset, 0, 0, &res);
nexttrace(t, &f, Keyreset, ctx);
checkpreedit(t, "", &res);
checkstr(t, "preedit", "", &res.preedit);
checkenginepreedit(t, "");
CT_CHECK(t, ctx->focused);
CT_EQ_PTR(t, ctx, testengineowner());
res = contextkey(t, &f, ctx, 'k', 0);
checkpreedit(t, "k", &res);
checkstr(t, "preedit", "k", &res.preedit);
CT_EQ_PTR(t, ctx, testengineowner());
releasecontext(ctx);
@@ -437,11 +491,7 @@ ibus_active_release_lifecycle(struct ct *t)
CT_CHECK(t, !ctx->focused);
checkenginepreedit(t, "");
CT_EQ_PTR(t, nil, testengineowner());
releasecontext(ctx);
notrace(t, &f);
dropcontext(ctx);
notrace(t, &f);
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/reset"));
checkdrop(t, &f, ctx, "/context/reset");
reused = newcontext(f.c1, "/context/destroy");
CT_EQ_PTR(t, ctx, reused);

View File

@@ -1,52 +1,25 @@
#define _GNU_SOURCE
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "../ipc.h"
#include "ipc.h"
#include "live.h"
enum
{
Maxclients = 64,
Calltimeout = 4000,
Starttimeout = 8000,
Stoptimeout = 3000,
Cappreedit = 1,
};
typedef struct Daemon Daemon;
typedef struct Response Response;
struct Daemon
{
pid_t pid;
int errfd;
char root[256];
char runtime[320];
char config[320];
char ibus[384];
char bus[448];
char home[320];
char socket[384];
char err[4096];
size_t nerr;
};
struct Response
{
int eaten;
@@ -54,485 +27,6 @@ struct Response
char preedit[Ipcfieldmax+1];
};
static int
fail(char *fmt, ...)
{
va_list ap;
fprintf(stderr, "ipc_live_test: ");
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
return 0;
}
static int64_t
nowms(void)
{
struct timespec ts;
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
return 0;
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static int
leftms(int64_t deadline)
{
int64_t n;
n = deadline - nowms();
if(n <= 0)
return 0;
if(n > 0x7fffffff)
return 0x7fffffff;
return n;
}
static int
makedir(char *path)
{
if(mkdir(path, 0700) == 0)
return 1;
return fail("mkdir %s: %s", path, strerror(errno));
}
static void
readerrors(Daemon *d)
{
ssize_t n;
if(d->errfd < 0)
return;
while(d->nerr + 1 < sizeof d->err){
n = read(d->errfd, d->err + d->nerr,
sizeof d->err - d->nerr - 1);
if(n > 0){
d->nerr += n;
continue;
}
if(n < 0 && errno == EINTR)
continue;
break;
}
d->err[d->nerr] = '\0';
}
static void
showerrors(Daemon *d)
{
readerrors(d);
if(d->nerr != 0)
fprintf(stderr, "ipc_live_test: daemon stderr:\n%s", d->err);
}
static int
waitsocket(Daemon *d, int notifyfd)
{
struct pollfd pfd[2];
struct stat st;
char buf[4096];
int n, status, timeout;
int64_t deadline;
deadline = nowms() + Starttimeout;
for(;;){
if(lstat(d->socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
(st.st_mode & 0777) == 0600)
return 1;
if(waitpid(d->pid, &status, WNOHANG) == d->pid){
d->pid = -1;
readerrors(d);
return fail("daemon exited before creating IPC socket");
}
timeout = leftms(deadline);
if(timeout == 0)
return fail("timed out waiting for protected IPC socket %s",
d->socket);
pfd[0].fd = notifyfd;
pfd[0].events = POLLIN;
pfd[0].revents = 0;
pfd[1].fd = d->errfd;
pfd[1].events = POLLIN;
pfd[1].revents = 0;
n = poll(pfd, 2, timeout);
if(n < 0 && errno == EINTR)
continue;
if(n < 0)
return fail("poll for IPC socket: %s", strerror(errno));
if(n == 0)
continue;
if(pfd[1].revents != 0)
readerrors(d);
if(pfd[0].revents & POLLIN)
while(read(notifyfd, buf, sizeof buf) < 0 && errno == EINTR)
;
}
}
static int
startdaemon(Daemon *d, char *program, char *mapdir)
{
char missing[384];
int fd, notifyfd, watch, errpipe[2];
long maxfd;
pid_t pid;
memset(d, 0, sizeof *d);
d->pid = -1;
d->errfd = -1;
snprintf(d->root, sizeof d->root, "/tmp/strans-ipc.XXXXXX");
if(mkdtemp(d->root) == NULL)
return fail("mkdtemp: %s", strerror(errno));
if(snprintf(d->runtime, sizeof d->runtime, "%s/runtime", d->root)
>= (int)sizeof d->runtime ||
snprintf(d->config, sizeof d->config, "%s/config", d->root)
>= (int)sizeof d->config ||
snprintf(d->ibus, sizeof d->ibus, "%s/ibus", d->config)
>= (int)sizeof d->ibus ||
snprintf(d->bus, sizeof d->bus, "%s/bus", d->ibus)
>= (int)sizeof d->bus ||
snprintf(d->home, sizeof d->home, "%s/home", d->root)
>= (int)sizeof d->home ||
snprintf(d->socket, sizeof d->socket, "%s/strans.sock", d->runtime)
>= (int)sizeof d->socket ||
snprintf(missing, sizeof missing, "%s/no-such-font.ttf", d->root)
>= (int)sizeof missing)
return fail("temporary path is too long");
if(!makedir(d->runtime) || !makedir(d->config) || !makedir(d->ibus) ||
!makedir(d->bus) || !makedir(d->home))
return 0;
notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
if(notifyfd < 0)
return fail("inotify_init1: %s", strerror(errno));
watch = inotify_add_watch(notifyfd, d->runtime,
IN_CREATE|IN_MOVED_TO|IN_ATTRIB);
if(watch < 0){
close(notifyfd);
return fail("inotify_add_watch: %s", strerror(errno));
}
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0){
close(notifyfd);
return fail("pipe2: %s", strerror(errno));
}
pid = fork();
if(pid < 0){
close(errpipe[0]);
close(errpipe[1]);
close(notifyfd);
return fail("fork: %s", strerror(errno));
}
if(pid == 0){
close(errpipe[0]);
close(notifyfd);
if(dup2(errpipe[1], STDOUT_FILENO) < 0 ||
dup2(errpipe[1], STDERR_FILENO) < 0)
_exit(126);
close(errpipe[1]);
if(close_range(3, UINT_MAX, 0) < 0){
maxfd = sysconf(_SC_OPEN_MAX);
if(maxfd < 0)
maxfd = 1024;
for(fd = 3; fd < maxfd; fd++)
close(fd);
}
if(setenv("XDG_RUNTIME_DIR", d->runtime, 1) < 0 ||
setenv("XDG_CONFIG_HOME", d->config, 1) < 0 ||
setenv("HOME", d->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
unsetenv("IBUS_ADDRESS") < 0){
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
strerror(errno));
_exit(126);
}
execl(program, program, mapdir, missing, (char*)0);
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
_exit(127);
}
close(errpipe[1]);
d->pid = pid;
d->errfd = errpipe[0];
if(!waitsocket(d, notifyfd)){
inotify_rm_watch(notifyfd, watch);
close(notifyfd);
return 0;
}
inotify_rm_watch(notifyfd, watch);
close(notifyfd);
return 1;
}
static int
clearbus(Daemon *d)
{
DIR *dir;
struct dirent *de;
char path[576];
int ok;
dir = opendir(d->bus);
if(dir == NULL)
return errno == ENOENT || fail("open cleanup directory %s: %s",
d->bus, strerror(errno));
ok = 1;
errno = 0;
while((de = readdir(dir)) != NULL){
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
continue;
if(snprintf(path, sizeof path, "%s/%s", d->bus, de->d_name)
>= (int)sizeof path){
fail("cleanup path is too long: %s", de->d_name);
ok = 0;
continue;
}
if(unlink(path) < 0 && errno != ENOENT){
fail("remove IBus address file %s: %s", de->d_name,
strerror(errno));
ok = 0;
}
errno = 0;
}
if(errno != 0){
fail("read cleanup directory %s: %s", d->bus, strerror(errno));
ok = 0;
}
closedir(dir);
return ok;
}
static int
rmdirknown(char *path)
{
if(path[0] == '\0' || rmdir(path) == 0 || errno == ENOENT)
return 1;
return fail("rmdir %s: %s", path, strerror(errno));
}
static int
killdaemon(Daemon *d, int *status)
{
int n, ok;
ok = 1;
if(kill(d->pid, SIGKILL) < 0 && errno != ESRCH){
fail("kill -9 %ld: %s", (long)d->pid, strerror(errno));
ok = 0;
}
do
n = waitpid(d->pid, status, 0);
while(n < 0 && errno == EINTR);
if(n != d->pid){
fail("reap %ld after SIGKILL: %s", (long)d->pid,
n < 0 ? strerror(errno) : "wrong child");
ok = 0;
}else
d->pid = -1;
return ok;
}
static int
stopdaemon(Daemon *d)
{
struct pollfd pfd;
int ok, status, n, reaped;
int64_t deadline;
ok = 1;
if(d->pid > 0){
do
n = waitpid(d->pid, &status, WNOHANG);
while(n < 0 && errno == EINTR);
if(n == d->pid){
fail("daemon exited before test termination with wait status %#x",
status);
d->pid = -1;
ok = 0;
}else if(n < 0){
fail("check daemon %ld before termination: %s",
(long)d->pid, strerror(errno));
if(errno == ECHILD)
d->pid = -1;
ok = 0;
}
}
if(d->pid > 0){
if(kill(d->pid, SIGTERM) < 0){
fail("kill %ld: %s", (long)d->pid, strerror(errno));
ok = 0;
}
reaped = 0;
deadline = nowms() + Stoptimeout;
for(;;){
n = waitpid(d->pid, &status, WNOHANG);
if(n == d->pid){
reaped = 1;
d->pid = -1;
break;
}
if(n < 0 && errno == EINTR)
continue;
if(n < 0){
fail("waitpid %ld: %s", (long)d->pid, strerror(errno));
ok = 0;
if(errno == ECHILD)
d->pid = -1;
break;
}
n = leftms(deadline);
if(n == 0){
fail("daemon %ld did not stop after SIGTERM", (long)d->pid);
ok = 0;
if(!killdaemon(d, &status))
ok = 0;
break;
}
pfd.fd = d->errfd;
pfd.events = POLLIN|POLLHUP;
pfd.revents = 0;
if(poll(&pfd, 1, n) < 0 && errno != EINTR){
fail("poll daemon %ld: %s", (long)d->pid, strerror(errno));
ok = 0;
if(!killdaemon(d, &status))
ok = 0;
break;
}
readerrors(d);
}
/* plan9port turns a caught termination note into exit status 1. */
if(reaped &&
!((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) ||
(WIFEXITED(status) && WEXITSTATUS(status) == 1))){
fail("daemon exited with unexpected wait status %#x", status);
ok = 0;
}
}
readerrors(d);
if(d->errfd >= 0){
close(d->errfd);
d->errfd = -1;
}
if(d->socket[0] != '\0' && unlink(d->socket) < 0 && errno != ENOENT){
fail("remove IPC socket %s: %s", d->socket, strerror(errno));
ok = 0;
}
if(!clearbus(d)) ok = 0;
if(!rmdirknown(d->bus)) ok = 0;
if(!rmdirknown(d->ibus)) ok = 0;
if(!rmdirknown(d->config)) ok = 0;
if(!rmdirknown(d->runtime)) ok = 0;
if(!rmdirknown(d->home)) ok = 0;
if(!rmdirknown(d->root)) ok = 0;
return ok;
}
static int
connectuntil(char *path, int64_t deadline)
{
struct sockaddr_un addr;
struct pollfd pfd;
socklen_t nerr;
int err, fd, flags, n;
memset(&addr, 0, sizeof addr);
addr.sun_family = AF_UNIX;
if(snprintf(addr.sun_path, sizeof addr.sun_path, "%s", path)
>= (int)sizeof addr.sun_path){
errno = ENAMETOOLONG;
return -1;
}
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if(fd < 0)
return -1;
if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0 &&
errno != EINPROGRESS){
err = errno;
close(fd);
errno = err;
return -1;
}
pfd.fd = fd;
pfd.events = POLLOUT;
pfd.revents = 0;
for(;;){
n = leftms(deadline);
if(n == 0){
close(fd);
errno = ETIMEDOUT;
return -1;
}
n = poll(&pfd, 1, n);
if(n < 0 && errno == EINTR)
continue;
if(n <= 0){
err = n == 0 ? ETIMEDOUT : errno;
close(fd);
errno = err;
return -1;
}
break;
}
err = 0;
nerr = sizeof err;
if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &nerr) < 0 || err != 0){
if(err == 0)
err = errno;
close(fd);
errno = err;
return -1;
}
flags = fcntl(fd, F_GETFL);
if(flags < 0 || fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0){
err = errno;
close(fd);
errno = err;
return -1;
}
return fd;
}
/* 1 is complete, 0 is peer closure, and -1 is a timeout or I/O error. */
static int
readuntil(int fd, void *buf, size_t n, int64_t deadline)
{
struct pollfd pfd;
unsigned char *p;
ssize_t r;
int timeout;
p = buf;
while(n > 0){
timeout = leftms(deadline);
if(timeout == 0){
errno = ETIMEDOUT;
return -1;
}
pfd.fd = fd;
pfd.events = POLLIN;
pfd.revents = 0;
r = poll(&pfd, 1, timeout);
if(r < 0){
if(errno == EINTR)
continue;
return -1;
}
if(r == 0){
errno = ETIMEDOUT;
return -1;
}
r = recv(fd, p, n, 0);
if(r < 0 && errno == EINTR)
continue;
if(r < 0)
return -1;
if(r == 0)
return 0;
p += r;
n -= r;
}
return 1;
}
static size_t
getlen(unsigned char p[Ipclensz])
{
@@ -616,6 +110,15 @@ sendreset(int fd, int want)
return ipcsend(fd, req, sizeof req) == 0;
}
static int
sendcap(int fd, int cap)
{
unsigned char req[Ipcreqsz];
ipcpackcap(req, cap);
return ipcsend(fd, req, sizeof req) == 0;
}
static int
expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
char *where)
@@ -632,11 +135,12 @@ expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
}
static int
requestreset(int fd, int want, int eaten, char *preedit, char *where)
requestreset(int fd, int want, int eaten, char *commit, char *preedit,
char *where)
{
if(!sendreset(fd, want))
return fail("send %s: %s", where, strerror(errno));
return expectresponse(fd, want, eaten, "", preedit, where);
return expectresponse(fd, want, eaten, commit, preedit, where);
}
static int
@@ -654,7 +158,7 @@ tryclient(char *path, int64_t deadline, int *client)
Response res;
int fd, rv;
fd = connectuntil(path, deadline);
fd = connectsocket(path, deadline);
if(fd < 0)
return errno == ETIMEDOUT ? -1 : 0;
if(!sendkey(fd, 1, 0, Kmodfirst)){
@@ -701,7 +205,7 @@ overflowrejected(char *path)
int64_t deadline;
deadline = nowms() + Calltimeout;
fd = connectuntil(path, deadline);
fd = connectsocket(path, deadline);
if(fd < 0)
return fail("overflow connect: %s", strerror(errno));
if(!sendkey(fd, 1, 0, Kmodfirst)){
@@ -742,7 +246,25 @@ overflowrejected(char *path)
}
static int
runlistener(Daemon *d)
runsmoke(char *path)
{
int client, ok;
client = connectsocket(path, nowms() + Calltimeout);
if(client < 0)
return fail("connect client: %s", strerror(errno));
ok = sendcap(client, Cappreedit) &&
expectresponse(client, 1, 1, "", "", "negotiate preedit") &&
requestkey(client, 1, Mctrl, 'n', 1, "", "",
"select Japanese") &&
requestkey(client, 1, 0, 'k', 1, "", "k", "preedit") &&
requestreset(client, 1, 1, "k", "", "reset");
close(client);
return ok;
}
static int
runcapacity(char *path)
{
int client[Maxclients];
int i, ok;
@@ -750,25 +272,16 @@ runlistener(Daemon *d)
for(i = 0; i < Maxclients; i++)
client[i] = -1;
ok = 0;
client[0] = connectuntil(d->socket, nowms() + Calltimeout);
client[0] = connectsocket(path, nowms() + Calltimeout);
if(client[0] < 0){
fail("connect first client: %s", strerror(errno));
goto out;
}
if(!requestkey(client[0], 1, Mctrl, 'n', 1, "", "", "select Japanese") ||
!requestkey(client[0], 1, 0, 'k', 1, "", "k", "GTK preedit") ||
!requestreset(client[0], 1, 1, "", "active reset"))
goto out;
if(!sendkey(client[0], 0, 0, 'k') ||
!sendkey(client[0], 1, 0, Kmodfirst)){
fail("send pipelined XIM/GTK requests: %s", strerror(errno));
goto out;
}
if(!expectresponse(client[0], 0, 1, "", "", "XIM framing") ||
!expectresponse(client[0], 1, 0, "", "k", "GTK after XIM framing"))
if(!requestkey(client[0], 1, Mctrl, 'n', 1, "", "",
"select Japanese"))
goto out;
for(i = 1; i < Maxclients; i++){
client[i] = connectuntil(d->socket, nowms() + Calltimeout);
client[i] = connectsocket(path, nowms() + Calltimeout);
if(client[i] < 0){
fail("connect capacity client %d: %s", i, strerror(errno));
goto out;
@@ -777,15 +290,15 @@ runlistener(Daemon *d)
"capacity modifier"))
goto out;
}
if(!overflowrejected(d->socket))
if(!overflowrejected(path))
goto out;
close(client[Maxclients-1]);
client[Maxclients-1] = -1;
if(!waitslot(d->socket, &client[Maxclients-1], "inactive slot recovery"))
if(!waitslot(path, &client[Maxclients-1], "inactive slot recovery"))
goto out;
close(client[0]);
client[0] = -1;
if(!waitslot(d->socket, &client[0], "active slot recovery") ||
if(!waitslot(path, &client[0], "active slot recovery") ||
!requestkey(client[0], 1, 0, 'a', 1, "", "",
"post-disconnect composition"))
goto out;
@@ -801,21 +314,35 @@ int
main(int argc, char **argv)
{
Daemon daemon;
int ok;
Live live;
int capacity, ok;
if(argc != 3){
fprintf(stderr, "usage: ipc_live_test strans mapdir\n");
testname = "ipc_live_test";
capacity = argc == 4 && strcmp(argv[1], "--capacity") == 0;
if((!capacity && argc != 3) || (capacity && argc != 4)){
fprintf(stderr,
"usage: ipc_live_test [--capacity] strans mapdir\n");
return 2;
}
ok = startdaemon(&daemon, argv[1], argv[2]);
daemoninit(&daemon, "daemon");
ok = livesetup(&live, "ipc") &&
startdaemon(&live, &daemon, argv[1+capacity], argv[2+capacity]) &&
waitready(&live, &daemon, NULL, 0);
if(ok)
ok = runlistener(&daemon);
ok = capacity ? runcapacity(live.socket) : runsmoke(live.socket);
if(!ok)
showerrors(&daemon);
if(!stopdaemon(&daemon))
ok = 0;
/* SIGTERM must leave no socket behind. */
if(access(live.socket, F_OK) == 0)
ok = fail("socket %s survived the daemon", live.socket);
if(!closeerrors(&daemon))
ok = 0;
if(!liveclean(&live))
ok = 0;
if(!ok)
return 1;
printf("ipc live listener: ok\n");
printf("ipc %s: ok\n", capacity ? "connection capacity" : "live smoke");
return 0;
}

View File

@@ -1,10 +1,33 @@
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>
#include "dat.h"
#include "fn.h"
#include "test.h"
#undef accept
#undef listen
#undef send
static int64_t
nowms(void)
{
struct timespec ts;
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
return 0;
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static void ipcclientdeadlines(struct ct*);
static void ipcconnectcloexec(struct ct*);
void
ipc_masks_modifiers(struct ct *t)
{
@@ -25,6 +48,103 @@ ipc_masks_modifiers(struct ct *t)
CT_EQ_UINT(t, 0, mod);
CT_EQ_UINT(t, 0, key);
CT_CHECK(t, ipcreqreset(buf));
/* Frontends share one keysym and modifier mapping. */
CT_EQ_UINT(t, 'a', ipckeysym('a', 'a'));
CT_EQ_UINT(t, '1', ipckeysym(0xffb1, '1')); /* KP_1 */
CT_EQ_UINT(t, Kret, ipckeysym(0xff0d, '\r'));
CT_EQ_UINT(t, Kret, ipckeysym(0xff8d, '\r')); /* KP_Enter */
CT_EQ_UINT(t, Ktab, ipckeysym(0xfe20, 0)); /* ISO_Left_Tab */
CT_EQ_UINT(t, Kback, ipckeysym(0xff08, 8));
CT_EQ_UINT(t, Kspec + 0xff, ipckeysym(0xffff, 0x7f)); /* Delete */
CT_EQ_UINT(t, 0x1f642, ipckeysym(0x101f642, 0x1f642));
CT_EQ_UINT(t, 0, ipckeysym(0xfe03, 0)); /* ISO_Level3_Shift */
CT_EQ_UINT(t, Mshift|Mctrl, ipcmod(Mshift|Mctrl|(1<<1)|(1<<4)));
CT_EQ_UINT(t, Msuper, ipcmod(1<<6));
CT_EQ_UINT(t, Msuper, ipcmod(1<<26));
}
void
ipc_control_and_caret_frames(struct ct *t)
{
static const uchar capon[Ipcreqsz] = {
Ipcext|Cclientpreedit, Ipcversion, Ipcopcap, 0, 0, 0,
};
static const uchar capoff[Ipcreqsz] = {
Ipcext, Ipcversion, Ipcopcap, 0, 0, 0,
};
static const uchar caret[Ipccaretsz] = {
Ipcext, Ipcversion, Ipcopcaret, 1,
0xfe, 0xff, 0xff, 0xff,
0x04, 0x03, 0x02, 0x01,
0x06, 0x05, 0x00, 0x00,
};
static const uchar nocaret[Ipccaretsz] = {
Ipcext, Ipcversion, Ipcopcaret, 0,
};
uchar buf[Ipccaretsz], bad[Ipccaretsz];
u32int key, mod;
int valid, want;
int32_t x, y, h;
ipcpackcap(buf, Cclientpreedit);
CT_EQ_MEM(t, capon, buf, sizeof capon);
CT_EQ_INT(t, Ipccap, ipcreqtype(buf));
/* The probe remains a harmless key-zero request to an old daemon. */
ipcunpackreq(buf, &want, &mod, &key);
CT_EQ_INT(t, 1, want);
CT_EQ_UINT(t, 0, key);
ipcpackcap(buf, 0);
CT_EQ_MEM(t, capoff, buf, sizeof capoff);
ipcunpackreq(buf, &want, &mod, &key);
CT_EQ_INT(t, 0, want);
CT_EQ_UINT(t, 0, key);
ipcpackcaret(buf, 1, -2, 0x01020304, 0x506);
CT_EQ_MEM(t, caret, buf, sizeof caret);
CT_EQ_INT(t, Ipccaret, ipcreqtype(buf));
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h))){
CT_EQ_INT(t, 1, valid);
CT_EQ_INT(t, -2, x);
CT_EQ_INT(t, 0x01020304, y);
CT_EQ_INT(t, 0x506, h);
}
ipcpackcaret(buf, 1, INT32_MIN, INT32_MAX, 0);
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h))){
CT_EQ_INT(t, INT32_MIN, x);
CT_EQ_INT(t, INT32_MAX, y);
CT_EQ_INT(t, 0, h);
}
ipcpackcaret(buf, 0, -2, 3, 4);
CT_EQ_MEM(t, nocaret, buf, sizeof nocaret);
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h)))
CT_EQ_INT(t, 0, valid);
memcpy(bad, caret, sizeof bad);
bad[1]++;
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
memcpy(bad, caret, sizeof bad);
bad[2]++;
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
memcpy(bad, caret, sizeof bad);
bad[3] = 2;
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
memcpy(bad, caret, sizeof bad);
bad[0] |= Ipcreqwant;
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
memcpy(bad, caret, sizeof bad);
bad[15] = 0x80;
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
memcpy(bad, capon, sizeof capon);
bad[3] = 1;
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
ipcpackreq(buf, 1, 0, 'a');
CT_EQ_INT(t, Ipckey, ipcreqtype(buf));
ipcpackreset(buf, 1);
CT_EQ_INT(t, Ipckey, ipcreqtype(buf));
}
void
@@ -45,7 +165,6 @@ ipc_runtime_path(struct ct *t)
snprint(fallback, sizeof fallback, "/tmp/strans.%d", getuid());
CT_EQ_STR(t, fallback, buf);
CT_EQ_INT(t, -1, ipcpath(buf, 4));
CT_EQ_INT(t, -1, ipcpath(nil, sizeof buf));
if(saved != nil){
setenv("XDG_RUNTIME_DIR", saved, 1);
free(saved);
@@ -83,22 +202,18 @@ ipc_response_pack_boundaries(struct ct *t)
CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1,
(char*)field, sizeof field, (char*)field, sizeof field, 1));
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
nil, 1, nil, 0, 0));
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
nil, 0, nil, 1, 1));
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
(char*)field, Ipcfieldmax+1, nil, 0, 0));
(char*)field, Ipcfieldmax+1, "", 0, 0));
}
void
ipc_response_empty_and_preedit(struct ct *t)
{
uchar first[Ipcmaxresp], second[Ipcmaxresp];
char commit[16], preedit[16];
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
Ipcresp resp;
int fd[2], nfirst, nsecond;
nfirst = ipcpackresp(first, sizeof first, 0, nil, 0, nil, 0, 0);
nfirst = ipcpackresp(first, sizeof first, 0, "", 0, "", 0, 0);
nsecond = ipcpackresp(second, sizeof second, 1,
"go", 2, "kana", 4, 1);
if(!CT_CHECK(t, nfirst > 0 && nsecond > 0))
@@ -109,18 +224,16 @@ ipc_response_empty_and_preedit(struct ct *t)
}
if(CT_EQ_INT(t, 0, ipcsend(fd[0], first, nfirst)) &&
CT_EQ_INT(t, 0, ipcsend(fd[0], second, nsecond)) &&
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, sizeof commit,
nil, 0, &resp))){
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, preedit, &resp))){
CT_EQ_INT(t, 0, resp.eaten);
CT_EQ_SIZE(t, 0, resp.ncommit);
CT_EQ_SIZE(t, 0, resp.npreedit);
CT_EQ_SIZE(t, 0, resp.commitlen);
CT_EQ_SIZE(t, 0, resp.preeditlen);
CT_EQ_STR(t, "", commit);
}
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, sizeof commit,
preedit, sizeof preedit, &resp))){
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit, &resp))){
CT_EQ_INT(t, 1, resp.eaten);
CT_EQ_SIZE(t, 2, resp.ncommit);
CT_EQ_SIZE(t, 4, resp.npreedit);
CT_EQ_SIZE(t, 2, resp.commitlen);
CT_EQ_SIZE(t, 4, resp.preeditlen);
CT_EQ_STR(t, "go", commit);
CT_EQ_STR(t, "kana", preedit);
}
@@ -132,14 +245,14 @@ void
ipc_response_max_and_drain(struct ct *t)
{
uchar first[Ipcmaxresp], second[Ipcmaxresp], field[Ipcfieldmax];
char commit[8], preedit[8];
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
Ipcresp resp;
int fd[2], nfirst, nsecond;
memset(field, 'x', sizeof field);
nfirst = ipcpackresp(first, sizeof first, 1,
(char*)field, sizeof field, (char*)field, sizeof field, 1);
nsecond = ipcpackresp(second, sizeof second, 0, "ok", 2, nil, 0, 0);
nsecond = ipcpackresp(second, sizeof second, 0, "ok", 2, "", 0, 0);
if(!CT_CHECK(t, nfirst == Ipcmaxresp && nsecond > 0))
return;
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
@@ -148,15 +261,15 @@ ipc_response_max_and_drain(struct ct *t)
}
if(CT_EQ_INT(t, 0, ipcsend(fd[0], first, nfirst)) &&
CT_EQ_INT(t, 0, ipcsend(fd[0], second, nsecond)) &&
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, sizeof commit,
preedit, sizeof preedit, &resp))){
CT_EQ_SIZE(t, Ipcfieldmax, resp.ncommit);
CT_EQ_SIZE(t, Ipcfieldmax, resp.npreedit);
CT_EQ_STR(t, "xxxxxxx", commit);
CT_EQ_STR(t, "xxxxxxx", preedit);
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit, &resp))){
CT_EQ_SIZE(t, Ipcfieldmax, resp.commitlen);
CT_EQ_SIZE(t, Ipcfieldmax, resp.preeditlen);
CT_EQ_MEM(t, field, commit, Ipcfieldmax);
CT_EQ_MEM(t, field, preedit, Ipcfieldmax);
CT_EQ_INT(t, 0, commit[Ipcfieldmax]);
CT_EQ_INT(t, 0, preedit[Ipcfieldmax]);
}
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, sizeof commit,
nil, 0, &resp)))
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, preedit, &resp)))
CT_EQ_STR(t, "ok", commit);
close(fd[0]);
close(fd[1]);
@@ -166,7 +279,7 @@ void
ipc_response_fragmented_and_truncated(struct ct *t)
{
uchar frame[Ipcmaxresp];
char commit[8], preedit[8];
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
Ipcresp resp;
int fd[2], i, n;
@@ -182,8 +295,8 @@ ipc_response_fragmented_and_truncated(struct ct *t)
CT_ERRORF(t, "fragment send failed");
break;
}
if(i == n && CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1,
commit, sizeof commit, preedit, sizeof preedit, &resp))){
if(i == n && CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit,
&resp))){
CT_EQ_STR(t, "abc", commit);
CT_EQ_STR(t, "xy", preedit);
}
@@ -196,8 +309,19 @@ ipc_response_fragmented_and_truncated(struct ct *t)
}
CT_EQ_INT(t, 0, ipcsend(fd[0], frame, n-1));
shutdown(fd[0], SHUT_WR);
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, sizeof commit,
preedit, sizeof preedit, &resp));
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, preedit, &resp));
close(fd[0]);
close(fd[1]);
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
CT_ERRORF(t, "socketpair failed");
return;
}
frame[0] = 2;
CT_EQ_INT(t, 0, ipcsend(fd[0], frame, n));
errno = 0;
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, preedit, &resp));
CT_EQ_INT(t, EPROTO, errno);
close(fd[0]);
close(fd[1]);
}
@@ -214,4 +338,104 @@ ipc_broken_peer_send(struct ct *t)
close(fd[1]);
CT_EQ_INT(t, -1, ipcsend(fd[0], "x", 1));
close(fd[0]);
ipcclientdeadlines(t);
ipcconnectcloexec(t);
}
static void
ipcclientdeadlines(struct ct *t)
{
char fill[4096], commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
Ipcresp resp;
int fd[2];
int64_t start, elapsed;
ssize_t n;
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
CT_ERRORF(t, "socketpair failed");
return;
}
start = nowms();
errno = 0;
CT_EQ_INT(t, -1, ipcreadresp(fd[0], 0, commit, preedit, &resp));
elapsed = nowms() - start;
CT_EQ_INT(t, ETIMEDOUT, errno);
CT_CHECK(t, elapsed >= 0 && elapsed < 4*Ipcwaitms);
memset(fill, 'x', sizeof fill);
for(;;){
n = send(fd[0], fill, sizeof fill, MSG_DONTWAIT|MSG_NOSIGNAL);
if(n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
break;
if(n <= 0){
CT_ERRORF(t, "fill send failed: %s", strerror(errno));
close(fd[0]);
close(fd[1]);
return;
}
}
start = nowms();
errno = 0;
CT_EQ_INT(t, -1, ipcsend(fd[0], "x", 1));
elapsed = nowms() - start;
CT_EQ_INT(t, ETIMEDOUT, errno);
CT_CHECK(t, elapsed >= 0 && elapsed < 4*Ipcwaitms);
close(fd[0]);
close(fd[1]);
}
static void
ipcconnectcloexec(struct ct *t)
{
struct sockaddr_un addr;
char root[] = "/tmp/strans-ipc.XXXXXX";
char *old, *saved;
int accepted, client, flags, full, listener;
accepted = client = full = listener = -1;
old = getenv("XDG_RUNTIME_DIR");
saved = old == nil ? nil : strdup(old);
if(mkdtemp(root) == nil){
CT_ERRORF(t, "mkdtemp failed: %s", strerror(errno));
free(saved);
return;
}
setenv("XDG_RUNTIME_DIR", root, 1);
memset(&addr, 0, sizeof addr);
addr.sun_family = AF_UNIX;
if(ipcpath(addr.sun_path, sizeof addr.sun_path) < 0 ||
(listener = socket(AF_UNIX, SOCK_STREAM, 0)) < 0 ||
bind(listener, (struct sockaddr*)&addr, sizeof addr) < 0 ||
listen(listener, 0) < 0){
CT_ERRORF(t, "listen failed: %s", strerror(errno));
goto Out;
}
client = ipcconnect();
if(!CT_CHECK(t, client >= 0))
goto Out;
flags = fcntl(client, F_GETFD);
CT_CHECK(t, flags >= 0 && (flags & FD_CLOEXEC) != 0);
errno = 0;
full = ipcconnect();
CT_EQ_INT(t, -1, full);
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
accepted = accept(listener, nil, nil);
if(!CT_CHECK(t, accepted >= 0))
goto Out;
Out:
if(full >= 0)
close(full);
if(accepted >= 0)
close(accepted);
if(client >= 0)
close(client);
if(listener >= 0)
close(listener);
unlink(addr.sun_path);
rmdir(root);
if(saved != nil){
setenv("XDG_RUNTIME_DIR", saved, 1);
free(saved);
}else
unsetenv("XDG_RUNTIME_DIR");
}

73
tests/keymap_test.c Normal file
View 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);
}
}

View File

@@ -1,24 +1,14 @@
#include "dat.h"
#include "fn.h"
#include "test.h"
static Str
kotrans(char *keys)
{
Emit e;
Im state;
Str out;
char *p;
Str out, raw;
memset(&state, 0, sizeof state);
state.l = getlang(LangKO);
sclear(&out);
for(p = keys; *p != '\0'; p++){
e = transko(&state, (uchar)*p);
sappend(&out, &e.s);
state.pre = e.next;
if(!e.eat)
sputr(&out, (uchar)*p);
}
sappend(&out, &state.pre);
raw = mkstr(keys);
transstr(getlang(LangKO), &raw, &out);
return out;
}

695
tests/live.c Normal file
View 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
View 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
View 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
View 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*);

View File

@@ -128,12 +128,12 @@ class MkhanjaTest(unittest.TestCase):
self.assertIn("\t", result.stdout)
def test_generator_keeps_runtime_candidate_limit(self):
rows = "".join(f"{chr(0x4E00 + n)}\t\n" for n in range(33))
rows = "".join(f"{chr(0x4E00 + n)}\t\n" for n in range(129))
result = run(GENERATE, self.source(rows))
self.assertEqual(result.returncode, 0, result.stderr)
candidates = result.stdout.rstrip().split("\t", 1)[1].split()
self.assertEqual(len(candidates), 32)
self.assertEqual(candidates[-1], chr(0x4E00 + 31))
self.assertEqual(len(candidates), 128)
self.assertEqual(candidates[-1], chr(0x4E00 + 127))
if __name__ == "__main__":

View File

@@ -1,34 +1,343 @@
#include "dat.h"
#include "fn.h"
#include "test.h"
#include <limits.h>
enum {
Rgbmask = 0xffffff,
};
static u32int
pixel(u32int *img, Popup *p, int x, int y)
{
return img[y*p->w + x] & Rgbmask;
}
static int
rectcolor(u32int *img, Popup *p, int x0, int y0, int x1, int y1,
u32int color)
{
int x, y;
color &= Rgbmask;
for(y = y0; y < y1; y++)
for(x = x0; x < x1; x++)
if(pixel(img, p, x, y) != color)
return 0;
return 1;
}
static void
checkpadding(struct ct *t, u32int *img, Popup *p)
{
CT_CHECK(t, rectcolor(img, p, 0, 0, p->w, PopupPad, Colbg));
CT_CHECK(t, rectcolor(img, p, 0, p->h - PopupPad,
p->w, p->h, Colbg));
CT_CHECK(t, rectcolor(img, p, 0, 0, PopupPad, p->h, Colbg));
CT_CHECK(t, rectcolor(img, p, p->w - PopupPad, 0,
p->w, p->h, Colbg));
}
void
popup_layout(struct ct *t)
{
Rune heart[] = { 0x2764, 0xfe0f };
int tinyheight[] = {
1, PopupPad, 2*PopupPad + Fontsz - 1,
};
static const struct {
int first;
int shown;
int total;
char *want;
} markers[] = {
{ 0, 0, 0, "" },
{ 0, 9, 9, "" },
{ 0, 3, 9, "1-3/9" },
{ 8, 1, 9, "9-9/9" },
{ 0, 9, 10, "1-9/10" },
{ 9, 1, 10, "10-10/10" },
{ 0, 9, 18, "1-9/18" },
{ 9, 9, 18, "10-18/18" },
{ 9, 9, 32, "10-18/32" },
{ 18, 9, 32, "19-27/32" },
{ 27, 5, 32, "28-32/32" },
};
char buf[32];
Area area, mon[2], out, work;
Caret caret;
int x, y;
Drawcmd dc;
Popup p;
Str longrow;
u32int c, *img;
int i, ink, markink, n, numink, selbg, textink, x, y;
for(i = 0; i < nelem(markers); i++){
n = pagemarker(buf, sizeof buf, markers[i].first,
markers[i].shown, markers[i].total);
CT_EQ_INT(t, strlen(markers[i].want), n);
CT_CHECK(t, strcmp(markers[i].want, buf) == 0);
}
/* Monitor selection retains origins and intersects the EWMH work area. */
mon[0] = (Area){0, 0, 1920, 1080};
mon[1] = (Area){1920, 200, 1280, 1024};
work = (Area){0, 30, 3200, 1070};
popuparea(mon, 2, &work, 2500, 500, &out);
CT_EQ_INT(t, 1920, out.x);
CT_EQ_INT(t, 200, out.y);
CT_EQ_INT(t, 1280, out.w);
CT_EQ_INT(t, 900, out.h);
work = (Area){60, 0, 3140, 1224};
popuparea(mon, 2, &work, 100, 100, &out);
CT_EQ_INT(t, 60, out.x);
CT_EQ_INT(t, 0, out.y);
CT_EQ_INT(t, 1860, out.w);
CT_EQ_INT(t, 1080, out.h);
work = (Area){5000, 0, 100, 100};
popuparea(mon, 2, &work, 2500, 500, &out);
CT_EQ_INT(t, mon[1].x, out.x);
CT_EQ_INT(t, mon[1].y, out.y);
CT_EQ_INT(t, mon[1].w, out.w);
CT_EQ_INT(t, mon[1].h, out.h);
mon[0] = (Area){0, 0, 100, 100};
mon[1] = (Area){200, 0, 100, 100};
popuparea(mon, 2, nil, 150, 50, &out);
CT_EQ_INT(t, 0, out.x);
CT_EQ_INT(t, 1, popupcells(heart, nelem(heart)));
memset(&caret, 0, sizeof caret);
popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y);
CT_EQ_INT(t, 50, x);
area = (Area){1920, 30, 1280, 900};
popupposition(&caret, 2000, 50, &area, 100, 60, &x, &y);
CT_EQ_INT(t, 2010, x);
CT_EQ_INT(t, 60, y);
popupposition(&caret, 2000, 900, &area, 100, 60, &x, &y);
CT_EQ_INT(t, 830, y); /* above the pointer, not over it */
caret.valid = 1;
caret.x = 70;
caret.x = 2100;
caret.y = 80;
caret.h = 20;
popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y);
CT_EQ_INT(t, 70, x);
popupposition(&caret, 0, 0, &area, 100, 60, &x, &y);
CT_EQ_INT(t, 2100, x);
CT_EQ_INT(t, 100, y);
caret.x = 790;
caret.y = 590;
popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y);
CT_EQ_INT(t, 700, x);
CT_EQ_INT(t, 540, y);
caret.x = INT_MIN;
caret.y = INT_MAX;
caret.h = INT_MAX;
popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y);
CT_EQ_INT(t, 0, x);
CT_EQ_INT(t, 540, y);
caret.x = 3150;
caret.y = 850;
popupposition(&caret, 0, 0, &area, 200, 100, &x, &y);
CT_EQ_INT(t, 3000, x);
CT_EQ_INT(t, 750, y);
/* A popup no larger than a small work area stays inside its origin. */
area = (Area){100, 200, 80, 80};
caret.x = 120;
caret.y = 230;
caret.h = 20;
popupposition(&caret, 0, 0, &area, 80, 80, &x, &y);
CT_EQ_INT(t, 100, x);
CT_EQ_INT(t, 200, y);
if(!CT_CHECK(t, textinit()))
return;
/* Empty sections reserve nothing. */
memset(&dc, 0, sizeof dc);
dc.sel = -1;
popuplayout(&dc, PopupBasew, Imgh, &p);
CT_EQ_INT(t, 0, p.w);
CT_EQ_INT(t, 0, p.h);
CT_EQ_INT(t, -1, p.prey);
CT_EQ_INT(t, -1, p.rowsy);
CT_EQ_INT(t, -1, p.marky);
/* Preedit only. */
dc.pre = mkstr("preedit");
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
CT_EQ_INT(t, PopupBasew, p.w);
CT_EQ_INT(t, PopupPad, p.prey);
CT_EQ_INT(t, -1, p.sepy);
CT_EQ_INT(t, -1, p.rowsy);
CT_EQ_INT(t, -1, p.marky);
CT_EQ_INT(t, 2*PopupPad + Fontsz, p.h);
/* Candidates only. */
memset(&dc, 0, sizeof dc);
dc.sel = -1;
dc.nkouho = 2;
dc.kouho[0] = mkstr("short");
dc.kouho[1] = mkstr("candidate");
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
CT_EQ_INT(t, PopupBasew, p.w);
CT_EQ_INT(t, -1, p.prey);
CT_EQ_INT(t, -1, p.sepy);
CT_EQ_INT(t, PopupPad, p.rowsy);
CT_EQ_INT(t, -1, p.marky);
CT_EQ_INT(t, 2*PopupPad + 2*Fontsz, p.h);
CT_EQ_INT(t, PopupTextw, p.textw);
CT_EQ_INT(t, p.w - PopupPad, PopupPad + PopupNumw + p.textw);
/* Preedit and candidates meet at one separator, with no empty rows. */
dc.pre = mkstr("preedit");
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
CT_EQ_INT(t, PopupBasew, p.w);
CT_EQ_INT(t, PopupPad, p.prey);
CT_EQ_INT(t, p.prey + Fontsz, p.sepy);
CT_EQ_INT(t, p.sepy + PopupSep, p.rowsy);
CT_EQ_INT(t, -1, p.marky);
CT_EQ_INT(t, p.rowsy + 2*Fontsz + PopupPad, p.h);
CT_EQ_INT(t, 2*PopupPad + 3*Fontsz + PopupSep, p.h);
/* Long preedit expands to the monitor cap just like a candidate. */
sclear(&longrow);
for(i = 0; i < Maxrunes; i++)
sputr(&longrow, 'W');
memset(&dc, 0, sizeof dc);
dc.pre = longrow;
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
CT_EQ_INT(t, 2*PopupBasew, p.w);
/* The largest panel includes all sections and fits its backing image. */
memset(&dc, 0, sizeof dc);
dc.pre = mkstr("preedit");
dc.nkouho = Maxdisp;
dc.sel = 4;
dc.first = 0;
dc.total = Maxdisp + 1;
for(i = 0; i < Maxdisp; i++)
dc.kouho[i] = mkstr(i == dc.sel ? "M" : "candidate");
dc.kouho[0] = longrow;
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
CT_EQ_INT(t, Maxdisp, p.n);
CT_EQ_INT(t, PopupPad, p.prey);
CT_EQ_INT(t, p.prey + Fontsz, p.sepy);
CT_EQ_INT(t, p.sepy + PopupSep, p.rowsy);
CT_EQ_INT(t, p.rowsy + Maxdisp*Fontsz, p.marky);
CT_EQ_INT(t, p.marky + Fontsz + PopupPad, p.h);
CT_EQ_INT(t, Imgh, p.h);
CT_EQ_INT(t, 2*PopupBasew, p.w);
CT_CHECK(t, p.h <= Imgh);
CT_EQ_INT(t, p.w - PopupPad, p.markx + p.markw);
CT_EQ_INT(t, p.w - 2*PopupPad, p.selw);
CT_EQ_INT(t, p.rowsy + dc.sel*Fontsz, p.sely);
CT_EQ_INT(t, p.w - PopupPad, PopupPad + PopupNumw + p.textw);
img = emalloc(2*PopupBasew*Imgh*sizeof img[0]);
popupdraw(img, &dc, &p);
checkpadding(t, img, &p);
for(x = 0; x < p.w; x++)
CT_EQ_UINT(t,
x >= PopupPad && x < p.w - PopupPad ? Colsep : Colbg,
pixel(img, &p, x, p.sepy));
/* Selection paint and both foreground columns stay inside the row. */
selbg = numink = textink = 0;
for(y = p.sely; y < p.sely + Fontsz; y++){
for(x = 0; x < p.w; x++){
c = pixel(img, &p, x, y);
if(x < PopupPad || x >= PopupPad + p.selw){
CT_EQ_UINT(t, Colbg, c);
continue;
}
if(c == (Colsel & Rgbmask)){
selbg++;
continue;
}
CT_CHECK(t,
(x >= PopupPad && x < PopupPad + PopupNumw) ||
(x >= (PopupPad + PopupNumw) && x < (PopupPad + PopupNumw) + p.textw));
if(x < PopupPad + PopupNumw)
numink++;
else
textink++;
}
}
CT_CHECK(t, Colsel != Colselfg);
CT_CHECK(t, selbg > p.selw*Fontsz/2);
CT_CHECK(t, numink > 0);
CT_CHECK(t, textink > 0);
/* The marker occupies only its compact, right-aligned footer box. */
markink = 0;
for(y = p.marky; y < p.marky + Fontsz; y++){
for(x = 0; x < p.w; x++){
if(pixel(img, &p, x, y) == (Colbg & Rgbmask))
continue;
markink++;
CT_CHECK(t, x >= p.markx && x < p.markx + p.markw);
}
}
CT_CHECK(t, markink > 0);
/* A long candidate grows from the stable base to the monitor cap. */
memset(&dc, 0, sizeof dc);
dc.sel = -1;
dc.nkouho = 1;
dc.kouho[0] = longrow;
n = textwidth(&longrow);
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
CT_EQ_INT(t, 2*PopupBasew, p.w);
CT_CHECK(t, p.textw > PopupTextw);
CT_CHECK(t, n > p.textw);
CT_EQ_INT(t, PopupPad, p.rowsy);
CT_EQ_INT(t, p.w - PopupPad, PopupPad + PopupNumw + p.textw);
popupdraw(img, &dc, &p);
ink = 0;
for(y = p.rowsy; y < p.rowsy + Fontsz; y++){
for(x = 0; x < p.w; x++){
c = pixel(img, &p, x, y);
if(x < PopupPad || x >= p.w - PopupPad)
CT_EQ_UINT(t, Colbg, c);
if(x >= (PopupPad + PopupNumw) && x < (PopupPad + PopupNumw) + p.textw &&
c != (Colbg & Rgbmask))
ink++;
}
}
CT_CHECK(t, ink > 0);
CT_EQ_INT(t, n, textwidth(&longrow));
popuplayout(&dc, PopupBasew - 1, Imgh, &p);
CT_EQ_INT(t, PopupBasew - 1, p.w);
/* Height truncation scrolls the row window to the selected candidate. */
memset(&dc, 0, sizeof dc);
dc.nkouho = Maxdisp;
dc.sel = Maxdisp - 1;
dc.total = Maxdisp;
for(i = 0; i < Maxdisp; i++)
dc.kouho[i] = mkstr("candidate");
popuplayout(&dc, PopupBasew, 2*PopupPad + 2*Fontsz, &p);
CT_EQ_INT(t, 1, p.n);
CT_EQ_INT(t, Maxdisp - 1, p.row0);
CT_EQ_INT(t, PopupPad, p.sely);
CT_CHECK(t, p.marky >= 0);
CT_CHECK(t, p.h <= 2*PopupPad + 2*Fontsz);
popupdraw(img, &dc, &p);
numink = 0;
for(y = p.rowsy; y < p.rowsy + Fontsz; y++)
for(x = PopupPad; x < PopupPad + PopupNumw; x++)
if(pixel(img, &p, x, y) != (Colsel & Rgbmask))
numink++;
markink = 0;
for(y = p.marky; y < p.marky + Fontsz; y++)
for(x = p.markx; x < p.markx + p.markw; x++)
if(pixel(img, &p, x, y) != (Colbg & Rgbmask))
markink++;
CT_CHECK(t, numink > 0); /* full-width */
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);
}

View File

@@ -3,25 +3,19 @@
#include <sys/stat.h>
#include <unistd.h>
#include "../srv.c"
#include "../cutest/cutest.h"
void testengineinit(int);
void testenginehandle(Keyreq*);
void* testengineowner(void);
void testenginepreedit(Str*);
#include "srv.c"
#include "test.h"
#undef recv
typedef struct Enginegate Enginegate;
typedef struct Serverfix Serverfix;
typedef struct Testclient Testclient;
struct Enginegate
/* A held pump plus a private client-slot channel of the wanted depth. */
struct Serverfix
{
Channel *seen;
Channel *go;
Channel *stop;
Channel *done;
Pump pump;
Channel *oldclientc;
};
struct Testclient
@@ -34,30 +28,30 @@ struct Testclient
};
static void
enginegate(void *arg)
serverbegin(Serverfix *f, int nclients)
{
Enginegate *g;
Keyreq req;
Drawcmd dc;
uchar token;
Alt alts[] = {
{keyc, &req, CHANRCV, nil},
{nil, &token, CHANRCV, nil},
{nil, nil, CHANEND, nil},
};
g = arg;
alts[1].c = g->stop;
for(;;)
switch(alt(alts)){
case 0:
chansend(g->seen, &req);
chanrecv(g->go, &token);
testenginehandle(&req);
break;
case 1:
chansend(g->done, &token);
return;
}
f->oldclientc = clientc;
while(channbrecv(drawc, &dc) > 0)
;
clientc = chancreate(sizeof token, nclients);
testengineinit(LangJP);
pumpstart(&f->pump, 0);
pumphold(&f->pump, Pumpall);
}
static void
serverend(Serverfix *f)
{
Drawcmd dc;
pumpstop(&f->pump);
chanfree(clientc);
clientc = f->oldclientc;
while(channbrecv(drawc, &dc) > 0)
;
}
static void
@@ -142,38 +136,70 @@ sendreset(struct ct *t, Testclient *client, int want)
}
static Keyreq
nextrequest(struct ct *t, Enginegate *g, int op)
nextrequestcap(struct ct *t, Pump *p, int op, int cap)
{
Keyreq req;
memset(&req, 0, sizeof req);
chanrecv(g->seen, &req);
chanrecv(p->trace, &req);
CT_EQ_INT(t, op, req.op);
CT_EQ_INT(t, cap, req.cap);
return req;
}
static Keyreq
nextrequest(struct ct *t, Pump *p, int op)
{
return nextrequestcap(t, p, op, Cclientpreedit);
}
static void
allowrequest(Enginegate *g)
allowrequest(Pump *p)
{
uchar token;
token = 0;
chansend(g->go, &token);
chansend(p->go, &token);
}
static int
readreply(struct ct *t, Testclient *client, int want, char *preedit, int npreedit)
readreply(struct ct *t, Testclient *client, int want, char *wantcommit,
char *preedit)
{
char commit[Maxutf];
char commit[Ipcfieldmax+1];
Ipcresp resp;
if(ipcreadresp(client->peer, want, commit, sizeof commit,
preedit, npreedit, &resp) < 0)
if(ipcreadresp(client->peer, want, commit, preedit, &resp) < 0)
return CT_ERRORF(t, "response read failed: %s", strerror(errno));
CT_EQ_STR(t, "", commit);
CT_EQ_STR(t, wantcommit, commit);
return resp.eaten;
}
static int
sendframe(struct ct *t, Testclient *client, uchar *frame, int n, int fragment)
{
int i, m;
for(i = 0; i < n; i += m){
m = fragment && n-i > 1 ? 1 : n-i;
if(ipcsend(client->peer, frame+i, m) < 0)
return CT_ERRORF(t, "frame send failed: %s", strerror(errno));
}
return 1;
}
static void
checknoreply(struct ct *t, Testclient *client)
{
uchar byte;
ssize_t n;
errno = 0;
n = recv(client->peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
CT_EQ_INT(t, -1, n);
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
}
static void
waitclient(struct ct *t, Testclient *client)
{
@@ -191,127 +217,112 @@ waitclient(struct ct *t, Testclient *client)
}
static void
disconnectclient(struct ct *t, Enginegate *g, Testclient *client, void *owner)
disconnectclient(struct ct *t, Pump *p, Testclient *client, void *owner)
{
Keyreq req;
uchar token;
close(client->peer);
client->peer = -1;
req = nextrequest(t, g, Keyrelease);
req = nextrequest(t, p, Keyrelease);
if(owner != nil)
CT_EQ_PTR(t, owner, req.owner);
CT_CHECK(t, channbrecv(client->done, &token) <= 0);
allowrequest(g);
allowrequest(p);
waitclient(t, client);
}
void
server_connection_ownership(struct ct *t)
{
Channel *oldclientc;
Enginegate gate;
Serverfix f;
Testclient a, b, c;
Keyreq req;
Drawcmd dc;
Str shown;
char preedit[Maxutf];
char preedit[Ipcfieldmax+1];
void *aowner, *bowner, *cowner;
uchar byte, token;
ssize_t n;
int gateactive;
memset(&gate, 0, sizeof gate);
memset(&a, 0, sizeof a);
memset(&b, 0, sizeof b);
memset(&c, 0, sizeof c);
a.fd = a.peer = b.fd = b.peer = c.fd = c.peer = -1;
aowner = bowner = cowner = nil;
oldclientc = clientc;
while(channbrecv(drawc, &dc) > 0)
;
clientc = chancreate(sizeof token, 3);
gate.seen = chancreate(sizeof(Keyreq), 0);
gate.go = chancreate(sizeof token, 0);
gate.stop = chancreate(sizeof token, 0);
gate.done = chancreate(sizeof token, 0);
testengineinit(LangJP);
gateactive = proccreate(enginegate, &gate, 8192) >= 0;
if(!CT_CHECK(t, gateactive))
goto cleanup;
serverbegin(&f, 3);
if(!startclient(t, &a) || !startclient(t, &b))
goto cleanup;
if(!sendkey(t, &a, 1, 0, 'k'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
req = nextrequest(t, &f.pump, Keypress);
aowner = req.owner;
allowrequest(&gate);
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
CT_EQ_STR(t, "k", preedit);
if(!sendkey(t, &a, 1, 0, 'a'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
req = nextrequest(t, &f.pump, Keypress);
CT_EQ_PTR(t, aowner, req.owner);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
CT_EQ_PTR(t, aowner, testengineowner());
if(!sendkey(t, &b, 1, 0, 'n'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
req = nextrequest(t, &f.pump, Keypress);
bowner = req.owner;
CT_CHECK(t, bowner != aowner);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
CT_EQ_PTR(t, bowner, testengineowner());
if(!sendreset(t, &a, 1))
goto cleanup;
req = nextrequest(t, &gate, Keyreset);
req = nextrequest(t, &f.pump, Keyreset);
CT_EQ_PTR(t, aowner, req.owner);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
CT_EQ_PTR(t, bowner, testengineowner());
if(!sendkey(t, &b, 1, 0, Kmodfirst))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
allowrequest(&gate);
CT_CHECK(t, !readreply(t, &b, 1, preedit, sizeof preedit));
req = nextrequest(t, &f.pump, Keypress);
allowrequest(&f.pump);
CT_CHECK(t, !readreply(t, &b, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
disconnectclient(t, &gate, &a, aowner);
disconnectclient(t, &f.pump, &a, aowner);
CT_EQ_PTR(t, bowner, testengineowner());
if(!sendkey(t, &b, 1, 0, 'y'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
req = nextrequest(t, &f.pump, Keypress);
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
if(!sendkey(t, &b, 1, 0, 'a'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
req = nextrequest(t, &f.pump, Keypress);
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
CT_EQ_STR(t, "にゃ", preedit);
if(!sendreset(t, &b, 1))
goto cleanup;
req = nextrequest(t, &gate, Keyreset);
req = nextrequest(t, &f.pump, Keyreset);
CT_EQ_PTR(t, bowner, req.owner);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &b, 1, "にゃ", preedit));
CT_EQ_STR(t, "", preedit);
CT_EQ_PTR(t, bowner, testengineowner());
if(!sendkey(t, &b, 0, 0, 'k'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
req = nextrequestcap(t, &f.pump, Keypress, 0);
CT_EQ_PTR(t, bowner, req.owner);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &b, 0, nil, 0));
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &b, 0, "", preedit));
errno = 0;
n = recv(b.peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
CT_EQ_INT(t, -1, n);
@@ -319,12 +330,12 @@ server_connection_ownership(struct ct *t)
CT_EQ_PTR(t, bowner, testengineowner());
if(!sendkey(t, &b, 1, 0, 'a'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
allowrequest(&gate);
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
req = nextrequest(t, &f.pump, Keypress);
allowrequest(&f.pump);
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
disconnectclient(t, &gate, &b, bowner);
disconnectclient(t, &f.pump, &b, bowner);
CT_EQ_PTR(t, nil, testengineowner());
testenginepreedit(&shown);
CT_EQ_INT(t, 0, shown.n);
@@ -333,38 +344,205 @@ server_connection_ownership(struct ct *t)
goto cleanup;
if(!sendkey(t, &c, 1, 0, 'k'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
req = nextrequest(t, &f.pump, Keypress);
cowner = req.owner;
close(c.peer);
c.peer = -1;
allowrequest(&gate);
req = nextrequest(t, &gate, Keyrelease);
allowrequest(&f.pump);
req = nextrequest(t, &f.pump, Keyrelease);
CT_EQ_PTR(t, cowner, req.owner);
CT_EQ_PTR(t, cowner, testengineowner());
CT_CHECK(t, channbrecv(c.done, &token) <= 0);
allowrequest(&gate);
allowrequest(&f.pump);
waitclient(t, &c);
CT_EQ_PTR(t, nil, testengineowner());
CT_CHECK(t, channbrecv(clientc, &token) <= 0);
cleanup:
if(a.peer >= 0)
disconnectclient(t, &gate, &a, aowner);
disconnectclient(t, &f.pump, &a, aowner);
if(b.peer >= 0)
disconnectclient(t, &gate, &b, bowner);
disconnectclient(t, &f.pump, &b, bowner);
if(c.peer >= 0)
disconnectclient(t, &gate, &c, cowner);
if(gateactive){
token = 0;
chansend(gate.stop, &token);
chanrecv(gate.done, &token);
}
chanfree(gate.seen);
chanfree(gate.go);
chanfree(gate.stop);
chanfree(gate.done);
chanfree(clientc);
clientc = oldclientc;
while(channbrecv(drawc, &dc) > 0)
;
disconnectclient(t, &f.pump, &c, cowner);
serverend(&f);
}
void
server_extension_stream(struct ct *t)
{
Serverfix f;
Testclient a, b;
Keyreq req;
uchar frame[Ipccaretsz];
char preedit[Ipcfieldmax+1];
void *aowner, *bowner;
memset(&a, 0, sizeof a);
memset(&b, 0, sizeof b);
a.fd = a.peer = b.fd = b.peer = -1;
aowner = bowner = nil;
serverbegin(&f, 2);
if(!startclient(t, &a))
goto cleanup;
/* Capability and caret frames may be split at any byte boundary. */
ipcpackcap(frame, Cclientpreedit);
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
goto cleanup;
req = nextrequest(t, &f.pump, Keycap);
aowner = req.owner;
CT_EQ_PTR(t, nil, testengineowner());
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
ipcpackcaret(frame, 1, -101, -7, 23);
if(!sendframe(t, &a, frame, Ipccaretsz, 1))
goto cleanup;
req = nextrequest(t, &f.pump, Keycaret);
CT_EQ_PTR(t, aowner, req.owner);
CT_EQ_INT(t, 1, req.caret.valid);
CT_EQ_INT(t, -101, req.caret.x);
CT_EQ_INT(t, -7, req.caret.y);
CT_EQ_INT(t, 23, req.caret.h);
CT_EQ_PTR(t, nil, testengineowner());
allowrequest(&f.pump);
checknoreply(t, &a);
ipcpackreq(frame, 1, 0, 'k');
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
goto cleanup;
req = nextrequest(t, &f.pump, Keypress);
CT_EQ_PTR(t, aowner, req.owner);
CT_EQ_INT(t, 1, req.caret.valid);
CT_EQ_INT(t, -101, req.caret.x);
CT_EQ_INT(t, -7, req.caret.y);
CT_EQ_INT(t, 23, req.caret.h);
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
CT_EQ_STR(t, "k", preedit);
CT_EQ_PTR(t, aowner, testengineowner());
ipcpackcap(frame, 0);
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
goto cleanup;
req = nextrequestcap(t, &f.pump, Keycap, 0);
CT_EQ_PTR(t, aowner, req.owner);
CT_EQ_INT(t, 1, req.caret.valid);
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &a, 0, "", preedit));
/* Reset is still the old six-byte frame and does not discard caret. */
ipcpackreset(frame, 0);
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
goto cleanup;
req = nextrequestcap(t, &f.pump, Keyreset, 0);
CT_EQ_PTR(t, aowner, req.owner);
CT_EQ_INT(t, 1, req.caret.valid);
CT_EQ_INT(t, -101, req.caret.x);
CT_EQ_INT(t, -7, req.caret.y);
CT_EQ_INT(t, 23, req.caret.h);
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &a, 0, "k", preedit));
ipcpackcap(frame, Cclientpreedit);
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
goto cleanup;
req = nextrequest(t, &f.pump, Keycap);
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
ipcpackreq(frame, 1, 0, 'n');
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
goto cleanup;
req = nextrequest(t, &f.pump, Keypress);
CT_EQ_INT(t, 1, req.caret.valid);
CT_EQ_INT(t, -101, req.caret.x);
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
CT_EQ_STR(t, "", preedit);
disconnectclient(t, &f.pump, &a, aowner);
/* Disconnect drops state; a legacy client gets the historical defaults. */
if(!startclient(t, &b) || !sendkey(t, &b, 1, 0, 'k'))
goto cleanup;
req = nextrequest(t, &f.pump, Keypress);
bowner = req.owner;
CT_EQ_INT(t, 0, req.caret.valid);
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &b, 1, "", preedit));
CT_EQ_STR(t, "k", preedit);
disconnectclient(t, &f.pump, &b, bowner);
cleanup:
if(a.peer >= 0)
disconnectclient(t, &f.pump, &a, aowner);
if(b.peer >= 0)
disconnectclient(t, &f.pump, &b, bowner);
serverend(&f);
}
void
server_rejects_unknown_extension(struct ct *t)
{
Serverfix f;
Testclient badver, badop, good;
Keyreq req;
uchar frame[Ipccaretsz];
char preedit[Ipcfieldmax+1];
void *owner;
memset(&badver, 0, sizeof badver);
memset(&badop, 0, sizeof badop);
memset(&good, 0, sizeof good);
badver.fd = badver.peer = badop.fd = badop.peer = -1;
good.fd = good.peer = -1;
owner = nil;
serverbegin(&f, 3);
if(!startclient(t, &badver))
goto cleanup;
ipcpackcaret(frame, 1, 3, 4, 5);
frame[1]++;
if(!sendframe(t, &badver, frame, sizeof frame, 0))
goto cleanup;
req = nextrequest(t, &f.pump, Keyrelease);
allowrequest(&f.pump);
waitclient(t, &badver);
close(badver.peer);
badver.peer = -1;
if(!startclient(t, &badop))
goto cleanup;
ipcpackcaret(frame, 1, 3, 4, 5);
frame[2]++;
if(!sendframe(t, &badop, frame, sizeof frame, 1))
goto cleanup;
req = nextrequest(t, &f.pump, Keyrelease);
allowrequest(&f.pump);
waitclient(t, &badop);
close(badop.peer);
badop.peer = -1;
/* A malformed peer must not disturb another client connection. */
if(!startclient(t, &good) || !sendkey(t, &good, 1, 0, 'k'))
goto cleanup;
req = nextrequest(t, &f.pump, Keypress);
owner = req.owner;
allowrequest(&f.pump);
CT_EQ_INT(t, 1, readreply(t, &good, 1, "", preedit));
CT_EQ_STR(t, "k", preedit);
disconnectclient(t, &f.pump, &good, owner);
cleanup:
if(badver.peer >= 0)
disconnectclient(t, &f.pump, &badver, nil);
if(badop.peer >= 0)
disconnectclient(t, &f.pump, &badop, nil);
if(good.peer >= 0)
disconnectclient(t, &f.pump, &good, owner);
serverend(&f);
}

View File

@@ -1,21 +1,52 @@
#include "dat.h"
#include "fn.h"
#include "test.h"
void
str_init_utf8(struct ct *t)
{
static char badtail[] = { 'a', (char)0x80 };
static char incomplete[] = { (char)0xea, (char)0xb0 };
static char overlong[] = { (char)0xc0, (char)0xaf };
static char outofrange[] = {
(char)0xf4, (char)0x90, (char)0x80, (char)0x80,
};
static char surrogate[] = {
(char)0xed, (char)0xa0, (char)0x80,
};
static char unexpected[] = { (char)0x80 };
static const struct {
char *name;
char *src;
int n;
int ok;
char *want;
} cases[] = {
{ "canonical Runeerror", "\xef\xbf\xbd", 3, 1, "\xef\xbf\xbd" },
{ "incomplete", incomplete, sizeof incomplete, 0, "" },
{ "unexpected continuation", unexpected, sizeof unexpected, 0, "" },
{ "overlong", overlong, sizeof overlong, 0, "" },
{ "surrogate", surrogate, sizeof surrogate, 0, "" },
{ "out of range", outofrange, sizeof outofrange, 0, "" },
{ "bad tail", badtail, sizeof badtail, 0, "" },
};
char full[Maxrunes+2];
char incomplete[] = { (char)0xea, (char)0xb0 };
Str s;
int i;
s = mkstr("A한😀");
CT_EQ_INT(t, 3, s.n);
checkstr(t, "round trip", "A한😀", &s);
sinit(&s, incomplete, sizeof incomplete);
CT_EQ_INT(t, 0, s.n);
for(i = 0; i < nelem(cases); i++){
memset(&s, 0xa5, sizeof s);
if(sinit(&s, cases[i].src, cases[i].n) != cases[i].ok)
CT_ERRORF(t, "%s: wrong validity", cases[i].name);
checkstr(t, cases[i].name, cases[i].want, &s);
}
memset(full, 'a', sizeof full);
full[sizeof full-1] = '\0';
sinit(&s, full, strlen(full));
CT_EQ_INT(t, Maxrunes, s.n);
CT_CHECK(t, !sinit(&s, full, strlen(full)));
CT_EQ_INT(t, 0, s.n);
}
void
@@ -45,7 +76,6 @@ str_utf8_capacity(struct ct *t)
int n;
char *want;
} cases[] = {
{ 0, 0, nil },
{ 1, 0, "" },
{ 2, 1, "a" },
{ 3, 1, "a" },
@@ -64,10 +94,6 @@ str_utf8_capacity(struct ct *t)
if(n != cases[i].n)
CT_ERRORF(t, "size %d: want length %d, got %d",
cases[i].size, cases[i].n, n);
if(cases[i].size == 0){
CT_EQ_INT(t, 'Z', buf[0]);
continue;
}
if(strcmp(cases[i].want, buf) != 0)
CT_ERRORF(t, "size %d: want \"%s\", got \"%s\"",
cases[i].size, cases[i].want, buf);
@@ -89,10 +115,6 @@ str_invalid_and_full_appends(struct ct *t)
Str s;
int i;
sinit(&s, nil, 1);
CT_EQ_INT(t, 0, s.n);
sinit(&s, "x", -1);
CT_EQ_INT(t, 0, s.n);
for(i = 0; i < Maxrunes; i++)
s.r[i] = 'a';
s.n = Maxrunes;

View File

@@ -1,22 +1,50 @@
#include "../cutest/cutest.h"
#include "../dat.h"
#include "../fn.h"
/* Included after dat.h and fn.h (or after the source under test). */
#include "cutest/cutest.h"
extern Lang testvi;
/*
* A test's stand-in for imthread: handles engine requests from keyc,
* tracing each one first. Requests whose op is holdop (every request
* when holdop is Pumpall) wait on go before the engine sees them.
*/
typedef struct Pump Pump;
struct Pump
{
Channel *trace;
Channel *go;
Channel *stop;
Channel *done;
int holdop;
int active;
};
enum
{
Pumpnone = -1,
Pumpall = -2,
};
Str mkstr(char*);
int checkstr(struct ct*, char*, char*, Str*);
Str shownpre(Im*);
void checkenginepreedit(struct ct*, char*);
void pumpstart(Pump*, int);
void pumphold(Pump*, int);
void pumpstop(Pump*);
/* Engine internals reached through engine_test.c. */
void testengineinit(int);
void testenginehandle(Keyreq*);
void* testengineowner(void);
void testenginepreedit(Str*);
void testenginecaret(Caret*);
void str_init_utf8(struct ct*);
void str_edit_and_alias(struct ct*);
void str_utf8_capacity(struct ct*);
void str_invalid_and_full_appends(struct ct*);
void hmap_set_replace_and_grow(struct ct*);
void hmap_long_utf8_keys(struct ct*);
void hmap_binary_keys_and_invalid_lengths(struct ct*);
void trie_exact_prefix_and_duplicate(struct ct*);
void trie_optional_outputs_and_invalid_lengths(struct ct*);
void trie_put_and_unloaded(struct ct*);
void popup_layout(struct ct*);
void font_render(struct ct*);
void production_maps_load(struct ct*);
@@ -32,10 +60,15 @@ void engine_clears_candidates(struct ct*);
void engine_backspace_clears_candidates(struct ct*);
void engine_selects_visible_candidate(struct ct*);
void engine_candidate_shortcut_modifiers(struct ct*);
void engine_dictionary_queue_latest_wins(struct ct*);
void engine_candidate_page_metadata(struct ct*);
void engine_candidate_page_snapshots(struct ct*);
void engine_candidate_page_movement(struct ct*);
void engine_candidate_completion(struct ct*);
void engine_active_owner_lifecycle(struct ct*);
void engine_active_owner_reset(struct ct*);
void engine_active_owner_caret(struct ct*);
void engine_popup_preedit_capability(struct ct*);
void engine_vietnamese_client_preedit(struct ct*);
void engine_commit_contract(struct ct*);
void engine_language_switch_state(struct ct*);
void engine_telex_history_bound(struct ct*);
@@ -50,9 +83,9 @@ void engine_emoji_queries(struct ct*);
void engine_emoji_japanese_and_multirune(struct ct*);
void engine_emoji_digit_aliases(struct ct*);
void engine_emoji_navigation(struct ct*);
void engine_search_candidate_keys(struct ct*);
void engine_emoji_preedit_languages(struct ct*);
void engine_emoji_start_and_unknown(struct ct*);
void engine_emoji_dictionary_identity(struct ct*);
void engine_hanja_search(struct ct*);
void engine_hanja_unknown_and_cancel(struct ct*);
void engine_hanja_korean_keys(struct ct*);
@@ -61,9 +94,10 @@ void engine_hanja_input_languages(struct ct*);
void engine_randomized_stress(struct ct*);
void engine_full_boundary_passthrough(struct ct*);
void dictionary_candidates(struct ct*);
void dictionary_misses_clear_result(struct ct*);
void dictionary_misses(struct ct*);
void dictionary_emoji_identity(struct ct*);
void ipc_masks_modifiers(struct ct*);
void ipc_control_and_caret_frames(struct ct*);
void ipc_runtime_path(struct ct*);
void ipc_response_pack_boundaries(struct ct*);
void ipc_response_empty_and_preedit(struct ct*);
@@ -71,7 +105,25 @@ void ipc_response_max_and_drain(struct ct*);
void ipc_response_fragmented_and_truncated(struct ct*);
void ipc_broken_peer_send(struct ct*);
void server_connection_ownership(struct ct*);
void server_extension_stream(struct ct*);
void server_rejects_unknown_extension(struct ct*);
void ibus_machine_id_fallback(struct ct*);
void ibus_startup_requires_ownership(struct ct*);
void ibus_capability_policy(struct ct*);
void ibus_private_input_policy(struct ct*);
void ibus_context_lifecycle(struct ct*);
void ibus_active_release_lifecycle(struct ct*);
void xim_keymap_lookup(struct ct*);
void xim_compound_text(struct ct*);
void xim_adapter_key_contract(struct ct*);
void xim_adapter_release_lifecycle(struct ct*);
void xim_adapter_free_waits_for_release(struct ct*);
void xim_adapter_styles(struct ct*);
void xim_adapter_placement(struct ct*);
void xim_adapter_placement_updates(struct ct*);
void xim_adapter_callback_replacement(struct ct*);
void xim_adapter_callback_unicode(struct ct*);
void xim_adapter_callback_cleanup(struct ct*);
void xim_adapter_callback_transfer(struct ct*);
void xim_adapter_callback_owner_loss(struct ct*);
void xim_adapter_commit_encoding(struct ct*);

View File

@@ -1,3 +1,5 @@
#include "dat.h"
#include "fn.h"
#include "test.h"
Str
@@ -21,22 +23,82 @@ checkstr(struct ct *t, char *where, char *want, Str *got)
where, want, buf);
}
Str
shownpre(Im *state)
void
checkenginepreedit(struct ct *t, char *want)
{
Str mapped, shown;
Str preedit;
if(state->l->lang == LangJP || state->l->lang == LangJPK){
shown = state->pre;
if(state->raw.n == 0)
return shown;
if(mapget(state->l->map, &state->raw, &mapped))
sappend(&shown, &mapped);
else
sappend(&shown, &state->raw);
return shown;
}
if(state->l->map != nil && mapget(state->l->map, &state->pre, &shown))
return shown;
return state->pre;
testenginepreedit(&preedit);
checkstr(t, "engine preedit", want, &preedit);
}
static void
pumpthread(void *arg)
{
Pump *p;
Keyreq req;
uchar token;
Alt alts[] = {
{nil, &req, CHANRCV, nil},
{nil, &token, CHANRCV, nil},
{nil, nil, CHANEND, nil},
};
p = arg;
alts[0].c = keyc;
alts[1].c = p->stop;
for(;;)
switch(alt(alts)){
case 0:
chansend(p->trace, &req);
if(p->holdop == Pumpall || p->holdop == req.op)
chanrecv(p->go, &token);
testenginehandle(&req);
break;
case 1:
chansend(p->done, &token);
return;
}
}
/* ntrace is the trace channel's depth: 0 makes every trace a rendezvous. */
void
pumpstart(Pump *p, int ntrace)
{
memset(p, 0, sizeof *p);
p->trace = chancreate(sizeof(Keyreq), ntrace);
p->go = chancreate(sizeof(uchar), 0);
p->stop = chancreate(sizeof(uchar), 0);
p->done = chancreate(sizeof(uchar), 0);
p->holdop = Pumpnone;
/* Its own proc: tests block in socket I/O while the engine runs. */
proccreate(pumpthread, p, 8192);
p->active = 1;
}
void
pumphold(Pump *p, int op)
{
p->holdop = op;
}
/* Every held request must have been let go before stopping. */
void
pumpstop(Pump *p)
{
Keyreq req;
uchar token;
if(!p->active)
return;
while(channbrecv(p->trace, &req) > 0)
;
token = 0;
chansend(p->stop, &token);
chanrecv(p->done, &token);
chanfree(p->trace);
chanfree(p->go);
chanfree(p->stop);
chanfree(p->done);
p->active = 0;
}

View File

@@ -1,54 +1,71 @@
#include "dat.h"
#include "fn.h"
#include "test.h"
void
trie_exact_prefix_and_duplicate(struct ct *t)
{
char *v, *sentinel;
static const struct {
char *key;
int match;
char *want;
} cases[] = {
{ "a", TrieExact, "alpha" },
{ "ab", TrieExact, "beta" },
{ "", TrieExact, "" },
{ "dupli", TriePrefix, nil },
{ "duplicate", TrieExact, "second" },
{ "missing", TrieMiss, nil },
{ "", TriePrefix, nil },
};
char *v;
Trie *trie;
int n;
Str key;
int i, match, n;
trie = trieopen("data/trie.map");
v = trieget(trie, "a", 1, &n);
if(!CT_CHECK(t, v != nil))
goto cleanup;
CT_EQ_INT(t, 5, n);
CT_EQ_MEM(t, "alpha", v, n);
sentinel = "unchanged";
v = sentinel;
n = 77;
CT_CHECK(t, trielookup(trie, "dupli", 5, &v, &n));
CT_EQ_PTR(t, sentinel, v);
CT_EQ_INT(t, 77, n);
v = trieget(trie, "duplicate", 9, &n);
if(!CT_CHECK(t, v != nil))
goto cleanup;
CT_EQ_INT(t, 6, n);
CT_EQ_MEM(t, "second", v, n);
CT_EQ_PTR(t, nil, trieget(trie, "missing", 7, &n));
cleanup:
for(i = 0; i < nelem(cases); i++){
key = mkstr(cases[i].key);
match = trielookup(trie, &key, &v, &n);
if(match != cases[i].match){
CT_ERRORF(t, "case %d: want match %d, got %d",
i, cases[i].match, match);
continue;
}
if(cases[i].want == nil){
CT_EQ_PTR(t, nil, v);
CT_EQ_INT(t, 0, n);
}else{
CT_EQ_INT(t, strlen(cases[i].want), n);
CT_EQ_MEM(t, cases[i].want, v, n);
}
}
trieclose(trie);
}
void
trie_optional_outputs_and_invalid_lengths(struct ct *t)
trie_put_and_unloaded(struct ct *t)
{
char *v;
Trie *trie;
Str key;
int n;
trie = trieopen("data/trie.map");
CT_CHECK(t, trieget(trie, "a", 1, nil) != nil);
v = nil;
CT_CHECK(t, trielookup(trie, "a", 1, &v, nil));
CT_CHECK(t, v != nil);
n = -1;
CT_CHECK(t, trielookup(trie, "a", 1, nil, &n));
CT_EQ_INT(t, 5, n);
CT_CHECK(t, trielookup(trie, "dupli", 5, nil, nil));
CT_EQ_PTR(t, nil, trieget(trie, nil, 1, &n));
CT_EQ_PTR(t, nil, trieget(trie, "a", -1, &n));
CT_CHECK(t, !trielookup(trie, nil, 1, &v, &n));
CT_CHECK(t, !trielookup(trie, "a", -1, &v, &n));
key = mkstr("k");
CT_EQ_INT(t, TrieMiss, trielookup(nil, &key, &v, &n));
trie = trienew();
CT_EQ_INT(t, TrieMiss, trielookup(trie, &key, &v, &n));
trieput(trie, "k", 1, "one two", 7);
trieput(trie, "ka", 2, "", 0);
if(CT_EQ_INT(t, TrieExact, trielookup(trie, &key, &v, &n)))
CT_EQ_MEM(t, "one two", v, 7);
key = mkstr("ka");
if(CT_EQ_INT(t, TrieExact, trielookup(trie, &key, &v, &n)))
CT_EQ_INT(t, 0, n);
trieput(trie, "k", 1, "three", 5);
key = mkstr("k");
if(CT_EQ_INT(t, TrieExact, trielookup(trie, &key, &v, &n)))
CT_EQ_MEM(t, "three", v, 5);
trieclose(trie);
}
@@ -84,13 +101,12 @@ transmap_states(struct ct *t)
int eat;
char *emit;
char *next;
char *mapped;
} cases[] = {
{ "", 'k', 1, "", "k", "" },
{ "k", 'a', 1, "", "ka", "" },
{ "ka", 's', 1, "", "s", "" },
{ "ka", 'q', 0, "", "", "" },
{ "k", 'q', 0, "k", "", "" },
{ "", 'k', 1, "", "k" },
{ "k", 'a', 1, "", "ka" },
{ "ka", 's', 1, "", "s" },
{ "ka", 'q', 0, "", "" },
{ "k", 'q', 0, "k", "" },
};
Emit e;
Im state;
@@ -110,7 +126,6 @@ transmap_states(struct ct *t)
i, cases[i].eat, e.eat);
checkstr(t, "emit", cases[i].emit, &e.s);
checkstr(t, "next", cases[i].next, &e.next);
checkstr(t, "mapped", cases[i].mapped, &e.dict);
}
trieclose(fixture);
state.l->map = saved;

View File

@@ -1,12 +1,12 @@
#define CT_IMPLEMENTATION
#include "dat.h"
#include "fn.h"
#include "test.h"
#include <stdarg.h>
Channel *drawc;
Channel *keyc;
Channel *dictreqc;
Channel *dictresc;
Lang testvi;
void
@@ -61,16 +61,14 @@ testmapinit(void)
testvi.map = trieopen("../map/telex.map");
}
#ifndef STRESS
static const struct ct_test tests[] = {
{ "str/init-utf8", str_init_utf8 },
{ "str/edit-and-alias", str_edit_and_alias },
{ "str/utf8-capacity", str_utf8_capacity },
{ "str/invalid-full-appends", str_invalid_and_full_appends },
{ "hmap/set-replace-grow", hmap_set_replace_and_grow },
{ "hmap/long-utf8-keys", hmap_long_utf8_keys },
{ "hmap/binary-invalid-lengths", hmap_binary_keys_and_invalid_lengths },
{ "trie/exact-prefix-duplicate", trie_exact_prefix_and_duplicate },
{ "trie/optional-invalid-lengths", trie_optional_outputs_and_invalid_lengths },
{ "trie/put-and-unloaded", trie_put_and_unloaded },
{ "popup/layout", popup_layout },
{ "font/render", font_render },
{ "map/production-lifecycle", production_maps_load },
@@ -86,10 +84,15 @@ static const struct ct_test tests[] = {
{ "engine/backspace-clears-candidates", engine_backspace_clears_candidates },
{ "engine/selects-visible-candidate", engine_selects_visible_candidate },
{ "engine/candidate-shortcut-modifiers", engine_candidate_shortcut_modifiers },
{ "engine/dictionary-queue-latest", engine_dictionary_queue_latest_wins },
{ "engine/candidate-page-metadata", engine_candidate_page_metadata },
{ "engine/candidate-page-snapshots", engine_candidate_page_snapshots },
{ "engine/candidate-page-movement", engine_candidate_page_movement },
{ "engine/candidate-completion", engine_candidate_completion },
{ "engine/active-owner-lifecycle", engine_active_owner_lifecycle },
{ "engine/active-owner-reset", engine_active_owner_reset },
{ "engine/active-owner-caret", engine_active_owner_caret },
{ "engine/popup-preedit-capability", engine_popup_preedit_capability },
{ "engine/vietnamese-client-preedit", engine_vietnamese_client_preedit },
{ "engine/commit-contract", engine_commit_contract },
{ "engine/language-switch-state", engine_language_switch_state },
{ "engine/telex-history-bound", engine_telex_history_bound },
@@ -104,20 +107,20 @@ static const struct ct_test tests[] = {
{ "engine/emoji-japanese-multirune", engine_emoji_japanese_and_multirune },
{ "engine/emoji-digit-aliases", engine_emoji_digit_aliases },
{ "engine/emoji-navigation", engine_emoji_navigation },
{ "engine/search-candidate-keys", engine_search_candidate_keys },
{ "engine/emoji-preedit-languages", engine_emoji_preedit_languages },
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
{ "engine/emoji-dictionary-identity", engine_emoji_dictionary_identity },
{ "engine/hanja-search", engine_hanja_search },
{ "engine/hanja-unknown-cancel", engine_hanja_unknown_and_cancel },
{ "engine/hanja-korean-keys", engine_hanja_korean_keys },
{ "engine/hanja-backspace", engine_hanja_backspace },
{ "engine/hanja-input-languages", engine_hanja_input_languages },
{ "engine/randomized-stress", engine_randomized_stress },
{ "engine/full-boundary-passthrough", engine_full_boundary_passthrough },
{ "dict/candidates", dictionary_candidates },
{ "dict/misses-clear-result", dictionary_misses_clear_result },
{ "dict/misses", dictionary_misses },
{ "dict/emoji-identity", dictionary_emoji_identity },
{ "ipc/masks-modifiers", ipc_masks_modifiers },
{ "ipc/control-caret-frames", ipc_control_and_caret_frames },
{ "ipc/runtime-path", ipc_runtime_path },
{ "ipc/response-pack-boundaries", ipc_response_pack_boundaries },
{ "ipc/response-empty-preedit", ipc_response_empty_and_preedit },
@@ -125,11 +128,34 @@ static const struct ct_test tests[] = {
{ "ipc/response-fragmented-truncated", ipc_response_fragmented_and_truncated },
{ "ipc/broken-peer-send", ipc_broken_peer_send },
{ "server/connection-ownership", server_connection_ownership },
{ "server/extension-stream", server_extension_stream },
{ "server/rejects-unknown-extension", server_rejects_unknown_extension },
{ "ibus/machine-id-fallback", ibus_machine_id_fallback },
{ "ibus/startup-requires-ownership", ibus_startup_requires_ownership },
{ "ibus/capability-policy", ibus_capability_policy },
{ "ibus/private-input-policy", ibus_private_input_policy },
{ "ibus/context-lifecycle", ibus_context_lifecycle },
{ "ibus/active-release-lifecycle", ibus_active_release_lifecycle },
{ "xim/keymap-lookup", xim_keymap_lookup },
{ "xim/compound-text", xim_compound_text },
{ "xim/adapter-key-contract", xim_adapter_key_contract },
{ "xim/adapter-release-lifecycle", xim_adapter_release_lifecycle },
{ "xim/adapter-free-waits-release", xim_adapter_free_waits_for_release },
{ "xim/styles", xim_adapter_styles },
{ "xim/placement", xim_adapter_placement },
{ "xim/placement-updates", xim_adapter_placement_updates },
{ "xim/callback-replacement", xim_adapter_callback_replacement },
{ "xim/callback-unicode", xim_adapter_callback_unicode },
{ "xim/callback-cleanup", xim_adapter_callback_cleanup },
{ "xim/callback-transfer", xim_adapter_callback_transfer },
{ "xim/callback-owner-loss", xim_adapter_callback_owner_loss },
{ "xim/commit-encoding", xim_adapter_commit_encoding },
};
#else
static const struct ct_test tests[] = {
{ "engine/randomized-stress", engine_randomized_stress },
};
#endif
void
threadmain(int argc, char **argv)
@@ -138,8 +164,6 @@ threadmain(int argc, char **argv)
drawc = chancreate(sizeof(Drawcmd), 4);
keyc = chancreate(sizeof(Keyreq), 0);
dictreqc = chancreate(sizeof(Dictreq), 64);
dictresc = chancreate(sizeof(Dictres), 0);
testmapinit();
status = CT_RUN_ARGS(tests, argc, argv);
for(i = 0; i < nlang; i++){
@@ -150,7 +174,5 @@ threadmain(int argc, char **argv)
testvi.map = nil;
chanfree(drawc);
chanfree(keyc);
chanfree(dictreqc);
chanfree(dictresc);
threadexitsall(status == 0 ? nil : "tests failed");
}

View File

@@ -1,25 +1,14 @@
#include "dat.h"
#include "fn.h"
#include "test.h"
static void
typevi(struct ct *t, char *keys, char *want)
{
Emit e;
Im state;
Str out, shown;
char *p;
Str out, raw;
memset(&state, 0, sizeof state);
state.l = &testvi;
sclear(&out);
for(p = keys; *p != '\0'; p++){
e = transvi(&state, (uchar)*p);
sappend(&out, &e.s);
state.pre = e.next;
if(!e.eat)
sputr(&out, (uchar)*p);
}
shown = shownpre(&state);
sappend(&out, &shown);
raw = mkstr(keys);
transstr(&testvi, &raw, &out);
checkstr(t, keys, want, &out);
}
@@ -46,6 +35,11 @@ vietnamese_transitions(struct ct *t)
{ "gias", "giá" },
{ "Gias", "Giá" },
{ "gif", "" },
{ "cuar", "của" },
{ "vowis", "với" },
{ "cuwar", "cửa" },
{ "nguyeenx", "nguyễn" },
{ "dduwowngf", "đường" },
{ "\\s", "s" },
{ "a\\s", "as" },
};
@@ -78,7 +72,7 @@ vietnamese_backspace(struct ct *t)
state.raw = mkstr(cases[i].raw);
backvi(&state);
checkstr(t, cases[i].raw, cases[i].rawafter, &state.raw);
shown = shownpre(&state);
impre(&state, &shown);
checkstr(t, cases[i].raw, cases[i].after, &shown);
}
}

1106
tests/xim_adapter_test.c Normal file

File diff suppressed because it is too large Load Diff

822
tests/xim_live_test.c Normal file
View 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;
}

View File

@@ -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
View File

@@ -1,12 +1,19 @@
#include <errno.h>
#include "dat.h"
#include "fn.h"
/*
* A byte trie over UTF-8 keys. Maps need prefix matches while composing;
* dictionaries need exact matches; both are small enough for one structure.
* Node 0 is the root.
*/
static int
newnode(Trie *t)
{
int i;
if(t->n >= t->cap){
if(t->n == t->cap){
t->cap *= 2;
t->nodes = erealloc(t->nodes, t->cap * sizeof(Tnode));
}
@@ -40,13 +47,25 @@ add(Trie *t, int ni, char c)
return pi;
}
static void
insert(Trie *t, char *key, int klen, char *val, int vlen)
Trie*
trienew(void)
{
int ni, ci;
int i;
Trie *t;
ni = t->root;
t = emalloc(sizeof(*t));
t->cap = 1024;
t->nodes = emalloc(t->cap * sizeof(Tnode));
t->n = 0;
newnode(t);
return t;
}
void
trieput(Trie *t, char *key, int klen, char *val, int vlen)
{
int ni, ci, i;
ni = 0;
for(i = 0; i < klen; i++){
ci = find(t, ni, key[i]);
if(ci < 0)
@@ -60,47 +79,64 @@ insert(Trie *t, char *key, int klen, char *val, int vlen)
t->nodes[ni].vlen = vlen;
}
static char*
readline(Biobuf *b, char *path)
{
char *line;
errno = 0;
line = Brdstr(b, '\n', 1);
if(errno != 0)
die("can't read %s: %r", path);
return line;
}
/*
* A file holds "key<tab>value" lines; ';' starts a comment. Keys and each
* space-separated word of a value must fit a Str, which is what lookups
* hand back.
*/
Trie*
trieopen(char *path)
{
Trie *t;
Biobuf *b;
char *line, *tab, *key, *val;
int klen, vlen;
Str s;
char *e, *line, *p, *tab;
int len, lineno;
b = Bopen(path, OREAD);
if(b == nil)
die("can't open: %s", path);
t = emalloc(sizeof(*t));
t->cap = 1024;
t->nodes = emalloc(t->cap * sizeof(Tnode));
t->n = 0;
t->root = newnode(t);
while((line = Brdstr(b, '\n', 1)) != nil){
vlen = strlen(line);
if(vlen > 0 && line[vlen-1] == '\r')
line[--vlen] = '\0';
if(line[0] == '\0' || line[0] == ';'){
die("can't open %s: %r", path);
t = trienew();
for(lineno = 1; (line = readline(b, path)) != nil; lineno++){
len = Blinelen(b);
if(memchr(line, '\0', len) != nil)
die("%s:%d: NUL byte", path, lineno);
if(len > 0 && line[len-1] == '\r')
line[--len] = '\0';
if(len == 0 || line[0] == ';'){
free(line);
continue;
}
tab = strchr(line, '\t');
if(tab == nil || tab == line || tab[1] == '\0' ||
strchr(tab+1, '\t') != nil)
die("malformed map: %s", path);
*tab = '\0';
key = line;
klen = tab - line;
if(utflen(key) > Maxrunes)
die("map key too long: %s", path);
val = tab + 1;
vlen = strlen(val);
if(utflen(val) > Maxrunes)
die("map value too long: %s", path);
insert(t, key, klen, val, vlen);
tab = memchr(line, '\t', len);
if(tab == nil || tab == line || tab == line+len-1 ||
memchr(tab+1, '\t', line+len-(tab+1)) != nil)
die("%s:%d: expected key, tab, value", path, lineno);
if(!sinit(&s, line, tab-line))
die("%s:%d: invalid or oversized key", path, lineno);
for(p = tab+1; p < line+len; p = e+1){
e = memchr(p, ' ', line+len-p);
if(e == nil)
e = line+len;
if(!sinit(&s, p, e-p))
die("%s:%d: invalid or oversized value", path, lineno);
}
trieput(t, line, tab-line, tab+1, line+len-(tab+1));
free(line);
}
Bterm(b);
if(Bterm(b) < 0)
die("can't close %s: %r", path);
return t;
}
@@ -117,46 +153,27 @@ trieclose(Trie *t)
free(t);
}
char*
trieget(Trie *t, char *key, int klen, int *vlen)
/* A nil trie is an unloaded map: every key misses. */
int
trielookup(Trie *t, Str *key, char **val, int *vlen)
{
int ni;
int i;
char buf[Maxutf];
int i, klen, ni;
if(t == nil || klen < 0 || (klen > 0 && key == nil))
return nil;
ni = t->root;
*val = nil;
*vlen = 0;
if(t == nil)
return TrieMiss;
klen = stoutf(key, buf, sizeof buf);
ni = 0;
for(i = 0; i < klen; i++){
ni = find(t, ni, key[i]);
ni = find(t, ni, buf[i]);
if(ni < 0)
return nil;
return TrieMiss;
}
if(t->nodes[ni].val == nil)
return nil;
if(vlen != nil)
*vlen = t->nodes[ni].vlen;
return t->nodes[ni].val;
}
int
trielookup(Trie *t, char *key, int klen, char **val, int *vlen)
{
int ni;
int i;
if(t == nil || klen < 0 || (klen > 0 && key == nil))
return 0;
ni = t->root;
for(i = 0; i < klen; i++){
ni = find(t, ni, key[i]);
if(ni < 0)
return 0;
}
if(t->nodes[ni].val != nil){
if(val != nil)
*val = t->nodes[ni].val;
if(vlen != nil)
*vlen = t->nodes[ni].vlen;
}
return 1;
return TriePrefix;
*val = t->nodes[ni].val;
*vlen = t->nodes[ni].vlen;
return TrieExact;
}

69
vi.c
View File

@@ -31,12 +31,6 @@ static struct {
{L'Y', {L'Ý', L'', L'', L'', L''}},
};
static int
istone(Rune c)
{
return c == 's' || c == 'f' || c == 'r' || c == 'x' || c == 'j';
}
static int
toneidx(Rune c)
{
@@ -114,20 +108,44 @@ onsetglide(Str *m)
return -1;
}
Emit
transvi(Im *im, Rune c)
/*
* The pending text is the key sequence itself, read through the map for
* display; raw keeps every key of it so Backspace can replay one fewer.
*/
static Emit
history(Im *im, Rune c, Emit e)
{
Emit e, mappedkey;
Str mapped;
e.raw = im->raw;
if(e.s.n > 0 || !e.eat)
sclear(&e.raw);
if(!e.eat || e.next.n == 0)
return e;
if(e.raw.n < Maxrunes){
sputr(&e.raw, c);
return e;
}
/* The history is full: flush the composed text as it stands. */
if(!mapget(im->l->map, &e.next, &mapped))
mapped = e.next;
sappend(&e.s, &mapped);
sclear(&e.next);
sclear(&e.raw);
return e;
}
static Emit
tone(Im *im, Rune c)
{
Emit e;
Str mapped, pre;
int i, tidx, vi, last, penult, glide;
Rune v, b1, b2;
if(!istone(c) && c != 'z')
return transmap(im, c);
mappedkey = transmap(im, c);
if(mappedkey.eat)
return mappedkey;
e = transmap(im, c);
if(e.eat)
return e;
memset(&e, 0, sizeof e);
if(im->pre.n == 0)
return e;
@@ -175,17 +193,24 @@ transvi(Im *im, Rune c)
return e;
}
if(c == 'z')
tidx = toneidx(c);
if(tidx < 0)
mapped.r[vi] = removetone(mapped.r[vi]);
else{
tidx = toneidx(c);
else
mapped.r[vi] = applytone(mapped.r[vi], tidx);
}
e.eat = 1;
e.next = mapped;
return e;
}
Emit
transvi(Im *im, Rune c)
{
if(toneidx(c) < 0 && c != 'z')
return history(im, c, transmap(im, c));
return history(im, c, tone(im, c));
}
void
backvi(Im *im)
{
@@ -200,10 +225,10 @@ backvi(Im *im)
raw = im->raw;
spopr(&raw);
sclear(&im->pre);
sclear(&im->raw);
for(i = 0; i < raw.n; i++){
e = transvi(im, raw.r[i]);
sclear(&im->pre);
sappend(&im->pre, &e.next);
im->pre = e.next;
im->raw = e.raw;
}
im->raw = raw;
}

314
win.c
View File

@@ -1,21 +1,17 @@
#include <xcb/xcb.h>
#include <xcb/randr.h>
#include "dat.h"
#include "fn.h"
enum {
Asciitofull = 0xFEE0,
};
extern char **fontfiles;
extern int nfontfiles;
static xcb_connection_t *conn;
static xcb_screen_t *scr;
static xcb_window_t win;
static xcb_gcontext_t gc;
static xcb_pixmap_t pix;
static u32int *img;
static int depth;
static xcb_atom_t currentdesktop;
static xcb_atom_t workarea;
static int hasrandr, imgh, imgw;
static xcb_screen_t*
getscr(xcb_connection_t *c, int n)
@@ -46,6 +42,7 @@ getvisual(xcb_screen_t *s)
return nil;
}
/* putimage writes native-endian x8r8g8b8; the root must take that. */
static int
validformat(xcb_connection_t *c, xcb_screen_t *s)
{
@@ -57,9 +54,8 @@ validformat(xcb_connection_t *c, xcb_screen_t *s)
setup = xcb_get_setup(c);
v = getvisual(s);
if(setup == nil || v == nil || s->root_depth != 24 ||
if(v == nil || s->root_depth != 24 ||
v->_class != XCB_VISUAL_CLASS_TRUE_COLOR ||
v->bits_per_rgb_value != 8 ||
v->red_mask != 0xff0000 || v->green_mask != 0x00ff00 ||
v->blue_mask != 0x0000ff)
return 0;
@@ -76,19 +72,129 @@ validformat(xcb_connection_t *c, xcb_screen_t *s)
return 0;
}
static xcb_atom_t
getatom(char *name)
{
xcb_intern_atom_cookie_t cookie;
xcb_intern_atom_reply_t *reply;
xcb_atom_t atom;
cookie = xcb_intern_atom(conn, 0, strlen(name), name);
reply = xcb_intern_atom_reply(conn, cookie, nil);
atom = reply == nil ? XCB_ATOM_NONE : reply->atom;
free(reply);
return atom;
}
static int
getcardinals(xcb_atom_t atom, u32int off, int n, u32int *v)
{
xcb_get_property_cookie_t cookie;
xcb_get_property_reply_t *reply;
int ok;
if(atom == XCB_ATOM_NONE)
return 0;
cookie = xcb_get_property(conn, 0, scr->root, atom,
XCB_ATOM_CARDINAL, off, n);
reply = xcb_get_property_reply(conn, cookie, nil);
ok = reply != nil && reply->type == XCB_ATOM_CARDINAL &&
reply->format == 32 &&
xcb_get_property_value_length(reply) == n*(int)sizeof v[0];
if(ok)
memmove(v, xcb_get_property_value(reply), n*sizeof v[0]);
free(reply);
return ok;
}
static int
getworkarea(Area *a)
{
u32int desktop, v[4];
if(!getcardinals(currentdesktop, 0, 1, &desktop) ||
desktop > 0x3fffffff ||
!getcardinals(workarea, desktop*4, 4, v) ||
v[0] > 0x7fffffff || v[1] > 0x7fffffff ||
v[2] == 0 || v[2] > 0x7fffffff ||
v[3] == 0 || v[3] > 0x7fffffff)
return 0;
a->x = v[0];
a->y = v[1];
a->w = v[2];
a->h = v[3];
return 1;
}
static void
getrootarea(Area *a)
{
xcb_get_geometry_cookie_t cookie;
xcb_get_geometry_reply_t *reply;
a->x = 0;
a->y = 0;
a->w = scr->width_in_pixels;
a->h = scr->height_in_pixels;
cookie = xcb_get_geometry(conn, scr->root);
reply = xcb_get_geometry_reply(conn, cookie, nil);
if(reply != nil && reply->width > 0 && reply->height > 0){
a->w = reply->width;
a->h = reply->height;
}
free(reply);
}
/* The area the popup may use: the RandR monitor under (x, y), or the
* root, cut down to the EWMH work area. */
static void
popupwork(int x, int y, Area *out)
{
xcb_randr_get_monitors_cookie_t cookie;
xcb_randr_get_monitors_reply_t *reply;
xcb_randr_monitor_info_iterator_t it;
Area root, net, *mon;
int n;
mon = nil;
n = 0;
reply = nil;
if(hasrandr){
cookie = xcb_randr_get_monitors(conn, scr->root, 1);
reply = xcb_randr_get_monitors_reply(conn, cookie, nil);
}
if(reply != nil && xcb_randr_get_monitors_monitors_length(reply) > 0){
mon = emalloc(xcb_randr_get_monitors_monitors_length(reply) *
sizeof mon[0]);
it = xcb_randr_get_monitors_monitors_iterator(reply);
for(; it.rem; xcb_randr_monitor_info_next(&it)){
if(it.data->width == 0 || it.data->height == 0)
continue;
mon[n].x = it.data->x;
mon[n].y = it.data->y;
mon[n].w = it.data->width;
mon[n].h = it.data->height;
n++;
}
}
if(n == 0){
free(mon);
getrootarea(&root);
mon = &root;
n = 1;
}
popuparea(mon, n, getworkarea(&net) ? &net : nil, x, y, out);
if(mon != &root)
free(mon);
free(reply);
}
static void
wincleanup(void)
{
textclose();
free(img);
img = nil;
if(conn != nil){
xcb_disconnect(conn);
conn = nil;
}
scr = nil;
win = 0;
gc = 0;
pix = 0;
xcb_disconnect(conn);
}
static int
@@ -96,11 +202,17 @@ wininit(void)
{
int n;
u32int mask, vals[4];
xcb_intern_atom_cookie_t c1, c2;
xcb_intern_atom_reply_t *r1, *r2;
xcb_atom_t type, tooltip;
xcb_randr_query_version_cookie_t rc;
xcb_randr_query_version_reply_t *rr;
const xcb_query_extension_reply_t *rext;
char *scale;
scale = getenv("GDK_SCALE");
if(scale != nil && atoi(scale) > 1)
popupscale = min(atoi(scale), 4);
conn = xcb_connect(nil, &n);
if(conn == nil || xcb_connection_has_error(conn)){
if(xcb_connection_has_error(conn)){
fprint(2, "strans: popup disabled: cannot connect to X display\n");
wincleanup();
return 0;
@@ -111,7 +223,16 @@ wininit(void)
wincleanup();
return 0;
}
depth = scr->root_depth;
rext = xcb_get_extension_data(conn, &xcb_randr_id);
if(rext != nil && rext->present){
rc = xcb_randr_query_version(conn, 1, 5);
rr = xcb_randr_query_version_reply(conn, rc, nil);
hasrandr = rr != nil && (rr->major_version > 1 ||
(rr->major_version == 1 && rr->minor_version >= 5));
free(rr);
}
currentdesktop = getatom("_NET_CURRENT_DESKTOP");
workarea = getatom("_NET_WORKAREA");
win = xcb_generate_id(conn);
mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
XCB_CW_OVERRIDE_REDIRECT | XCB_CW_SAVE_UNDER;
@@ -122,26 +243,14 @@ wininit(void)
xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, scr->root,
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
scr->root_visual, mask, vals);
c1 = xcb_intern_atom(conn, 0,
strlen("_NET_WM_WINDOW_TYPE"), "_NET_WM_WINDOW_TYPE");
c2 = xcb_intern_atom(conn, 0,
strlen("_NET_WM_WINDOW_TYPE_TOOLTIP"),
"_NET_WM_WINDOW_TYPE_TOOLTIP");
r1 = xcb_intern_atom_reply(conn, c1, nil);
r2 = xcb_intern_atom_reply(conn, c2, nil);
if(r1 != nil && r2 != nil)
type = getatom("_NET_WM_WINDOW_TYPE");
tooltip = getatom("_NET_WM_WINDOW_TYPE_TOOLTIP");
if(type != XCB_ATOM_NONE && tooltip != XCB_ATOM_NONE)
xcb_change_property(conn, XCB_PROP_MODE_REPLACE,
win, r1->atom, XCB_ATOM_ATOM, 32, 1, &r2->atom);
free(r1);
free(r2);
win, type, XCB_ATOM_ATOM, 32, 1, &tooltip);
gc = xcb_generate_id(conn);
xcb_create_gc(conn, gc, win, 0, nil);
pix = xcb_generate_id(conn);
xcb_create_pixmap(conn, depth, pix, win, Imgw, Imgh);
mask = XCB_CW_BACK_PIXMAP;
xcb_change_window_attributes(conn, win, mask, &pix);
img = emalloc(Imgw * Imgh * sizeof(img[0]));
if(!fontinit(fontfiles, nfontfiles)){
if(!textinit()){
fprint(2, "strans: popup disabled: no usable fonts\n");
wincleanup();
return 0;
@@ -149,53 +258,50 @@ wininit(void)
return 1;
}
static void
drawstr(u32int *buf, int x, int y, Rune *r, int n, int maxw, int maxh)
/* The image and its retained pixmap only ever grow. */
static int
resizebacking(int w, int h)
{
while(n-- > 0){
if(*r != 0xfe0e && *r != 0xfe0f){
putfont(buf, maxw, maxh, x, y, *r);
x += Fontsz;
}
r++;
xcb_generic_error_t *err;
xcb_pixmap_t old, new;
xcb_void_cookie_t cookie;
int nh, nw;
if(w <= imgw && h <= imgh)
return 1;
nw = max(w, imgw);
nh = max(h, imgh);
img = erealloc(img, (ulong)nw * nh * sizeof img[0]);
new = xcb_generate_id(conn);
cookie = xcb_create_pixmap_checked(conn, scr->root_depth, new, win,
nw, nh);
err = xcb_request_check(conn, cookie);
if(err != nil){
free(err);
return 0;
}
}
static void
fill(u32int *buf, int n, u32int color)
{
int i;
for(i = 0; i < n; i++)
buf[i] = color;
}
static void
drawkouho(Drawcmd *dc, int n, int w, int h)
{
int npre, sely, y, i;
Str *s;
fill(img, w * h, Colbg);
npre = dc->pre.n != 0;
if(npre)
drawstr(img, 0, 0, dc->pre.r, dc->pre.n, w, h);
if(dc->sel >= 0 && dc->sel < n){
sely = (npre + dc->sel) * Fontsz;
fill(img + sely * w, Fontsz * w, Colsel);
}
for(i = 0, y = npre * Fontsz; i < n; i++, y += Fontsz){
s = &dc->kouho[i];
putfont(img, w, h, 0, y, '1' + i + Asciitofull);
drawstr(img, 2*Fontsz, y, s->r, s->n, w, h);
cookie = xcb_change_window_attributes_checked(conn, win,
XCB_CW_BACK_PIXMAP, &new);
err = xcb_request_check(conn, cookie);
if(err != nil){
free(err);
xcb_free_pixmap(conn, new);
return 0;
}
old = pix;
pix = new;
imgw = nw;
imgh = nh;
if(old != 0)
xcb_free_pixmap(conn, old);
return 1;
}
static void
putimage(int w, int h)
{
xcb_put_image(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc,
w, h, 0, 0, 0, depth, w * h * 4, (u8int*)img);
w, h, 0, 0, 0, scr->root_depth, w * h * 4, (u8int*)img);
/* The retained background pixmap lets the server repaint exposures. */
xcb_clear_area(conn, 0, win, 0, 0, w, h);
}
@@ -207,24 +313,19 @@ winhide(void)
return xcb_flush(conn) > 0;
}
/* Draws dc at the caret, or by the pointer when the caret is unknown. */
static int
winshow(Drawcmd *dc)
{
int npre, px, py, w, h, i, n, maxw;
u32int vals[4];
Area area;
Popup p;
int ax, ay, px, py, x, y;
u32int vals[5];
xcb_query_pointer_reply_t *ptr;
xcb_query_pointer_cookie_t cookie;
n = min(max(dc->nkouho, 0), Maxdisp);
npre = dc->pre.n != 0;
if(n == 0 && npre == 0){
if(dc->nkouho == 0 && dc->pre.n == 0)
return winhide();
}
maxw = popupcells(dc->pre.r, dc->pre.n);
for(i = 0; i < n; i++)
maxw = max(maxw, popupcells(dc->kouho[i].r, dc->kouho[i].n));
vals[3] = h = (n + npre) * Fontsz;
vals[2] = w = (maxw + 3) * Fontsz;
px = py = 0;
if(!dc->caret.valid){
cookie = xcb_query_pointer(conn, scr->root);
@@ -235,17 +336,26 @@ winshow(Drawcmd *dc)
py = ptr->root_y;
free(ptr);
}
popupposition(&dc->caret, px, py, scr->width_in_pixels,
scr->height_in_pixels, w, h, &px, &py);
vals[1] = py;
vals[0] = px;
ax = dc->caret.valid ? dc->caret.x : px;
ay = dc->caret.valid ? dc->caret.y : py;
popupwork(ax, ay, &area);
popuplayout(dc, area.w, area.h, &p);
if(p.w <= 0 || p.h <= 0 || !resizebacking(p.w, p.h))
return 0;
popupposition(&dc->caret, px, py, &area, p.w, p.h, &x, &y);
/* Mapping does not restack: raise above windows opened since. */
vals[0] = x;
vals[1] = y;
vals[2] = p.w;
vals[3] = p.h;
vals[4] = XCB_STACK_MODE_ABOVE;
xcb_configure_window(conn, win,
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
vals);
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_STACK_MODE, vals);
popupdraw(img, dc, &p);
putimage(p.w, p.h);
xcb_map_window(conn, win);
drawkouho(dc, n, w, h);
putimage(w, h);
return xcb_flush(conn) > 0;
}
@@ -257,14 +367,8 @@ drawthread(void*)
threadsetname("draw");
if(!wininit())
return;
while(chanrecv(drawc, &dc) > 0){
while(channbrecv(drawc, &dc) > 0)
;
if(dc.nkouho == 0 && dc.pre.n == 0){
if(!winhide())
break;
}else if(!winshow(&dc))
while(chanrecv(drawc, &dc) > 0)
if(!winshow(&dc))
break;
}
wincleanup();
}

View File

@@ -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

View File

@@ -1,37 +1,37 @@
#include <stdint.h>
#include <xcb/xcb.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-names.h>
enum
{
ShiftMask = 1<<0,
LockMask = 1<<1,
GroupShift = 13,
GroupMask = 3,
};
/* The keysym for an X core key event, with its modifier and group state. */
uint32_t
keymaplookup(const uint32_t *syms, int nsyms, uint16_t state)
keymaplookup(struct xkb_state *state, uint8_t keycode, uint16_t corestate)
{
uint32_t lo, hi, lower, upper;
int col, group, shift;
static const char *modname[] = {
XKB_MOD_NAME_SHIFT,
XKB_MOD_NAME_CAPS,
XKB_MOD_NAME_CTRL,
XKB_MOD_NAME_MOD1,
XKB_MOD_NAME_MOD2,
XKB_MOD_NAME_MOD3,
XKB_MOD_NAME_MOD4,
XKB_MOD_NAME_MOD5,
};
struct xkb_keymap *keymap;
xkb_mod_index_t index;
xkb_mod_mask_t mods;
int i;
if(syms == NULL || nsyms <= 0)
return XKB_KEY_NoSymbol;
group = (state >> GroupShift) & GroupMask;
col = group * 2;
if(col >= nsyms || syms[col] == XKB_KEY_NoSymbol)
col = 0;
lo = syms[col];
if(lo == XKB_KEY_NoSymbol)
return XKB_KEY_NoSymbol;
hi = col + 1 < nsyms ? syms[col + 1] : XKB_KEY_NoSymbol;
lower = xkb_keysym_to_lower(lo);
upper = xkb_keysym_to_upper(lo);
if(hi == XKB_KEY_NoSymbol)
hi = lower != upper ? upper : lo;
shift = (state & ShiftMask) != 0;
/* Lock reverses Shift only for keys with an alphabetic case pair. */
if((state & LockMask) && lower != upper)
shift = !shift;
return shift ? hi : lo;
keymap = xkb_state_get_keymap(state);
mods = 0;
for(i = 0; i < (int)(sizeof modname / sizeof modname[0]); i++){
if(!(corestate & (1U << i)))
continue;
index = xkb_keymap_mod_get_index(keymap, modname[i]);
if(index != XKB_MOD_INVALID)
mods |= (xkb_mod_mask_t)1 << index;
}
xkb_state_update_mask(state, mods, 0, 0, 0, 0,
(corestate >> 13) & 3);
return xkb_state_key_get_one_sym(state, keycode);
}

View File

@@ -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
View 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");
}

View File

@@ -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);
}