Compare commits
53 Commits
ea363ca792
...
544ed835b7
| Author | SHA1 | Date | |
|---|---|---|---|
| 544ed835b7 | |||
| ae7dbae993 | |||
| 2e53627b7d | |||
| 3527bcd489 | |||
| 6a6749e824 | |||
| c2aec44aa9 | |||
| cf8eca2ff1 | |||
| d129f406d5 | |||
| e54783dc92 | |||
| 3a90826238 | |||
| 0ba7dc7f50 | |||
| ef7fb627c6 | |||
| a0e83f98c6 | |||
| 34c0c501aa | |||
| 0f684ae6d3 | |||
| c102c87d55 | |||
| bfa919f623 | |||
| 5a6f352d72 | |||
| 88ad5f7630 | |||
| 33023d7f51 | |||
| 46bb8b1d46 | |||
| 82cb07eba2 | |||
| b979afc3f7 | |||
| dd389edc4f | |||
| 268b687ee6 | |||
| e32cea3296 | |||
| a03fb05324 | |||
| 42568ab020 | |||
| 7c9e736996 | |||
| 0cb395579f | |||
| 33848709a5 | |||
| 47f9801ad8 | |||
| fcdce7dc16 | |||
| 0c4271b8b5 | |||
| bab40c95a7 | |||
| 87cb03fdf8 | |||
| cd637be4ca | |||
| 5faefd9a87 | |||
| 3d862bdc0d | |||
| 97a838eda1 | |||
| 1c8d9fac07 | |||
| 97e6f2cf1a | |||
| 852e129f62 | |||
| 1285476b41 | |||
| f0498868eb | |||
| ab0366b7f7 | |||
| f61f7ab5f8 | |||
| 95dff0b82b | |||
| abff5ed122 | |||
| 221117f93d | |||
| b77400fa86 | |||
| 63fc60dc06 | |||
| e8519b4e06 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -11,8 +11,6 @@
|
|||||||
/tests/daemon_collision_test
|
/tests/daemon_collision_test
|
||||||
/tests/daemon_failure_test
|
/tests/daemon_failure_test
|
||||||
/tests/daemon_restart_test
|
/tests/daemon_restart_test
|
||||||
/xim/*.o
|
|
||||||
/xim/xim_test
|
|
||||||
/gtk/im-strans.so
|
/gtk/im-strans.so
|
||||||
/bench/bench
|
/bench/bench
|
||||||
/bench/perf.data*
|
/bench/perf.data*
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ RUN pacman -Syu --noconfirm --needed \
|
|||||||
which \
|
which \
|
||||||
xorg-server-xvfb \
|
xorg-server-xvfb \
|
||||||
xcb-imdkit \
|
xcb-imdkit \
|
||||||
|
xcb-util \
|
||||||
dbus \
|
dbus \
|
||||||
&& pacman -Scc --noconfirm
|
&& pacman -Scc --noconfirm
|
||||||
|
|
||||||
@@ -35,7 +36,6 @@ RUN export DEBUGINFOD_URLS=https://debuginfod.archlinux.org \
|
|||||||
"$debugdir/${buildid#??}.debug" \
|
"$debugdir/${buildid#??}.debug" \
|
||||||
&& rm -rf /root/.cache/debuginfod_client
|
&& rm -rf /root/.cache/debuginfod_client
|
||||||
|
|
||||||
RUN printf "egrep='grep -E'\n" > /usr/lib/plan9/config
|
|
||||||
RUN dbus-uuidgen --ensure=/etc/machine-id
|
RUN dbus-uuidgen --ensure=/etc/machine-id
|
||||||
|
|
||||||
ENV PLAN9=/usr/lib/plan9
|
ENV PLAN9=/usr/lib/plan9
|
||||||
|
|||||||
@@ -6,4 +6,8 @@
|
|||||||
- `GPL-2.0-or-later.txt` contains GNU GPL version 2. The header of
|
- `GPL-2.0-or-later.txt` contains GNU GPL version 2. The header of
|
||||||
`map/kanji.dict` grants the option to use GPL version 2 or any later
|
`map/kanji.dict` grants the option to use GPL version 2 or any later
|
||||||
version.
|
version.
|
||||||
|
- `Unicode-3.0.txt` contains the Unicode License v3, the terms for the
|
||||||
|
emoji names and keywords in `map/emoji.src` and generated
|
||||||
|
`map/emoji.dict`, derived from Unicode's `emoji-test.txt` and CLDR's
|
||||||
|
annotations.
|
||||||
No repository-wide license has been declared for original strans source.
|
No repository-wide license has been declared for original strans source.
|
||||||
|
|||||||
39
LICENSES/Unicode-3.0.txt
Normal file
39
LICENSES/Unicode-3.0.txt
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
UNICODE LICENSE V3
|
||||||
|
|
||||||
|
COPYRIGHT AND PERMISSION NOTICE
|
||||||
|
|
||||||
|
Copyright © 1991-2026 Unicode, Inc.
|
||||||
|
|
||||||
|
NOTICE TO USER: Carefully read the following legal agreement. BY
|
||||||
|
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
|
||||||
|
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
|
||||||
|
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
|
||||||
|
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of data files and any associated documentation (the "Data Files") or
|
||||||
|
software and any associated documentation (the "Software") to deal in the
|
||||||
|
Data Files or Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, and/or sell
|
||||||
|
copies of the Data Files or Software, and to permit persons to whom the
|
||||||
|
Data Files or Software are furnished to do so, provided that either (a)
|
||||||
|
this copyright and permission notice appear with all copies of the Data
|
||||||
|
Files or Software, or (b) this copyright and permission notice appear in
|
||||||
|
associated Documentation.
|
||||||
|
|
||||||
|
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
||||||
|
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||||
|
THIRD PARTY RIGHTS.
|
||||||
|
|
||||||
|
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
|
||||||
|
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
|
||||||
|
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||||
|
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
|
||||||
|
FILES OR SOFTWARE.
|
||||||
|
|
||||||
|
Except as contained in this notice, the name of a copyright holder shall
|
||||||
|
not be used in advertising or otherwise to promote the sale, use or other
|
||||||
|
dealings in these Data Files or Software without prior written
|
||||||
|
authorization of the copyright holder.
|
||||||
33
Makefile
33
Makefile
@@ -5,12 +5,12 @@ CFLAGS ?= -O2 -g
|
|||||||
WARN_CFLAGS = -Wall -Wextra
|
WARN_CFLAGS = -Wall -Wextra
|
||||||
IBUS_CFLAGS = $(shell $(PKG_CONFIG) --cflags dbus-1 xkbcommon)
|
IBUS_CFLAGS = $(shell $(PKG_CONFIG) --cflags dbus-1 xkbcommon)
|
||||||
IBUS_LIBS = $(shell $(PKG_CONFIG) --libs 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_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
||||||
XIM_LIBS = $(shell $(PKG_CONFIG) --libs xcb-imdkit xcb-xkb xkbcommon-x11)
|
XIM_LIBS = $(shell $(PKG_CONFIG) --libs xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
||||||
TEXT_CFLAGS = $(shell $(PKG_CONFIG) --cflags pangocairo cairo fontconfig)
|
TEXT_CFLAGS = $(shell $(PKG_CONFIG) --cflags pangocairo cairo fontconfig)
|
||||||
TEXT_LIBS = $(shell $(PKG_CONFIG) --libs pangocairo cairo fontconfig)
|
TEXT_LIBS = $(shell $(PKG_CONFIG) --libs pangocairo cairo fontconfig)
|
||||||
POPUP_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-randr)
|
POPUP_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-aux xcb-randr)
|
||||||
POPUP_LIBS = $(shell $(PKG_CONFIG) --libs xcb-randr)
|
POPUP_LIBS = $(shell $(PKG_CONFIG) --libs xcb-aux xcb-randr)
|
||||||
PROJECT_CPPFLAGS =
|
PROJECT_CPPFLAGS =
|
||||||
PROJECT_LDLIBS = -lthread -lbio -lxcb $(IBUS_LIBS) $(TEXT_LIBS) \
|
PROJECT_LDLIBS = -lthread -lbio -lxcb $(IBUS_LIBS) $(TEXT_LIBS) \
|
||||||
$(XIM_LIBS) $(POPUP_LIBS)
|
$(XIM_LIBS) $(POPUP_LIBS)
|
||||||
@@ -19,11 +19,9 @@ DOCKER_IMAGE = strans-build
|
|||||||
DOCKER_RUN = docker run --rm --user "$$(id -u):$$(id -g)" \
|
DOCKER_RUN = docker run --rm --user "$$(id -u):$$(id -g)" \
|
||||||
-v "$(CURDIR):/src" $(DOCKER_IMAGE)
|
-v "$(CURDIR):/src" $(DOCKER_IMAGE)
|
||||||
|
|
||||||
SRCS = dict.c font.c ibus.c ipc.c ko.c main.c popup_layout.c \
|
SRCS = compose.c dict.c font.c ibus.c ipc.c ko.c main.c popup_layout.c \
|
||||||
srv.c str.c strans.c trie.c vi.c win.c
|
srv.c str.c strans.c trie.c vi.c win.c xim.c
|
||||||
XIMSRCS = xim/xim.c xim/keymap.c
|
OBJS = $(SRCS:.c=.o)
|
||||||
XIMOBJS = $(XIMSRCS:.c=.o)
|
|
||||||
OBJS = $(SRCS:.c=.o) $(XIMOBJS)
|
|
||||||
|
|
||||||
all: $(PROG) gtk
|
all: $(PROG) gtk
|
||||||
|
|
||||||
@@ -31,10 +29,10 @@ $(PROG): $(OBJS)
|
|||||||
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(PROJECT_LDLIBS) $(LDLIBS)
|
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(PROJECT_LDLIBS) $(LDLIBS)
|
||||||
|
|
||||||
$(OBJS): dat.h fn.h ipc.h
|
$(OBJS): dat.h fn.h ipc.h
|
||||||
ibus.o: PROJECT_CPPFLAGS += $(IBUS_CFLAGS)
|
compose.o ibus.o: PROJECT_CPPFLAGS += $(IBUS_CFLAGS)
|
||||||
font.o: PROJECT_CPPFLAGS += $(TEXT_CFLAGS)
|
font.o: PROJECT_CPPFLAGS += $(TEXT_CFLAGS)
|
||||||
win.o: PROJECT_CPPFLAGS += $(POPUP_CFLAGS)
|
win.o: PROJECT_CPPFLAGS += $(POPUP_CFLAGS)
|
||||||
$(XIMOBJS): PROJECT_CPPFLAGS += -I. $(XIM_CFLAGS)
|
xim.o: PROJECT_CPPFLAGS += $(XIM_CFLAGS)
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
$(CC) $(CPPFLAGS) $(PROJECT_CPPFLAGS) $(WARN_CFLAGS) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CPPFLAGS) $(PROJECT_CPPFLAGS) $(WARN_CFLAGS) $(CFLAGS) -c -o $@ $<
|
||||||
@@ -89,13 +87,14 @@ check-stress: $(PROG)
|
|||||||
test: check
|
test: check
|
||||||
|
|
||||||
verify-map:
|
verify-map:
|
||||||
|
test "$$(cut -f1 map/hira.map)" = "$$(cut -f1 map/kata.map)"
|
||||||
python3 map/mktelex.py | cmp - map/telex.map
|
python3 map/mktelex.py | cmp - map/telex.map
|
||||||
python3 -B map/mkemoji | cmp - map/emoji.dict
|
python3 map/mkemoji | cmp - map/emoji.dict
|
||||||
python3 -B map/mkhanja | cmp - map/hanja.dict
|
python3 map/mkhanja | cmp - map/hanja.dict
|
||||||
python3 -B map/verifymap.py map/*.map map/*.dict
|
python3 map/verifymap.py map/*.map map/*.dict
|
||||||
python3 -B tests/mkemoji_test.py
|
python3 tests/mkemoji_test.py
|
||||||
python3 -B tests/mkhanja_test.py
|
python3 tests/mkhanja_test.py
|
||||||
python3 -B tests/skk2ktrans_test.py
|
python3 tests/skk2ktrans_test.py
|
||||||
|
|
||||||
.PHONY: all check check-live check-stress test verify-map clean gtk 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 docker-image docker-build docker-check docker-check-live \
|
||||||
|
|||||||
134
README.md
134
README.md
@@ -1,8 +1,9 @@
|
|||||||
# strans
|
# strans
|
||||||
|
|
||||||
strans is a small, single-user input method for Korean, Japanese, English,
|
strans is a small, single-user input method for Korean, Japanese, English,
|
||||||
emoji, and symbols. It provides one engine for IBus, XIM, and GTK 3.
|
emoji, and symbols. It provides one engine for IBus (which GTK 4 and
|
||||||
Vietnamese Telex is also available as a compatibility mode.
|
Qt use), XIM, and GTK 3. Vietnamese Telex is also available as a
|
||||||
|
compatibility mode.
|
||||||
|
|
||||||
## Input modes
|
## Input modes
|
||||||
|
|
||||||
@@ -16,20 +17,56 @@ Vietnamese Telex is also available as a compatibility mode.
|
|||||||
| `Ctrl+E` | Emoji and symbol search |
|
| `Ctrl+E` | Emoji and symbol search |
|
||||||
| `Ctrl+H` | One-shot Hanja search |
|
| `Ctrl+H` | One-shot Hanja search |
|
||||||
|
|
||||||
Use `Up`/`Down` to move through candidates and `PageUp`/`PageDown` to move by
|
Switching mode shows the mode switched to — `A`, `한`, `あ`, `ア`, `ă` — in
|
||||||
nine without wrapping. An unmodified `1`-`9` selects that row on the current
|
the popup until the next key.
|
||||||
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.
|
A Korean keyboard's 한/영 key toggles Korean and English, and its 한자 key
|
||||||
Katakana mode does not perform Kanji conversion. Emoji and Hanja searches
|
is `Ctrl+H`; a Japanese keyboard's 半角/全角 toggles Japanese and English,
|
||||||
return to the previous mode after use; switching language during a search
|
ひらがな/カタカナ switches between the two kana modes, 変換 turns Japanese
|
||||||
commits the shown query. `Ctrl+H` takes the syllable being composed as its
|
on and then converts like `Space`, and 無変換 turns it off. Only a plain
|
||||||
query; the Hanja dictionary lists one modern Hangul syllable at a time.
|
`Ctrl` chord is a strans key: with `Shift`, `Alt`, or `Super` held it
|
||||||
|
commits what is pending and goes to the application, so `Ctrl+Shift+V`
|
||||||
|
still pastes.
|
||||||
|
|
||||||
|
Hiragana mode composes a complete reading, then shows its Kanji candidates
|
||||||
|
with none chosen: the exact readings of the dictionary, then the verbs and
|
||||||
|
adjectives SKK keys by stem and okurigana (かく offers 確 and 書く), and
|
||||||
|
under `Space` the reading in Katakana last, so a word the dictionary lacks
|
||||||
|
converts to Katakana with one `Space`. `Space` or `Tab` chooses the first
|
||||||
|
candidate and then steps on, wrapping (with `Shift`, back); `Up`/`Down` and
|
||||||
|
`PageUp`/`PageDown` move without wrapping. Once a candidate is chosen, an unmodified `1`-`9` commits that row of the
|
||||||
|
current page; before that the digits type. `Enter` commits the chosen candidate, or the
|
||||||
|
reading when none is chosen, as `0` always does; so does typing on, or any
|
||||||
|
key that ends the composition. `Backspace` deletes the last kana shown;
|
||||||
|
romaji that made no kana stays in the reading as typed until it is mended.
|
||||||
|
The dictionary keys a verb or adjective by its stem, so `kaku` offers the
|
||||||
|
readings of かく and then 書く and its kin; typing the okurigana with
|
||||||
|
`Shift`, as SKK does — `kaKu` — asks for that split first. Caps Lock is
|
||||||
|
not `Shift` and marks nothing.
|
||||||
|
`Backspace` and `Esc` on a chosen candidate go back to the reading, and
|
||||||
|
`Esc` on the reading cancels it. Katakana mode does not perform Kanji
|
||||||
|
conversion.
|
||||||
|
|
||||||
|
Korean composes one syllable at a time; `Enter`, `Tab`, `Esc`, and any key
|
||||||
|
that is not a jamo commit the syllable and go on to the application, so
|
||||||
|
`Esc` still leaves insert mode. Two lone consonants join into the compound
|
||||||
|
final they make (rt → ㄳ) and a vowel typed before its consonant is
|
||||||
|
reordered under it (kr → 가), as in libhangul.
|
||||||
|
|
||||||
|
Emoji and Hanja searches take over the keys until `Enter`, `Space`, or
|
||||||
|
`1`-`9` picks a result or `Esc` cancels; they return to the previous mode
|
||||||
|
after use, and switching language during a search commits the shown
|
||||||
|
query. `Tab`/`Shift-Tab` wrap through the results, and a search with
|
||||||
|
nothing typed yet shows ☺ or 漢 in the popup. The Emoji search matches the
|
||||||
|
typed keys, and what they spell in the current language, against a prefix
|
||||||
|
of every emoji's CLDR name and keywords in English, Korean, and Japanese,
|
||||||
|
and against ASCII symbol aliases such as `->` and `<=`. `Ctrl+H` takes the
|
||||||
|
syllable being composed as its query and composes on from it; `Esc` gives
|
||||||
|
the syllable back. The Hanja dictionary converts a word as well as a
|
||||||
|
syllable, so `Ctrl+H` and then 한자 gives 漢字, and 대한민국 gives 大韓民國;
|
||||||
|
a syllable already committed is the application's text, not the engine's,
|
||||||
|
and cannot be converted. Leaving the field or
|
||||||
|
clicking elsewhere commits what is pending.
|
||||||
|
|
||||||
## Preedit and candidates
|
## Preedit and candidates
|
||||||
|
|
||||||
@@ -40,10 +77,18 @@ query; the Hanja dictionary lists one modern Hangul syllable at a time.
|
|||||||
| XIM PreeditPosition | strans popup | strans popup |
|
| XIM PreeditPosition | strans popup | strans popup |
|
||||||
| XIM PreeditNothing | strans popup | strans popup |
|
| XIM PreeditNothing | strans popup | strans popup |
|
||||||
|
|
||||||
|
Each XIM style is offered with StatusNothing and with StatusNone.
|
||||||
|
|
||||||
|
Dead keys and Compose sequences are composed by strans itself for the XIM
|
||||||
|
and IBus frontends, from the table `XCOMPOSEFILE` or the locale names; the
|
||||||
|
GTK 3 module leaves them to GtkIMContextSimple. Their text follows
|
||||||
|
whatever was pending.
|
||||||
|
|
||||||
XIM text travels as `COMPOUND_TEXT`, which carries any UTF-8. A client that
|
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
|
sends `XNSpotLocation` gets the popup under that spot whatever its preedit
|
||||||
style; otherwise the popup sits under the focus window, or under the client
|
style, and above the line when there is no room below; otherwise the popup
|
||||||
window when no focus window is set.
|
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
|
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
|
X11-only, and GTK popup placement converts the GTK caret to X11 root
|
||||||
@@ -53,8 +98,7 @@ display environment supports it.
|
|||||||
|
|
||||||
## Build and test
|
## Build and test
|
||||||
|
|
||||||
Initialize the bundled test library, then use the pinned Docker environment
|
Initialize the bundled test library, then build in the Docker environment:
|
||||||
for a reproducible build:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
@@ -83,15 +127,16 @@ tests. `make docker-check` rebuilds and runs all three tiers in the
|
|||||||
container, and `make docker-valgrind` runs the unit suite under Valgrind
|
container, and `make docker-valgrind` runs the unit suite under Valgrind
|
||||||
there; run `make docker-image` first after changing `Dockerfile`.
|
there; run `make docker-image` first after changing `Dockerfile`.
|
||||||
|
|
||||||
Native Linux source builds require a C toolchain, Make, pkg-config, Python 3,
|
Native Linux source builds require a C toolchain, Make, and pkg-config, plus
|
||||||
and cmp/diff, plus development files for:
|
development files for:
|
||||||
|
|
||||||
- Plan 9 port (`9c`, `9l`, `libthread`, and `libbio`);
|
- Plan 9 port (`9c`, `9l`, `libthread`, and `libbio`);
|
||||||
- D-Bus development files;
|
- D-Bus development files;
|
||||||
- XCB, XCB RandR, XCB XKB, xcb-imdkit, xkbcommon, and xkbcommon-x11;
|
- XCB, XCB RandR, XCB XKB, xcb-util, xcb-imdkit, xkbcommon, and xkbcommon-x11;
|
||||||
- Pango, PangoCairo, Cairo, and Fontconfig;
|
- Pango, PangoCairo, Cairo, and Fontconfig;
|
||||||
- GTK 3, plus libibus and Xlib for the live clients;
|
- GTK 3, plus libibus and Xlib for the live clients;
|
||||||
- Xvfb for the X11 live tests;
|
- Xvfb for the X11 live tests;
|
||||||
|
- Python 3 and `cmp` for `make check` and the map generators;
|
||||||
- a working `C.UTF-8` locale and fonts covering Latin, CJK, and emoji.
|
- 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
|
The package names used by the canonical Arch Linux image are listed in
|
||||||
@@ -99,9 +144,8 @@ The package names used by the canonical Arch Linux image are listed in
|
|||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
The popup uses the normal system fonts found by Pango and Fontconfig; no font
|
The popup uses the normal system fonts found by Pango and Fontconfig. Run
|
||||||
file argument or system-wide strans installation is required. Run it directly
|
it directly from the build tree:
|
||||||
from the build tree:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./run.sh
|
./run.sh
|
||||||
@@ -113,19 +157,18 @@ from the build tree:
|
|||||||
build-tree daemon in the background, and returns to the shell. It requires
|
build-tree daemon in the background, and returns to the shell. It requires
|
||||||
`pkill` from procps. Startup and frontend failures remain visible on the
|
`pkill` from procps. Startup and frontend failures remain visible on the
|
||||||
caller's standard error. A service supervisor should instead run
|
caller's standard error. A service supervisor should instead run
|
||||||
`./strans map` directly in the foreground.
|
`./strans map` directly in the foreground, with the session's
|
||||||
|
`XDG_RUNTIME_DIR` and `DISPLAY`: the GTK module finds the daemon at
|
||||||
|
`$XDG_RUNTIME_DIR/strans.sock` (else `/tmp/strans.UID`), and IBus clients
|
||||||
|
through the address file libibus expects under `~/.config/ibus/bus/`,
|
||||||
|
which strans writes as `ibus-daemon` would, named after `WAYLAND_DISPLAY`
|
||||||
|
when there is one and `DISPLAY` otherwise.
|
||||||
|
|
||||||
The build does not copy or install the daemon or its data. `./strans DIR`
|
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
|
opens `hira.map`, `kata.map`, `telex.map`, `kanji.dict`, `emoji.dict`, and
|
||||||
`hanja.dict` from `DIR` at runtime; `run.sh` passes the tracked `map/`
|
`hanja.dict` from `DIR` at runtime; `run.sh` passes the tracked `map/`
|
||||||
directory. Only the GTK subdirectory has install and uninstall targets.
|
directory. Only the GTK subdirectory has install and uninstall targets.
|
||||||
|
|
||||||
Pango shapes the complete string and selects glyph fallback from the system
|
|
||||||
font set.
|
|
||||||
|
|
||||||
The popup renderer uses Pango/PangoCairo and Cairo. When `DISPLAY` is set,
|
|
||||||
`strans` starts its XIM worker in the same process.
|
|
||||||
|
|
||||||
Configure clients as needed:
|
Configure clients as needed:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -140,13 +183,12 @@ export GTK_IM_MODULE=strans
|
|||||||
GLFW_IM_MODULE=ibus kitty
|
GLFW_IM_MODULE=ibus kitty
|
||||||
```
|
```
|
||||||
|
|
||||||
Building `gtk/im-strans.so` requires GTK development files. Installing an
|
Building `gtk/im-strans.so` requires GTK development files; installing one
|
||||||
already-built, up-to-date module from this build tree does not: the install
|
built elsewhere, in the container for instance, does not. The install
|
||||||
target first uses an explicit `GTK_MODULE_DIR`, then GTK's pkg-config
|
target uses `GTK_MODULE_DIR` when it is set, else GTK's pkg-config
|
||||||
variables, and finally the module directory reported by the GTK runtime's
|
variables, else the directory the installed `gtk-query-immodules-3.0`
|
||||||
`gtk-query-immodules-3.0`. Packagers and cross builds should set
|
reports. Packagers and cross builds should set `GTK_MODULE_DIR`, and a
|
||||||
`GTK_MODULE_DIR` explicitly. A prebuilt module must still match the target
|
prebuilt module must still match the target architecture and library ABI.
|
||||||
architecture and library ABI.
|
|
||||||
|
|
||||||
`DESTDIR` staging is supported and deliberately skips the host module-cache
|
`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
|
update. A native install needs the GTK runtime query utility so it can refresh
|
||||||
@@ -157,21 +199,25 @@ doas make -C gtk uninstall
|
|||||||
```
|
```
|
||||||
|
|
||||||
strans provides its own IBus endpoint; `ibus-daemon` and fcitx are not
|
strans provides its own IBus endpoint; `ibus-daemon` and fcitx are not
|
||||||
required. Without `DISPLAY`, the daemon and IBus frontend still work, but XIM
|
required. IBus clients that process keys synchronously — GTK 4, or GTK 3
|
||||||
|
with `IBUS_ENABLE_SYNC_MODE=1` — get their commits and preedits through
|
||||||
|
`PostProcessKeyEvent`, in order with the key. Without `DISPLAY`, the daemon and IBus frontend still work, but XIM
|
||||||
is not started and the popup is disabled.
|
is not started and the popup is disabled.
|
||||||
|
|
||||||
## Dictionary data
|
## Dictionary data
|
||||||
|
|
||||||
After changing an input source, regenerate and verify the dictionaries:
|
After changing an input source, regenerate and verify the maps and
|
||||||
|
dictionaries:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
python3 map/mktelex.py >map/telex.map
|
||||||
map/mkemoji >map/emoji.dict
|
map/mkemoji >map/emoji.dict
|
||||||
map/mkhanja >map/hanja.dict
|
map/mkhanja >map/hanja.dict
|
||||||
make verify-map
|
make verify-map
|
||||||
```
|
```
|
||||||
|
|
||||||
See [`map/README`](map/README) for Hanja import and Japanese dictionary
|
See [`map/README`](map/README) for the emoji, Hanja, and Japanese data and
|
||||||
generation.
|
where it comes from.
|
||||||
|
|
||||||
## Benchmark
|
## Benchmark
|
||||||
|
|
||||||
|
|||||||
5
bench.sh
5
bench.sh
@@ -9,6 +9,11 @@ if test "$#" -ne 0; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if ! test -x ./bench/bench; then
|
||||||
|
echo "bench.sh: ./bench/bench is not executable; run make bench first" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
strans_pid=
|
strans_pid=
|
||||||
perf_pid=
|
perf_pid=
|
||||||
|
|
||||||
|
|||||||
54
compose.c
Normal file
54
compose.c
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
|
|
||||||
|
#include <locale.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
#include <xkbcommon/xkbcommon-compose.h>
|
||||||
|
|
||||||
|
static struct xkb_compose_state *cstate;
|
||||||
|
|
||||||
|
/* Dead keys and Compose: no frontend's client does them for us. */
|
||||||
|
void
|
||||||
|
composeinit(void)
|
||||||
|
{
|
||||||
|
struct xkb_context *ctx;
|
||||||
|
struct xkb_compose_table *table;
|
||||||
|
char *locale;
|
||||||
|
|
||||||
|
locale = setlocale(LC_CTYPE, "");
|
||||||
|
ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||||
|
if(ctx == nil)
|
||||||
|
return;
|
||||||
|
table = xkb_compose_table_new_from_locale(ctx,
|
||||||
|
locale != nil ? locale : "C", XKB_COMPOSE_COMPILE_NO_FLAGS);
|
||||||
|
if(table != nil)
|
||||||
|
cstate = xkb_compose_state_new(table, XKB_COMPOSE_STATE_NO_FLAGS);
|
||||||
|
xkb_compose_table_unref(table);
|
||||||
|
xkb_context_unref(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Feeds sym to the Compose table. Returns 1 while a sequence is
|
||||||
|
* unfinished: the key belongs to the sequence, not to the engine. A
|
||||||
|
* finished sequence leaves its text in buf, which the caller commits
|
||||||
|
* after whatever the engine had pending.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
composekey(u32int sym, char *buf, int n)
|
||||||
|
{
|
||||||
|
buf[0] = '\0';
|
||||||
|
if(cstate == nil ||
|
||||||
|
xkb_compose_state_feed(cstate, sym) != XKB_COMPOSE_FEED_ACCEPTED)
|
||||||
|
return 0;
|
||||||
|
switch(xkb_compose_state_get_status(cstate)){
|
||||||
|
case XKB_COMPOSE_COMPOSING:
|
||||||
|
case XKB_COMPOSE_CANCELLED:
|
||||||
|
return 1;
|
||||||
|
case XKB_COMPOSE_COMPOSED:
|
||||||
|
xkb_compose_state_get_utf8(cstate, buf, n);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
16
dat.h
16
dat.h
@@ -25,6 +25,7 @@ enum
|
|||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Maxclients = 64,
|
Maxclients = 64,
|
||||||
|
Ownerpoll = 200, /* ms between a frontend's owner checks while its preedit shows */
|
||||||
Maxkouho = 128,
|
Maxkouho = 128,
|
||||||
Maxdisp = 9,
|
Maxdisp = 9,
|
||||||
|
|
||||||
@@ -40,10 +41,10 @@ extern int popupscale;
|
|||||||
#define Fontsz (32*popupscale)
|
#define Fontsz (32*popupscale)
|
||||||
#define PopupPad (4*popupscale)
|
#define PopupPad (4*popupscale)
|
||||||
#define PopupSep popupscale
|
#define PopupSep popupscale
|
||||||
|
#define PopupBorder popupscale
|
||||||
#define PopupNumw Fontsz
|
#define PopupNumw Fontsz
|
||||||
#define PopupTextw (12*Fontsz)
|
#define PopupTextw (12*Fontsz)
|
||||||
#define PopupBasew (2*PopupPad + PopupNumw + PopupTextw)
|
#define PopupBasew (2*PopupPad + PopupNumw + PopupTextw)
|
||||||
#define Imgh (2*PopupPad + (Maxdisp + 2)*Fontsz + PopupSep)
|
|
||||||
|
|
||||||
typedef struct Str Str;
|
typedef struct Str Str;
|
||||||
struct Str
|
struct Str
|
||||||
@@ -73,7 +74,7 @@ struct Tnode
|
|||||||
int vlen;
|
int vlen;
|
||||||
int child;
|
int child;
|
||||||
int sibling;
|
int sibling;
|
||||||
char c;
|
Rune c;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Trie Trie;
|
typedef struct Trie Trie;
|
||||||
@@ -82,6 +83,9 @@ struct Trie
|
|||||||
Tnode *nodes;
|
Tnode *nodes;
|
||||||
int n;
|
int n;
|
||||||
int cap;
|
int cap;
|
||||||
|
/* the last key put and its path, so a sorted file walks it once */
|
||||||
|
Str last;
|
||||||
|
int path[Maxrunes];
|
||||||
};
|
};
|
||||||
|
|
||||||
enum
|
enum
|
||||||
@@ -172,10 +176,6 @@ struct Popup
|
|||||||
typedef struct Keyreq Keyreq;
|
typedef struct Keyreq Keyreq;
|
||||||
typedef struct Keyres Keyres;
|
typedef struct Keyres Keyres;
|
||||||
enum
|
enum
|
||||||
{
|
|
||||||
Cclientpreedit = 1<<0,
|
|
||||||
};
|
|
||||||
enum
|
|
||||||
{
|
{
|
||||||
Keypress,
|
Keypress,
|
||||||
Keyreset,
|
Keyreset,
|
||||||
@@ -185,7 +185,7 @@ enum
|
|||||||
};
|
};
|
||||||
struct Keyres
|
struct Keyres
|
||||||
{
|
{
|
||||||
int eaten; /* Keycap: caller is active and capability was applied. */
|
int eaten; /* Keycap: the caller is the owner and clientpre took. */
|
||||||
Str commit;
|
Str commit;
|
||||||
Str preedit;
|
Str preedit;
|
||||||
};
|
};
|
||||||
@@ -193,7 +193,7 @@ struct Keyres
|
|||||||
struct Keyreq
|
struct Keyreq
|
||||||
{
|
{
|
||||||
void *owner; /* stable until the context's release is acknowledged */
|
void *owner; /* stable until the context's release is acknowledged */
|
||||||
int cap;
|
int clientpre; /* the client draws the preedit; the popup does not */
|
||||||
int op;
|
int op;
|
||||||
u32int ks;
|
u32int ks;
|
||||||
u32int mod;
|
u32int mod;
|
||||||
|
|||||||
61
dict.c
61
dict.c
@@ -1,36 +1,67 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
/*
|
/* Appends the space-separated words of a node's entry to out[], up to max. */
|
||||||
* Fills out[] with up to max candidates for key: the space-separated words
|
static int
|
||||||
* of the entry, minus the key itself except in the emoji dictionary, where
|
words(Tnode *nd, Str *out, int n, int max)
|
||||||
* a query may name its own answer.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
dictlookup(Lang *l, Str *key, Str *out, int max)
|
|
||||||
{
|
{
|
||||||
char *p, *e, *sp;
|
char *p, *e, *sp;
|
||||||
Str tmp;
|
Str tmp;
|
||||||
int n, vlen;
|
|
||||||
|
|
||||||
if(l == nil || key->n == 0 ||
|
if(nd->val == nil)
|
||||||
trielookup(l->dict, key, &p, &vlen) != TrieExact)
|
return n;
|
||||||
return 0;
|
p = nd->val;
|
||||||
n = 0;
|
e = p + nd->vlen;
|
||||||
e = p + vlen;
|
|
||||||
while(n < max && p < e){
|
while(n < max && p < e){
|
||||||
while(p < e && *p == ' ')
|
while(p < e && *p == ' ')
|
||||||
p++;
|
p++;
|
||||||
sp = p;
|
sp = p;
|
||||||
while(p < e && *p != ' ')
|
while(p < e && *p != ' ')
|
||||||
p++;
|
p++;
|
||||||
if(sinit(&tmp, sp, p - sp) && tmp.n > 0 &&
|
if(sinit(&tmp, sp, p - sp) && tmp.n > 0)
|
||||||
(l->lang == LangEMOJI || scmp(&tmp, key) != 0))
|
|
||||||
out[n++] = tmp;
|
out[n++] = tmp;
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The entries at and below a node, the shortest keys first. */
|
||||||
|
static int
|
||||||
|
below(Trie *t, int ni, Str *out, int n, int max)
|
||||||
|
{
|
||||||
|
Tnode *nd;
|
||||||
|
int ci;
|
||||||
|
|
||||||
|
nd = &t->nodes[ni];
|
||||||
|
n = words(nd, out, n, max);
|
||||||
|
for(ci = nd->child; ci >= 0 && n < max; ci = t->nodes[ci].sibling)
|
||||||
|
n = below(t, ci, out, n, max);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fills out[] with up to max candidates for key: the words of its entry. */
|
||||||
|
int
|
||||||
|
dictlookup(Trie *t, Str *key, Str *out, int max)
|
||||||
|
{
|
||||||
|
int ni;
|
||||||
|
|
||||||
|
ni = key->n == 0 ? -1 : trienode(t, key);
|
||||||
|
if(ni < 0)
|
||||||
|
return 0;
|
||||||
|
return words(&t->nodes[ni], out, 0, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* As dictlookup, for every entry key is a prefix of, key's own first. */
|
||||||
|
int
|
||||||
|
dictprefix(Trie *t, Str *key, Str *out, int max)
|
||||||
|
{
|
||||||
|
int ni;
|
||||||
|
|
||||||
|
ni = key->n == 0 ? -1 : trienode(t, key);
|
||||||
|
if(ni < 0)
|
||||||
|
return 0;
|
||||||
|
return below(t, ni, out, 0, max);
|
||||||
|
}
|
||||||
|
|
||||||
static Trie*
|
static Trie*
|
||||||
langopen(char *dir, char *name, char *ext)
|
langopen(char *dir, char *name, char *ext)
|
||||||
{
|
{
|
||||||
|
|||||||
11
fn.h
11
fn.h
@@ -15,12 +15,14 @@ Trie* trienew(void);
|
|||||||
void trieput(Trie*, char*, int, char*, int);
|
void trieput(Trie*, char*, int, char*, int);
|
||||||
Trie* trieopen(char*);
|
Trie* trieopen(char*);
|
||||||
void trieclose(Trie*);
|
void trieclose(Trie*);
|
||||||
|
int trienode(Trie*, Str*);
|
||||||
int trielookup(Trie*, Str*, char**, int*);
|
int trielookup(Trie*, Str*, char**, int*);
|
||||||
|
|
||||||
Lang* getlang(int);
|
Lang* getlang(int);
|
||||||
void langinit(char*);
|
void langinit(char*);
|
||||||
|
|
||||||
int dictlookup(Lang*, Str*, Str*, int);
|
int dictlookup(Trie*, Str*, Str*, int);
|
||||||
|
int dictprefix(Trie*, Str*, Str*, int);
|
||||||
|
|
||||||
void drawthread(void*);
|
void drawthread(void*);
|
||||||
void popuparea(Area*, int, Area*, int, int, Area*);
|
void popuparea(Area*, int, Area*, int, int, Area*);
|
||||||
@@ -30,7 +32,7 @@ void popuplayout(Drawcmd*, int, int, Popup*);
|
|||||||
void popupdraw(u32int*, Drawcmd*, Popup*);
|
void popupdraw(u32int*, Drawcmd*, Popup*);
|
||||||
void imthread(void*);
|
void imthread(void*);
|
||||||
void impre(Im*, Str*);
|
void impre(Im*, Str*);
|
||||||
void transstr(Lang*, Str*, Str*);
|
void transstr(Lang*, Str*, Str*, Str*);
|
||||||
Emit transmap(Im*, Rune);
|
Emit transmap(Im*, Rune);
|
||||||
Emit transko(Im*, Rune);
|
Emit transko(Im*, Rune);
|
||||||
Emit transvi(Im*, Rune);
|
Emit transvi(Im*, Rune);
|
||||||
@@ -43,10 +45,13 @@ void ibusthread(void*);
|
|||||||
void ximthread(void*);
|
void ximthread(void*);
|
||||||
int keymeaningful(u32int);
|
int keymeaningful(u32int);
|
||||||
|
|
||||||
|
void composeinit(void);
|
||||||
|
int composekey(u32int, char*, int);
|
||||||
|
|
||||||
void* emalloc(ulong);
|
void* emalloc(ulong);
|
||||||
void* erealloc(void*, ulong);
|
void* erealloc(void*, ulong);
|
||||||
|
|
||||||
int textinit(void);
|
void textinit(void);
|
||||||
void textclose(void);
|
void textclose(void);
|
||||||
int textwidth(Str*);
|
int textwidth(Str*);
|
||||||
void textdraw(u32int*, int, int, int, int, int, u32int, Str*);
|
void textdraw(u32int*, int, int, int, int, int, u32int, Str*);
|
||||||
|
|||||||
40
font.c
40
font.c
@@ -7,13 +7,6 @@ static PangoFontMap *fontmap;
|
|||||||
static PangoContext *context;
|
static PangoContext *context;
|
||||||
static PangoLayout *layout;
|
static PangoLayout *layout;
|
||||||
|
|
||||||
static void
|
|
||||||
resetlayout(void)
|
|
||||||
{
|
|
||||||
pango_layout_set_width(layout, -1);
|
|
||||||
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
textclose(void)
|
textclose(void)
|
||||||
{
|
{
|
||||||
@@ -50,28 +43,16 @@ setfont(void)
|
|||||||
pango_font_description_free(font);
|
pango_font_description_free(font);
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
void
|
||||||
textinit(void)
|
textinit(void)
|
||||||
{
|
{
|
||||||
textclose();
|
|
||||||
FcInit();
|
FcInit();
|
||||||
fontmap = pango_cairo_font_map_new();
|
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);
|
pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(fontmap), 96);
|
||||||
context = pango_font_map_create_context(fontmap);
|
context = pango_font_map_create_context(fontmap);
|
||||||
if(context != nil)
|
layout = pango_layout_new(context);
|
||||||
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);
|
pango_layout_set_single_paragraph_mode(layout, TRUE);
|
||||||
setfont();
|
setfont();
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -80,8 +61,6 @@ settext(Str *s)
|
|||||||
char utf[Maxutf];
|
char utf[Maxutf];
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
if(layout == nil || s == nil || s->n <= 0)
|
|
||||||
return 0;
|
|
||||||
n = stoutf(s, utf, sizeof utf);
|
n = stoutf(s, utf, sizeof utf);
|
||||||
pango_layout_set_text(layout, utf, n);
|
pango_layout_set_text(layout, utf, n);
|
||||||
return n > 0;
|
return n > 0;
|
||||||
@@ -109,9 +88,8 @@ textwidth(Str *s)
|
|||||||
{
|
{
|
||||||
PangoRectangle r;
|
PangoRectangle r;
|
||||||
|
|
||||||
if(layout == nil)
|
pango_layout_set_width(layout, -1);
|
||||||
return 0;
|
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
|
||||||
resetlayout();
|
|
||||||
if(!settext(s))
|
if(!settext(s))
|
||||||
return 0;
|
return 0;
|
||||||
textextents(&r);
|
textextents(&r);
|
||||||
@@ -130,20 +108,16 @@ textdraw(u32int *buf, int w, int h, int x, int y, int fit, u32int color,
|
|||||||
PangoRectangle r;
|
PangoRectangle r;
|
||||||
cairo_surface_t *surface;
|
cairo_surface_t *surface;
|
||||||
cairo_t *cr;
|
cairo_t *cr;
|
||||||
int b, g, red, stride;
|
int b, g, red;
|
||||||
|
|
||||||
if(layout == nil || fit <= 0)
|
if(fit <= 0)
|
||||||
return;
|
return;
|
||||||
resetlayout();
|
|
||||||
pango_layout_set_width(layout, fit * PANGO_SCALE);
|
pango_layout_set_width(layout, fit * PANGO_SCALE);
|
||||||
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
|
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
|
||||||
if(w <= 0 || h <= 0 || !settext(s))
|
if(w <= 0 || h <= 0 || !settext(s))
|
||||||
return;
|
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,
|
surface = cairo_image_surface_create_for_data((uchar*)buf,
|
||||||
CAIRO_FORMAT_RGB24, w, h, stride);
|
CAIRO_FORMAT_RGB24, w, h, w * sizeof buf[0]);
|
||||||
if(cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS){
|
if(cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS){
|
||||||
cairo_surface_destroy(surface);
|
cairo_surface_destroy(surface);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ GTK_QUERY_IMMODULES ?= gtk-query-immodules-3.0
|
|||||||
GTK_LIBDIR ?= $(shell $(PKG_CONFIG) --variable=libdir gtk+-3.0 2>/dev/null)
|
GTK_LIBDIR ?= $(shell $(PKG_CONFIG) --variable=libdir gtk+-3.0 2>/dev/null)
|
||||||
GTK_BINARY_VERSION ?= $(shell $(PKG_CONFIG) \
|
GTK_BINARY_VERSION ?= $(shell $(PKG_CONFIG) \
|
||||||
--variable=gtk_binary_version gtk+-3.0 2>/dev/null)
|
--variable=gtk_binary_version gtk+-3.0 2>/dev/null)
|
||||||
|
# Without GTK development files, ask the runtime where its modules live.
|
||||||
GTK_RUNTIME_MODULE_DIR = $(shell GTK_PATH= GTK_IM_MODULE_FILE= \
|
GTK_RUNTIME_MODULE_DIR = $(shell GTK_PATH= GTK_IM_MODULE_FILE= \
|
||||||
$(GTK_QUERY_IMMODULES) 2>/dev/null | \
|
$(GTK_QUERY_IMMODULES) 2>/dev/null | \
|
||||||
awk -F '"' '/^"\// { sub("/[^/]*$$", "", $$2); print $$2; exit }')
|
awk -F '"' '/^"\// { sub("/[^/]*$$", "", $$2); print $$2; exit }')
|
||||||
|
|||||||
10
gtk/main.c
10
gtk/main.c
@@ -252,6 +252,7 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
|||||||
char pre[Ipcfieldmax+1];
|
char pre[Ipcfieldmax+1];
|
||||||
uint32_t key, mod;
|
uint32_t key, mod;
|
||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
|
gboolean r;
|
||||||
|
|
||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
if(ev->type != GDK_KEY_PRESS){
|
if(ev->type != GDK_KEY_PRESS){
|
||||||
@@ -263,8 +264,13 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
|||||||
return simplefilter(ctx, ev, 0);
|
return simplefilter(ctx, ev, 0);
|
||||||
key = ipckeysym(ev->keyval, gdk_keyval_to_unicode(ev->keyval));
|
key = ipckeysym(ev->keyval, gdk_keyval_to_unicode(ev->keyval));
|
||||||
mod = ipcmod(ev->state);
|
mod = ipcmod(ev->state);
|
||||||
if(im->private || key == 0)
|
if(im->private || key == 0){
|
||||||
return simplefilter(ctx, ev, 0);
|
r = simplefilter(ctx, ev, 0);
|
||||||
|
/* A dead key starts a compose: pending text was typed first. */
|
||||||
|
if(im->simpleactive)
|
||||||
|
sendreset(im);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
if(srvconnect(im) < 0)
|
if(srvconnect(im) < 0)
|
||||||
return simplefilter(ctx, ev, 0);
|
return simplefilter(ctx, ev, 0);
|
||||||
/* A retained GDK window may have moved since the last cursor report. */
|
/* A retained GDK window may have moved since the last cursor report. */
|
||||||
|
|||||||
430
ibus.c
430
ibus.c
@@ -2,10 +2,7 @@
|
|||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <dbus/dbus.h>
|
#include <dbus/dbus.h>
|
||||||
@@ -13,12 +10,10 @@
|
|||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Maxconns = Maxclients,
|
Maxwatches = 2*Maxclients + 1,
|
||||||
Maxwatches = 2*Maxconns + 1,
|
|
||||||
/* Toolkits keep one context per widget that ever took focus. */
|
/* Toolkits keep one context per widget that ever took focus. */
|
||||||
Maxcontexts = 1024,
|
Maxcontexts = 1024,
|
||||||
Relmask = 1<<30,
|
Relmask = 1<<30,
|
||||||
Ownerpoll = 200, /* ms between owner checks while a preedit shows */
|
|
||||||
/* IBus wire constants; the daemon does not link libibus. */
|
/* IBus wire constants; the daemon does not link libibus. */
|
||||||
Ibuscappreedit = 1<<0,
|
Ibuscappreedit = 1<<0,
|
||||||
Ibuspurposepassword = 8,
|
Ibuspurposepassword = 8,
|
||||||
@@ -26,6 +21,22 @@ enum
|
|||||||
Ibusattrunderline = 1,
|
Ibusattrunderline = 1,
|
||||||
Ibusunderlinesingle = 1,
|
Ibusunderlinesingle = 1,
|
||||||
Ibuspreeditclear = 0,
|
Ibuspreeditclear = 0,
|
||||||
|
Maxpost = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A client in IBus's synchronous mode (GTK 4) cannot take signals while
|
||||||
|
* it waits for its ProcessKeyEvent reply: it asks afterwards, through the
|
||||||
|
* PostProcessKeyEvent property, for what the key produced, as typed
|
||||||
|
* (yv) pairs — 'c' a commit, 'u' or 'm' a preedit and then its
|
||||||
|
* "cursor,visible[,mode]".
|
||||||
|
*/
|
||||||
|
typedef struct Post Post;
|
||||||
|
struct Post
|
||||||
|
{
|
||||||
|
char type;
|
||||||
|
char text[Maxutf];
|
||||||
|
char pos[32];
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Ictx Ictx;
|
typedef struct Ictx Ictx;
|
||||||
@@ -37,12 +48,16 @@ struct Ictx
|
|||||||
u32int cap;
|
u32int cap;
|
||||||
u32int purpose;
|
u32int purpose;
|
||||||
int clientcommitpreedit;
|
int clientcommitpreedit;
|
||||||
|
int postprocess;
|
||||||
|
int keying; /* inside ProcessKeyEvent: hold signals for the post */
|
||||||
|
int npost;
|
||||||
|
Post post[Maxpost];
|
||||||
Caret caret;
|
Caret caret;
|
||||||
};
|
};
|
||||||
|
|
||||||
static DBusWatch *watches[Maxwatches];
|
static DBusWatch *watches[Maxwatches];
|
||||||
static int nwatches;
|
static int nwatches;
|
||||||
static DBusConnection *conns[Maxconns];
|
static DBusConnection *conns[Maxclients];
|
||||||
static int nconns;
|
static int nconns;
|
||||||
static DBusServer *srv;
|
static DBusServer *srv;
|
||||||
static Ictx contexts[Maxcontexts];
|
static Ictx contexts[Maxcontexts];
|
||||||
@@ -70,10 +85,8 @@ unlinkaddr(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
addrnote(void *v, char *note)
|
addrnote(void*, char*)
|
||||||
{
|
{
|
||||||
USED(v);
|
|
||||||
USED(note);
|
|
||||||
unlinkaddr();
|
unlinkaddr();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -100,52 +113,63 @@ machineidfiles(char *buf, int sz, char **path, int npath)
|
|||||||
buf[0] = '\0';
|
buf[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* libibus reads the D-Bus file first and calls it "machine-id" if neither is there. */
|
||||||
static void
|
static void
|
||||||
machineid(char *buf, int sz)
|
machineid(char *buf, int sz)
|
||||||
{
|
{
|
||||||
char *path[] = {
|
char *path[] = {
|
||||||
"/etc/machine-id",
|
|
||||||
"/var/lib/dbus/machine-id",
|
"/var/lib/dbus/machine-id",
|
||||||
|
"/etc/machine-id",
|
||||||
};
|
};
|
||||||
|
|
||||||
machineidfiles(buf, sz, path, nelem(path));
|
machineidfiles(buf, sz, path, nelem(path));
|
||||||
|
if(buf[0] == '\0')
|
||||||
|
snprintf(buf, sz, "machine-id");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
copyfield(char *dst, int sz, char *src, int n)
|
||||||
|
{
|
||||||
|
if(n >= sz)
|
||||||
|
n = sz - 1;
|
||||||
|
memcpy(dst, src, n);
|
||||||
|
dst[n] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The display libibus names the file after: the whole WAYLAND_DISPLAY,
|
||||||
|
* else DISPLAY's host and number, else unix and 0.
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
xdisplay(char *host, int hsz, char *num, int nsz)
|
xdisplay(char *host, int hsz, char *num, int nsz)
|
||||||
{
|
{
|
||||||
char *d, *colon, *dot, *p;
|
char *d, *colon, *dot, *p;
|
||||||
int n;
|
|
||||||
|
|
||||||
strncpy(host, "unix", hsz);
|
copyfield(host, hsz, "unix", 4);
|
||||||
host[hsz-1] = '\0';
|
copyfield(num, nsz, "0", 1);
|
||||||
strncpy(num, "0", nsz);
|
d = getenv("WAYLAND_DISPLAY");
|
||||||
num[nsz-1] = '\0';
|
if(d != nil && d[0] != '\0'){
|
||||||
|
copyfield(num, nsz, d, strlen(d));
|
||||||
|
return;
|
||||||
|
}
|
||||||
d = getenv("DISPLAY");
|
d = getenv("DISPLAY");
|
||||||
if(d == nil || d[0] == '\0')
|
if(d == nil || d[0] == '\0')
|
||||||
return;
|
return;
|
||||||
colon = strchr(d, ':');
|
colon = strchr(d, ':');
|
||||||
if(colon == nil)
|
if(colon == nil)
|
||||||
return;
|
return;
|
||||||
if(colon > d){
|
if(colon > d)
|
||||||
n = colon - d;
|
copyfield(host, hsz, d, colon - d);
|
||||||
if(n >= hsz) n = hsz - 1;
|
|
||||||
memcpy(host, d, n);
|
|
||||||
host[n] = '\0';
|
|
||||||
}
|
|
||||||
p = colon + 1;
|
p = colon + 1;
|
||||||
dot = strchr(p, '.');
|
dot = strchr(p, '.');
|
||||||
n = dot ? dot - p : (int)strlen(p);
|
copyfield(num, nsz, p, dot ? dot - p : (int)strlen(p));
|
||||||
if(n >= nsz) n = nsz - 1;
|
|
||||||
memcpy(num, p, n);
|
|
||||||
num[n] = '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The IBus address file lives where libibus looks: config/ibus/bus/. */
|
/* The IBus address file lives where libibus looks: config/ibus/bus/. */
|
||||||
static int
|
static int
|
||||||
buildaddrpath(char *buf, int sz)
|
buildaddrpath(char *buf, int sz)
|
||||||
{
|
{
|
||||||
char mid[64], host[64], num[8], dir[512];
|
char mid[64], host[64], num[64], dir[512];
|
||||||
char *cfg, *home, *explicit, *p;
|
char *cfg, *home, *explicit, *p;
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
@@ -155,8 +179,6 @@ buildaddrpath(char *buf, int sz)
|
|||||||
return explicit[0] == '\0' || n < 0 || n >= sz ? -1 : 0;
|
return explicit[0] == '\0' || n < 0 || n >= sz ? -1 : 0;
|
||||||
}
|
}
|
||||||
machineid(mid, sizeof(mid));
|
machineid(mid, sizeof(mid));
|
||||||
if(mid[0] == '\0')
|
|
||||||
return -1;
|
|
||||||
xdisplay(host, sizeof(host), num, sizeof(num));
|
xdisplay(host, sizeof(host), num, sizeof(num));
|
||||||
cfg = getenv("XDG_CONFIG_HOME");
|
cfg = getenv("XDG_CONFIG_HOME");
|
||||||
if(cfg != nil && cfg[0] != '\0')
|
if(cfg != nil && cfg[0] != '\0')
|
||||||
@@ -211,11 +233,10 @@ writeaddr(char *path, char *addr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static dbus_bool_t
|
static dbus_bool_t
|
||||||
addwatch(DBusWatch *w, void *_)
|
addwatch(DBusWatch *w, void*)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
USED(_);
|
|
||||||
for(i = 0; i < nwatches; i++)
|
for(i = 0; i < nwatches; i++)
|
||||||
if(watches[i] == nil){
|
if(watches[i] == nil){
|
||||||
watches[i] = w;
|
watches[i] = w;
|
||||||
@@ -228,11 +249,10 @@ addwatch(DBusWatch *w, void *_)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
removewatch(DBusWatch *w, void *_)
|
removewatch(DBusWatch *w, void*)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
USED(_);
|
|
||||||
for(i = 0; i < nwatches; i++)
|
for(i = 0; i < nwatches; i++)
|
||||||
if(watches[i] == w){
|
if(watches[i] == w){
|
||||||
watches[i] = nil;
|
watches[i] = nil;
|
||||||
@@ -240,13 +260,6 @@ removewatch(DBusWatch *w, void *_)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
togglewatch(DBusWatch *w, void *_)
|
|
||||||
{
|
|
||||||
USED(w);
|
|
||||||
USED(_);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Ictx*
|
static Ictx*
|
||||||
findcontext(DBusConnection *conn, const char *path)
|
findcontext(DBusConnection *conn, const char *path)
|
||||||
{
|
{
|
||||||
@@ -287,7 +300,7 @@ sendrequest(Ictx *ctx, int op, u32int ks, u32int mod, Keyres *res)
|
|||||||
|
|
||||||
memset(&kr, 0, sizeof kr);
|
memset(&kr, 0, sizeof kr);
|
||||||
kr.owner = ctx;
|
kr.owner = ctx;
|
||||||
kr.cap = clientpreedit(ctx) ? Cclientpreedit : 0;
|
kr.clientpre = clientpreedit(ctx);
|
||||||
kr.op = op;
|
kr.op = op;
|
||||||
kr.ks = ks;
|
kr.ks = ks;
|
||||||
kr.mod = mod;
|
kr.mod = mod;
|
||||||
@@ -384,16 +397,34 @@ newsignal(const char *path, const char *name)
|
|||||||
return sig;
|
return sig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
postpush(Ictx *ctx, char type, const char *text, const char *pos)
|
||||||
|
{
|
||||||
|
Post *p;
|
||||||
|
|
||||||
|
if(!ctx->postprocess || !ctx->keying)
|
||||||
|
return 0;
|
||||||
|
if(ctx->npost < Maxpost){
|
||||||
|
p = &ctx->post[ctx->npost++];
|
||||||
|
p->type = type;
|
||||||
|
strcpy(p->text, text);
|
||||||
|
strcpy(p->pos, pos);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
emitcommit(DBusConnection *c, const char *path, const char *text)
|
emitcommit(Ictx *ctx, const char *text)
|
||||||
{
|
{
|
||||||
DBusMessage *sig;
|
DBusMessage *sig;
|
||||||
DBusMessageIter it;
|
DBusMessageIter it;
|
||||||
|
|
||||||
sig = newsignal(path, "CommitText");
|
if(postpush(ctx, 'c', text, ""))
|
||||||
|
return;
|
||||||
|
sig = newsignal(ctx->path, "CommitText");
|
||||||
dbus_message_iter_init_append(sig, &it);
|
dbus_message_iter_init_append(sig, &it);
|
||||||
appendibustext(&it, text, 0);
|
appendibustext(&it, text, 0);
|
||||||
dbus_connection_send(c, sig, nil);
|
dbus_connection_send(ctx->conn, sig, nil);
|
||||||
dbus_message_unref(sig);
|
dbus_message_unref(sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,28 +436,33 @@ emitpreedit(Ictx *ctx, const char *text)
|
|||||||
DBusMessageIter it;
|
DBusMessageIter it;
|
||||||
dbus_uint32_t cursor, mode;
|
dbus_uint32_t cursor, mode;
|
||||||
dbus_bool_t visible;
|
dbus_bool_t visible;
|
||||||
|
char pos[32];
|
||||||
|
|
||||||
if(text[0] == '\0' && preowner != ctx)
|
if(text[0] == '\0' && preowner != ctx)
|
||||||
return;
|
return;
|
||||||
sig = newsignal(ctx->path, ctx->clientcommitpreedit ?
|
|
||||||
"UpdatePreeditTextWithMode" : "UpdatePreeditText");
|
|
||||||
dbus_message_iter_init_append(sig, &it);
|
|
||||||
appendibustext(&it, text, 1);
|
|
||||||
cursor = utflen((char*)text);
|
cursor = utflen((char*)text);
|
||||||
visible = text[0] != '\0' ? TRUE : FALSE;
|
visible = text[0] != '\0' ? TRUE : FALSE;
|
||||||
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &cursor);
|
mode = Ibuspreeditclear;
|
||||||
dbus_message_iter_append_basic(&it, DBUS_TYPE_BOOLEAN, &visible);
|
if(ctx->clientcommitpreedit)
|
||||||
if(ctx->clientcommitpreedit){
|
snprintf(pos, sizeof pos, "%u,%u,%u", cursor, visible, mode);
|
||||||
mode = Ibuspreeditclear;
|
else
|
||||||
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &mode);
|
snprintf(pos, sizeof pos, "%u,%u", cursor, visible);
|
||||||
|
if(!postpush(ctx, ctx->clientcommitpreedit ? 'm' : 'u', text, pos)){
|
||||||
|
sig = newsignal(ctx->path, ctx->clientcommitpreedit ?
|
||||||
|
"UpdatePreeditTextWithMode" : "UpdatePreeditText");
|
||||||
|
dbus_message_iter_init_append(sig, &it);
|
||||||
|
appendibustext(&it, text, 1);
|
||||||
|
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &cursor);
|
||||||
|
dbus_message_iter_append_basic(&it, DBUS_TYPE_BOOLEAN, &visible);
|
||||||
|
if(ctx->clientcommitpreedit)
|
||||||
|
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &mode);
|
||||||
|
dbus_connection_send(ctx->conn, sig, nil);
|
||||||
|
dbus_message_unref(sig);
|
||||||
}
|
}
|
||||||
if(dbus_connection_send(ctx->conn, sig, nil)){
|
if(text[0] != '\0')
|
||||||
if(text[0] != '\0')
|
preowner = ctx;
|
||||||
preowner = ctx;
|
else if(preowner == ctx)
|
||||||
else if(preowner == ctx)
|
preowner = nil;
|
||||||
preowner = nil;
|
|
||||||
}
|
|
||||||
dbus_message_unref(sig);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Another frontend may have taken the engine; then our preedit is stale. */
|
/* Another frontend may have taken the engine; then our preedit is stale. */
|
||||||
@@ -456,7 +492,7 @@ flushcontext(Ictx *ctx, int op)
|
|||||||
emitpreedit(ctx, "");
|
emitpreedit(ctx, "");
|
||||||
stoutf(&res.commit, commit, sizeof commit);
|
stoutf(&res.commit, commit, sizeof commit);
|
||||||
if(commit[0] != '\0')
|
if(commit[0] != '\0')
|
||||||
emitcommit(ctx->conn, ctx->path, commit);
|
emitcommit(ctx, commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Nobody is left to hand text to: the context or its connection is gone. */
|
/* Nobody is left to hand text to: the context or its connection is gone. */
|
||||||
@@ -490,13 +526,26 @@ dropconncontexts(DBusConnection *conn)
|
|||||||
dropcontext(&contexts[i]);
|
dropcontext(&contexts[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A key is proof of focus: some clients never send FocusIn, and the
|
||||||
|
* focus events of two applications can cross.
|
||||||
|
*/
|
||||||
static int
|
static int
|
||||||
processkey(Ictx *ctx, u32int sym, u32int state, Keyres *res)
|
processkey(Ictx *ctx, u32int sym, u32int state, char *text, int n, Keyres *res)
|
||||||
{
|
{
|
||||||
if(state & Relmask || !ctx->focused || hidden(ctx))
|
text[0] = '\0';
|
||||||
|
if(state & Relmask || hidden(ctx))
|
||||||
return 0;
|
return 0;
|
||||||
sendrequest(ctx, Keypress, ipckeysym(sym, xkb_keysym_to_utf32(sym)),
|
ctx->focused = 1;
|
||||||
ipcmod(state), res);
|
/* An IBus client drops a dead key we do not take: compose it here. */
|
||||||
|
if(composekey(sym, text, n))
|
||||||
|
return -1;
|
||||||
|
if(text[0] != '\0')
|
||||||
|
/* Composed text follows whatever was pending. */
|
||||||
|
sendrequest(ctx, Keyreset, 0, 0, res);
|
||||||
|
else
|
||||||
|
sendrequest(ctx, Keypress, ipckeysym(sym, xkb_keysym_to_utf32(sym)),
|
||||||
|
ipcmod(state), res);
|
||||||
if(preowner != nil && preowner != ctx)
|
if(preowner != nil && preowner != ctx)
|
||||||
checkpreowner();
|
checkpreowner();
|
||||||
return 1;
|
return 1;
|
||||||
@@ -600,29 +649,66 @@ handlekey(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|||||||
{
|
{
|
||||||
dbus_uint32_t sym, code, state;
|
dbus_uint32_t sym, code, state;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
char commit[Maxutf], preedit[Maxutf];
|
char commit[Maxutf], preedit[Maxutf], text[Maxutf];
|
||||||
int restart;
|
int restart, rv;
|
||||||
|
|
||||||
if(!dbus_message_get_args(m, nil,
|
dbus_message_get_args(m, nil, DBUS_TYPE_UINT32, &sym,
|
||||||
DBUS_TYPE_UINT32, &sym,
|
DBUS_TYPE_UINT32, &code, DBUS_TYPE_UINT32, &state,
|
||||||
DBUS_TYPE_UINT32, &code,
|
DBUS_TYPE_INVALID);
|
||||||
DBUS_TYPE_UINT32, &state,
|
rv = processkey(ctx, sym, state, text, sizeof text, &res);
|
||||||
DBUS_TYPE_INVALID))
|
if(rv <= 0) /* below zero: a sequence in the making */
|
||||||
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
return replybool(c, m, rv < 0);
|
||||||
"ProcessKeyEvent expects three unsigned integers");
|
|
||||||
if(!processkey(ctx, sym, state, &res))
|
|
||||||
return replybool(c, m, 0);
|
|
||||||
stoutf(&res.commit, commit, sizeof commit);
|
stoutf(&res.commit, commit, sizeof commit);
|
||||||
stoutf(&res.preedit, preedit, sizeof preedit);
|
stoutf(&res.preedit, preedit, sizeof preedit);
|
||||||
/* A commit ends the preedit around it, so clients place it right. */
|
/* A commit ends the preedit around it, so clients place it right. */
|
||||||
restart = clientpreedit(ctx) && commit[0] != '\0' && preowner == ctx;
|
restart = clientpreedit(ctx) && commit[0] != '\0' && preowner == ctx;
|
||||||
|
ctx->keying = 1;
|
||||||
if(restart)
|
if(restart)
|
||||||
emitpreedit(ctx, "");
|
emitpreedit(ctx, "");
|
||||||
if(commit[0] != '\0')
|
if(commit[0] != '\0')
|
||||||
emitcommit(c, dbus_message_get_path(m), commit);
|
emitcommit(ctx, commit);
|
||||||
|
if(text[0] != '\0')
|
||||||
|
emitcommit(ctx, text);
|
||||||
if(clientpreedit(ctx) && (!restart || preedit[0] != '\0'))
|
if(clientpreedit(ctx) && (!restart || preedit[0] != '\0'))
|
||||||
emitpreedit(ctx, preedit);
|
emitpreedit(ctx, preedit);
|
||||||
return replybool(c, m, res.eaten);
|
ctx->keying = 0;
|
||||||
|
return replybool(c, m, res.eaten || text[0] != '\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Properties.Get PostProcessKeyEvent: what the last key produced. */
|
||||||
|
static DBusHandlerResult
|
||||||
|
handlepost(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
||||||
|
{
|
||||||
|
DBusMessage *r;
|
||||||
|
DBusMessageIter it, v, a, st;
|
||||||
|
Post *p;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
r = dbus_message_new_method_return(m);
|
||||||
|
if(r == nil)
|
||||||
|
die("ibus: out of memory");
|
||||||
|
dbus_message_iter_init_append(r, &it);
|
||||||
|
dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, "a(yv)", &v);
|
||||||
|
dbus_message_iter_open_container(&v, DBUS_TYPE_ARRAY, "(yv)", &a);
|
||||||
|
for(i = 0; i < ctx->npost; i++){
|
||||||
|
p = &ctx->post[i];
|
||||||
|
dbus_message_iter_open_container(&a, DBUS_TYPE_STRUCT, nil, &st);
|
||||||
|
dbus_message_iter_append_basic(&st, DBUS_TYPE_BYTE, &p->type);
|
||||||
|
appendibustext(&st, p->text, p->type != 'c');
|
||||||
|
dbus_message_iter_close_container(&a, &st);
|
||||||
|
if(p->type == 'c')
|
||||||
|
continue;
|
||||||
|
dbus_message_iter_open_container(&a, DBUS_TYPE_STRUCT, nil, &st);
|
||||||
|
dbus_message_iter_append_basic(&st, DBUS_TYPE_BYTE, &p->type);
|
||||||
|
appendibustext(&st, p->pos, 0);
|
||||||
|
dbus_message_iter_close_container(&a, &st);
|
||||||
|
}
|
||||||
|
dbus_message_iter_close_container(&v, &a);
|
||||||
|
dbus_message_iter_close_container(&it, &v);
|
||||||
|
dbus_connection_send(c, r, nil);
|
||||||
|
dbus_message_unref(r);
|
||||||
|
ctx->npost = 0;
|
||||||
|
return DBUS_HANDLER_RESULT_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DBusHandlerResult
|
static DBusHandlerResult
|
||||||
@@ -657,10 +743,7 @@ handlecap(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|||||||
char preedit[Maxutf];
|
char preedit[Maxutf];
|
||||||
u32int old;
|
u32int old;
|
||||||
|
|
||||||
if(!dbus_message_get_args(m, nil, DBUS_TYPE_UINT32, &cap,
|
dbus_message_get_args(m, nil, DBUS_TYPE_UINT32, &cap, DBUS_TYPE_INVALID);
|
||||||
DBUS_TYPE_INVALID))
|
|
||||||
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
||||||
"SetCapabilities expects one unsigned integer");
|
|
||||||
old = ctx->cap;
|
old = ctx->cap;
|
||||||
ctx->cap = cap;
|
ctx->cap = cap;
|
||||||
if(ctx->focused){
|
if(ctx->focused){
|
||||||
@@ -715,7 +798,7 @@ getcontent(DBusMessageIter *value, u32int *purpose)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Only ContentType and ClientCommitPreedit are settable; none is readable. */
|
/* ContentType, ClientCommitPreedit, and EffectivePostProcessKeyEvent are settable. */
|
||||||
static DBusHandlerResult
|
static DBusHandlerResult
|
||||||
handlepropertyset(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
handlepropertyset(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
||||||
{
|
{
|
||||||
@@ -741,19 +824,23 @@ handlepropertyset(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|||||||
flushcontext(ctx, Keyreset);
|
flushcontext(ctx, Keyreset);
|
||||||
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
||||||
}
|
}
|
||||||
if(strcmp(name, "ClientCommitPreedit") == 0){
|
if(strcmp(name, "ClientCommitPreedit") == 0 ||
|
||||||
|
strcmp(name, "EffectivePostProcessKeyEvent") == 0){
|
||||||
if(dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_STRUCT)
|
if(dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_STRUCT)
|
||||||
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
||||||
"ClientCommitPreedit expects (b)");
|
"the property expects (b)");
|
||||||
dbus_message_iter_recurse(&value, &st);
|
dbus_message_iter_recurse(&value, &st);
|
||||||
if(dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_BOOLEAN)
|
if(dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_BOOLEAN)
|
||||||
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
||||||
"ClientCommitPreedit expects (b)");
|
"the property expects (b)");
|
||||||
dbus_message_iter_get_basic(&st, &b);
|
dbus_message_iter_get_basic(&st, &b);
|
||||||
if(dbus_message_iter_next(&st))
|
if(dbus_message_iter_next(&st))
|
||||||
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
||||||
"ClientCommitPreedit expects (b)");
|
"the property expects (b)");
|
||||||
ctx->clientcommitpreedit = b != FALSE;
|
if(name[0] == 'C')
|
||||||
|
ctx->clientcommitpreedit = b != FALSE;
|
||||||
|
else
|
||||||
|
ctx->postprocess = b != FALSE;
|
||||||
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
||||||
}
|
}
|
||||||
return handleerror(c, m, DBUS_ERROR_UNKNOWN_PROPERTY,
|
return handleerror(c, m, DBUS_ERROR_UNKNOWN_PROPERTY,
|
||||||
@@ -796,35 +883,35 @@ handlecursor(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|||||||
{
|
{
|
||||||
dbus_int32_t x, y, w, h;
|
dbus_int32_t x, y, w, h;
|
||||||
|
|
||||||
if(!dbus_message_get_args(m, nil,
|
dbus_message_get_args(m, nil, DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y,
|
||||||
DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y,
|
DBUS_TYPE_INT32, &w, DBUS_TYPE_INT32, &h, DBUS_TYPE_INVALID);
|
||||||
DBUS_TYPE_INT32, &w, DBUS_TYPE_INT32, &h,
|
|
||||||
DBUS_TYPE_INVALID))
|
|
||||||
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
||||||
"SetCursorLocation expects four integers");
|
|
||||||
if(w < 0 || h < 0)
|
|
||||||
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
||||||
"cursor dimensions must not be negative");
|
|
||||||
setcursor(ctx, x, y, h);
|
setcursor(ctx, x, y, h);
|
||||||
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Argument-free InputContext calls, checked for an empty signature. */
|
/* SetEngine: there is one engine, so any name is fine. */
|
||||||
static DBusHandlerResult
|
static DBusHandlerResult
|
||||||
handleplain(DBusConnection *c, DBusMessage *m, Ictx *ctx, const char *member)
|
handleok(DBusConnection *c, DBusMessage *m, Ictx*)
|
||||||
{
|
{
|
||||||
if(!dbus_message_has_signature(m, ""))
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
||||||
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
||||||
"this call takes no arguments");
|
|
||||||
if(strcmp(member, "FocusIn") == 0)
|
|
||||||
return handlefocusin(c, m, ctx);
|
|
||||||
if(strcmp(member, "FocusOut") == 0)
|
|
||||||
return handlefocusout(c, m, ctx);
|
|
||||||
if(strcmp(member, "Reset") == 0)
|
|
||||||
return handlereset(c, m, ctx);
|
|
||||||
return handledestroy(c, m, ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The InputContext interface: member, argument signature, handler. */
|
||||||
|
static struct {
|
||||||
|
char *member;
|
||||||
|
char *sig;
|
||||||
|
DBusHandlerResult (*fn)(DBusConnection*, DBusMessage*, Ictx*);
|
||||||
|
} ictab[] = {
|
||||||
|
{"ProcessKeyEvent", "uuu", handlekey},
|
||||||
|
{"FocusIn", "", handlefocusin},
|
||||||
|
{"FocusOut", "", handlefocusout},
|
||||||
|
{"Reset", "", handlereset},
|
||||||
|
{"Destroy", "", handledestroy},
|
||||||
|
{"SetCursorLocation", "iiii", handlecursor},
|
||||||
|
{"SetCapabilities", "u", handlecap},
|
||||||
|
{"SetEngine", "s", handleok},
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* One handler for everything a client sends: the tiny slice of the bus
|
* One handler for everything a client sends: the tiny slice of the bus
|
||||||
* that libibus needs, then the InputContext interface. Unhandled calls
|
* that libibus needs, then the InputContext interface. Unhandled calls
|
||||||
@@ -835,6 +922,7 @@ onmsg(DBusConnection *c, DBusMessage *m, void*)
|
|||||||
{
|
{
|
||||||
const char *iface, *member, *path;
|
const char *iface, *member, *path;
|
||||||
Ictx *ctx;
|
Ictx *ctx;
|
||||||
|
int i;
|
||||||
|
|
||||||
if(dbus_message_get_type(m) != DBUS_MESSAGE_TYPE_METHOD_CALL)
|
if(dbus_message_get_type(m) != DBUS_MESSAGE_TYPE_METHOD_CALL)
|
||||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||||
@@ -861,14 +949,20 @@ onmsg(DBusConnection *c, DBusMessage *m, void*)
|
|||||||
return handlecreate(c, m);
|
return handlecreate(c, m);
|
||||||
}
|
}
|
||||||
if(strcmp(iface, "org.freedesktop.DBus.Properties") == 0){
|
if(strcmp(iface, "org.freedesktop.DBus.Properties") == 0){
|
||||||
if(findcontext(c, path) == nil)
|
ctx = findcontext(c, path);
|
||||||
|
if(ctx == nil)
|
||||||
return handleerror(c, m, DBUS_ERROR_UNKNOWN_OBJECT,
|
return handleerror(c, m, DBUS_ERROR_UNKNOWN_OBJECT,
|
||||||
"unknown input context");
|
"unknown input context");
|
||||||
if(strcmp(member, "Set") == 0)
|
if(strcmp(member, "Set") == 0)
|
||||||
return handlepropertyset(c, m, findcontext(c, path));
|
return handlepropertyset(c, m, ctx);
|
||||||
if(strcmp(member, "Get") == 0)
|
if(strcmp(member, "Get") == 0){
|
||||||
|
if(dbus_message_get_args(m, nil, DBUS_TYPE_STRING, &iface,
|
||||||
|
DBUS_TYPE_STRING, &member, DBUS_TYPE_INVALID) &&
|
||||||
|
strcmp(member, "PostProcessKeyEvent") == 0)
|
||||||
|
return handlepost(c, m, ctx);
|
||||||
return handleerror(c, m, DBUS_ERROR_UNKNOWN_PROPERTY,
|
return handleerror(c, m, DBUS_ERROR_UNKNOWN_PROPERTY,
|
||||||
"input context properties are write-only");
|
"unknown input context property");
|
||||||
|
}
|
||||||
if(strcmp(member, "GetAll") == 0)
|
if(strcmp(member, "GetAll") == 0)
|
||||||
return handlepropertygetall(c, m);
|
return handlepropertygetall(c, m);
|
||||||
}
|
}
|
||||||
@@ -881,50 +975,39 @@ onmsg(DBusConnection *c, DBusMessage *m, void*)
|
|||||||
if(ctx == nil)
|
if(ctx == nil)
|
||||||
return handleerror(c, m, DBUS_ERROR_UNKNOWN_OBJECT,
|
return handleerror(c, m, DBUS_ERROR_UNKNOWN_OBJECT,
|
||||||
"unknown input context");
|
"unknown input context");
|
||||||
if(strcmp(member, "ProcessKeyEvent") == 0)
|
for(i = 0; i < nelem(ictab); i++){
|
||||||
return handlekey(c, m, ctx);
|
if(strcmp(member, ictab[i].member) != 0)
|
||||||
if(strcmp(member, "FocusIn") == 0 ||
|
continue;
|
||||||
strcmp(member, "FocusOut") == 0 ||
|
if(!dbus_message_has_signature(m, ictab[i].sig))
|
||||||
strcmp(member, "Reset") == 0 ||
|
|
||||||
strcmp(member, "Destroy") == 0)
|
|
||||||
return handleplain(c, m, ctx, member);
|
|
||||||
if(strcmp(member, "SetCursorLocation") == 0)
|
|
||||||
return handlecursor(c, m, ctx);
|
|
||||||
if(strcmp(member, "SetCapabilities") == 0)
|
|
||||||
return handlecap(c, m, ctx);
|
|
||||||
if(strcmp(member, "SetEngine") == 0){
|
|
||||||
if(!dbus_message_has_signature(m, "s"))
|
|
||||||
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
||||||
"SetEngine expects one engine name");
|
"bad arguments");
|
||||||
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
return ictab[i].fn(c, m, ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
newconn(DBusServer *s, DBusConnection *c, void *_)
|
newconn(DBusServer*, DBusConnection *c, void*)
|
||||||
{
|
{
|
||||||
DBusObjectPathVTable vt;
|
DBusObjectPathVTable vt;
|
||||||
|
|
||||||
USED(s);
|
if(nconns >= Maxclients){
|
||||||
USED(_);
|
fprint(2, "strans: ibus: rejecting client: %d-connection limit reached\n",
|
||||||
if(nconns >= Maxconns){
|
Maxclients);
|
||||||
fprintf(stderr, "strans: ibus: rejecting client: %d-connection limit reached\n",
|
|
||||||
Maxconns);
|
|
||||||
dbus_connection_close(c);
|
dbus_connection_close(c);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!dbus_connection_set_watch_functions(c, addwatch, removewatch,
|
if(!dbus_connection_set_watch_functions(c, addwatch, removewatch,
|
||||||
togglewatch, nil, nil)){
|
nil, nil, nil)){
|
||||||
fprintf(stderr, "strans: ibus: cannot watch client connection\n");
|
fprint(2, "strans: ibus: cannot watch client connection\n");
|
||||||
dbus_connection_close(c);
|
dbus_connection_close(c);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
memset(&vt, 0, sizeof(vt));
|
memset(&vt, 0, sizeof(vt));
|
||||||
vt.message_function = onmsg;
|
vt.message_function = onmsg;
|
||||||
if(!dbus_connection_register_fallback(c, "/", &vt, nil)){
|
if(!dbus_connection_register_fallback(c, "/", &vt, nil)){
|
||||||
fprintf(stderr, "strans: ibus: cannot register client handler\n");
|
fprint(2, "strans: ibus: cannot register client handler\n");
|
||||||
dbus_connection_set_watch_functions(c, nil, nil, nil, nil, nil);
|
dbus_connection_set_watch_functions(c, nil, nil, nil, nil, nil);
|
||||||
dbus_connection_close(c);
|
dbus_connection_close(c);
|
||||||
return;
|
return;
|
||||||
@@ -950,70 +1033,35 @@ pruneconns(void)
|
|||||||
nconns = j;
|
nconns = j;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static void
|
||||||
ibusinit(void)
|
ibusinit(void)
|
||||||
{
|
{
|
||||||
DBusError err;
|
DBusError err;
|
||||||
char addr[128];
|
char addr[64], *full;
|
||||||
char *full;
|
|
||||||
int cleanupregistered;
|
|
||||||
|
|
||||||
full = nil;
|
if(buildaddrpath(addrfile, sizeof addrfile) < 0)
|
||||||
cleanupregistered = 0;
|
die("ibus: cannot build address path");
|
||||||
if(buildaddrpath(addrfile, sizeof(addrfile)) < 0){
|
snprintf(addr, sizeof addr, "unix:abstract=strans-%d", (int)getpid());
|
||||||
fprintf(stderr, "strans: ibus: cannot build address path\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if(snprintf(addr, sizeof(addr), "unix:abstract=strans-%d",
|
|
||||||
(int)getpid()) >= (int)sizeof addr){
|
|
||||||
fprintf(stderr, "strans: ibus: address is too long\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
dbus_error_init(&err);
|
dbus_error_init(&err);
|
||||||
srv = dbus_server_listen(addr, &err);
|
srv = dbus_server_listen(addr, &err);
|
||||||
if(srv == nil){
|
if(srv == nil)
|
||||||
fprintf(stderr, "strans: ibus: listen: %s\n", err.message);
|
die("ibus: listen: %s", err.message);
|
||||||
dbus_error_free(&err);
|
|
||||||
addrfile[0] = '\0';
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
dbus_server_set_new_connection_function(srv, newconn, nil, nil);
|
dbus_server_set_new_connection_function(srv, newconn, nil, nil);
|
||||||
if(!dbus_server_set_watch_functions(srv, addwatch, removewatch,
|
if(!dbus_server_set_watch_functions(srv, addwatch, removewatch,
|
||||||
togglewatch, nil, nil)){
|
nil, nil, nil))
|
||||||
fprintf(stderr, "strans: ibus: cannot watch server\n");
|
die("ibus: cannot watch server");
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
full = dbus_server_get_address(srv);
|
full = dbus_server_get_address(srv);
|
||||||
if(full == nil){
|
if(full == nil)
|
||||||
fprintf(stderr, "strans: ibus: cannot get server address\n");
|
die("ibus: cannot get server address");
|
||||||
goto fail;
|
atexit(unlinkaddr);
|
||||||
}
|
|
||||||
if(!atexit(unlinkaddr)){
|
|
||||||
fprintf(stderr, "strans: ibus: cannot register address cleanup\n");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
cleanupregistered = 1;
|
|
||||||
threadnotify(addrnote, 1);
|
threadnotify(addrnote, 1);
|
||||||
if(writeaddr(addrfile, full) < 0){
|
if(writeaddr(addrfile, full) < 0)
|
||||||
fprintf(stderr, "strans: ibus: cannot write %s\n", addrfile);
|
die("ibus: cannot write %s", addrfile);
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
dbus_free(full);
|
dbus_free(full);
|
||||||
return 0;
|
|
||||||
fail:
|
|
||||||
if(cleanupregistered)
|
|
||||||
atexitdont(unlinkaddr);
|
|
||||||
if(full != nil)
|
|
||||||
dbus_free(full);
|
|
||||||
dbus_server_disconnect(srv);
|
|
||||||
dbus_server_unref(srv);
|
|
||||||
srv = nil;
|
|
||||||
addrfile[0] = '\0';
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ibusthread(void *_)
|
ibusthread(void*)
|
||||||
{
|
{
|
||||||
struct pollfd pfds[Maxwatches];
|
struct pollfd pfds[Maxwatches];
|
||||||
DBusWatch *polled[Maxwatches];
|
DBusWatch *polled[Maxwatches];
|
||||||
@@ -1021,13 +1069,9 @@ ibusthread(void *_)
|
|||||||
int i, n, rv;
|
int i, n, rv;
|
||||||
unsigned int f;
|
unsigned int f;
|
||||||
|
|
||||||
USED(_);
|
|
||||||
threadsetname("ibus");
|
threadsetname("ibus");
|
||||||
if(ibusinit() < 0)
|
ibusinit();
|
||||||
die("ibus: initialization failed");
|
|
||||||
replyc = chancreate(sizeof(Keyres), 0);
|
replyc = chancreate(sizeof(Keyres), 0);
|
||||||
if(replyc == nil)
|
|
||||||
die("ibus: cannot create reply channel");
|
|
||||||
for(;;){
|
for(;;){
|
||||||
if(!dbus_server_get_is_connected(srv))
|
if(!dbus_server_get_is_connected(srv))
|
||||||
die("ibus: server disconnected");
|
die("ibus: server disconnected");
|
||||||
@@ -1050,7 +1094,7 @@ ibusthread(void *_)
|
|||||||
if(rv < 0 && errno == EINTR)
|
if(rv < 0 && errno == EINTR)
|
||||||
continue;
|
continue;
|
||||||
if(rv < 0)
|
if(rv < 0)
|
||||||
die("ibus: poll: %s", strerror(errno));
|
die("ibus: poll: %r");
|
||||||
for(i = 0; i < n; i++){
|
for(i = 0; i < n; i++){
|
||||||
if(pfds[i].revents == 0)
|
if(pfds[i].revents == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
15
ipc.c
15
ipc.c
@@ -165,20 +165,31 @@ ipcconnect(void)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Engine key for an X keysym and its Unicode value: printable characters
|
* Engine key for an X keysym and its Unicode value: printable characters
|
||||||
* as themselves, the function keysyms 0xff00-0xffff as Kspec+offset.
|
* as themselves, the function keysyms 0xff00-0xffff as Kspec+offset, and
|
||||||
* The keypad and Shift+Tab (ISO_Left_Tab) variants fold onto Enter and Tab.
|
* no key at all for a modifier. The keypad keys and Shift+Tab
|
||||||
|
* (ISO_Left_Tab) fold onto their plain counterparts.
|
||||||
*/
|
*/
|
||||||
uint32_t
|
uint32_t
|
||||||
ipckeysym(uint32_t sym, uint32_t unicode)
|
ipckeysym(uint32_t sym, uint32_t unicode)
|
||||||
{
|
{
|
||||||
if(unicode >= ' ' && unicode != 0x7f)
|
if(unicode >= ' ' && unicode != 0x7f)
|
||||||
return unicode;
|
return unicode;
|
||||||
|
if(sym >= 0xffe1 && sym <= 0xffee)
|
||||||
|
return 0;
|
||||||
switch(sym){
|
switch(sym){
|
||||||
case 0xfe20:
|
case 0xfe20:
|
||||||
case 0xff89:
|
case 0xff89:
|
||||||
return Ktab;
|
return Ktab;
|
||||||
case 0xff8d:
|
case 0xff8d:
|
||||||
return Kret;
|
return Kret;
|
||||||
|
case 0xff97:
|
||||||
|
return Kup;
|
||||||
|
case 0xff99:
|
||||||
|
return Kdown;
|
||||||
|
case 0xff9a:
|
||||||
|
return Kpgup;
|
||||||
|
case 0xff9b:
|
||||||
|
return Kpgdown;
|
||||||
}
|
}
|
||||||
if(sym >= 0xff00 && sym <= 0xffff)
|
if(sym >= 0xff00 && sym <= 0xffff)
|
||||||
return Kspec + (sym - 0xff00);
|
return Kspec + (sym - 0xff00);
|
||||||
|
|||||||
8
ipc.h
8
ipc.h
@@ -39,8 +39,12 @@ enum
|
|||||||
Kdown = Kspec|0x54,
|
Kdown = Kspec|0x54,
|
||||||
Kpgup = Kspec|0x55,
|
Kpgup = Kspec|0x55,
|
||||||
Kpgdown = Kspec|0x56,
|
Kpgdown = Kspec|0x56,
|
||||||
Kmodfirst = Kspec|0xe1,
|
Khangul = Kspec|0x31,
|
||||||
Kmodlast = Kspec|0xee,
|
Khanja = Kspec|0x34,
|
||||||
|
Kmuhenkan = Kspec|0x22,
|
||||||
|
Khenkan = Kspec|0x23,
|
||||||
|
Kkana = Kspec|0x27,
|
||||||
|
Kzenkaku = Kspec|0x2a,
|
||||||
|
|
||||||
/* X core modifier bits; GDK and IBus also flag Super at bit 26. */
|
/* X core modifier bits; GDK and IBus also flag Super at bit 26. */
|
||||||
Mshift = 1<<0,
|
Mshift = 1<<0,
|
||||||
|
|||||||
79
ko.c
79
ko.c
@@ -4,8 +4,6 @@
|
|||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Sbase = 0xAC00,
|
Sbase = 0xAC00,
|
||||||
Jbase = 0x3131,
|
|
||||||
Jrange = 51,
|
|
||||||
Ncho = 19,
|
Ncho = 19,
|
||||||
Njung = 21,
|
Njung = 21,
|
||||||
Njong = 28,
|
Njong = 28,
|
||||||
@@ -35,41 +33,21 @@ static Rune jong[] = {
|
|||||||
L'ㅌ', L'ㅍ', L'ㅎ',
|
L'ㅌ', L'ㅍ', L'ㅎ',
|
||||||
};
|
};
|
||||||
|
|
||||||
static int choidx[Jrange] = {
|
/* A jamo's index in cho, jung, or jong, or -1; jong[0] is no jong. */
|
||||||
[L'ㄱ'-Jbase] = 1, [L'ㄲ'-Jbase] = 2, [L'ㄴ'-Jbase] = 3,
|
static int
|
||||||
[L'ㄷ'-Jbase] = 4, [L'ㄸ'-Jbase] = 5, [L'ㄹ'-Jbase] = 6,
|
idx(Rune *t, int n, Rune r)
|
||||||
[L'ㅁ'-Jbase] = 7, [L'ㅂ'-Jbase] = 8, [L'ㅃ'-Jbase] = 9,
|
{
|
||||||
[L'ㅅ'-Jbase] = 10, [L'ㅆ'-Jbase] = 11, [L'ㅇ'-Jbase] = 12,
|
int i;
|
||||||
[L'ㅈ'-Jbase] = 13, [L'ㅉ'-Jbase] = 14, [L'ㅊ'-Jbase] = 15,
|
|
||||||
[L'ㅋ'-Jbase] = 16, [L'ㅌ'-Jbase] = 17, [L'ㅍ'-Jbase] = 18,
|
|
||||||
[L'ㅎ'-Jbase] = 19,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int jungidx[Jrange] = {
|
for(i = 0; i < n; i++)
|
||||||
[L'ㅏ'-Jbase] = 1, [L'ㅐ'-Jbase] = 2, [L'ㅑ'-Jbase] = 3,
|
if(t[i] == r)
|
||||||
[L'ㅒ'-Jbase] = 4, [L'ㅓ'-Jbase] = 5, [L'ㅔ'-Jbase] = 6,
|
return i;
|
||||||
[L'ㅕ'-Jbase] = 7, [L'ㅖ'-Jbase] = 8, [L'ㅗ'-Jbase] = 9,
|
return -1;
|
||||||
[L'ㅘ'-Jbase] = 10, [L'ㅙ'-Jbase] = 11, [L'ㅚ'-Jbase] = 12,
|
}
|
||||||
[L'ㅛ'-Jbase] = 13, [L'ㅜ'-Jbase] = 14, [L'ㅝ'-Jbase] = 15,
|
|
||||||
[L'ㅞ'-Jbase] = 16, [L'ㅟ'-Jbase] = 17, [L'ㅠ'-Jbase] = 18,
|
|
||||||
[L'ㅡ'-Jbase] = 19, [L'ㅢ'-Jbase] = 20, [L'ㅣ'-Jbase] = 21,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int jongidx[Jrange] = {
|
#define Choidx(r) idx(cho, Ncho, r)
|
||||||
[L'ㄱ'-Jbase] = 2, [L'ㄲ'-Jbase] = 3, [L'ㄳ'-Jbase] = 4,
|
#define Jungidx(r) idx(jung, Njung, r)
|
||||||
[L'ㄴ'-Jbase] = 5, [L'ㄵ'-Jbase] = 6, [L'ㄶ'-Jbase] = 7,
|
#define Jongidx(r) idx(jong, Njong, r)
|
||||||
[L'ㄷ'-Jbase] = 8, [L'ㄹ'-Jbase] = 9, [L'ㄺ'-Jbase] = 10,
|
|
||||||
[L'ㄻ'-Jbase] = 11, [L'ㄼ'-Jbase] = 12, [L'ㄽ'-Jbase] = 13,
|
|
||||||
[L'ㄾ'-Jbase] = 14, [L'ㄿ'-Jbase] = 15, [L'ㅀ'-Jbase] = 16,
|
|
||||||
[L'ㅁ'-Jbase] = 17, [L'ㅂ'-Jbase] = 18, [L'ㅄ'-Jbase] = 19,
|
|
||||||
[L'ㅅ'-Jbase] = 20, [L'ㅆ'-Jbase] = 21, [L'ㅇ'-Jbase] = 22,
|
|
||||||
[L'ㅈ'-Jbase] = 23, [L'ㅊ'-Jbase] = 24, [L'ㅋ'-Jbase] = 25,
|
|
||||||
[L'ㅌ'-Jbase] = 26, [L'ㅍ'-Jbase] = 27, [L'ㅎ'-Jbase] = 28,
|
|
||||||
};
|
|
||||||
|
|
||||||
#define Choidx(r) (choidx[(r) - Jbase] - 1)
|
|
||||||
#define Jungidx(r) (jungidx[(r) - Jbase] - 1)
|
|
||||||
#define Jongidx(r) (jongidx[(r) - Jbase] - 1)
|
|
||||||
|
|
||||||
static Rune jamomap[128] = {
|
static Rune jamomap[128] = {
|
||||||
['r'] = L'ㄱ', ['R'] = L'ㄲ',
|
['r'] = L'ㄱ', ['R'] = L'ㄲ',
|
||||||
@@ -221,12 +199,23 @@ transko(Im *im, Rune c)
|
|||||||
sputr(&e.next, compose(ci, ji, 0));
|
sputr(&e.next, compose(ci, ji, 0));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
if(Jungidx(last) >= 0 && ji >= 0){
|
/* A vowel typed first is reordered under its consonant. */
|
||||||
comb = combine(cvow, nelem(cvow), last, jm);
|
if(Jungidx(last) >= 0 && Choidx(jm) >= 0){
|
||||||
if(comb){
|
sputr(&e.next, compose(Choidx(jm), Jungidx(last), 0));
|
||||||
sputr(&e.next, comb);
|
return e;
|
||||||
return e;
|
}
|
||||||
}
|
/* Two vowels or two consonants may be one jamo, ㅘ or ㄳ. */
|
||||||
|
comb = combine(cvow, nelem(cvow), last, jm);
|
||||||
|
if(comb == 0)
|
||||||
|
comb = combine(cjong, nelem(cjong), last, jm);
|
||||||
|
if(comb){
|
||||||
|
sputr(&e.next, comb);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
if(ji >= 0 && splitpair(cjong, nelem(cjong), last, &stay, &next)){
|
||||||
|
sputr(&e.s, stay);
|
||||||
|
sputr(&e.next, compose(Choidx(next), ji, 0));
|
||||||
|
return e;
|
||||||
}
|
}
|
||||||
e.s = im->pre;
|
e.s = im->pre;
|
||||||
sputr(&e.next, jm);
|
sputr(&e.next, jm);
|
||||||
@@ -294,12 +283,10 @@ backko(Im *im)
|
|||||||
|
|
||||||
last = slastr(&im->pre);
|
last = slastr(&im->pre);
|
||||||
if(!issyl(last)){
|
if(!issyl(last)){
|
||||||
if(splitpair(cvow, nelem(cvow), last, &stay, &next)){
|
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&im->pre, stay);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
spopr(&im->pre);
|
spopr(&im->pre);
|
||||||
|
if(splitpair(cvow, nelem(cvow), last, &stay, &next) ||
|
||||||
|
splitpair(cjong, nelem(cjong), last, &stay, &next))
|
||||||
|
sputr(&im->pre, stay);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
decompose(last, &c, &j, &jo);
|
decompose(last, &c, &j, &jo);
|
||||||
|
|||||||
1
main.c
1
main.c
@@ -55,6 +55,7 @@ threadmain(int argc, char **argv)
|
|||||||
drawc = chancreate(sizeof(Drawcmd), 4);
|
drawc = chancreate(sizeof(Drawcmd), 4);
|
||||||
keyc = chancreate(sizeof(Keyreq), 0);
|
keyc = chancreate(sizeof(Keyreq), 0);
|
||||||
langinit(argv[1]);
|
langinit(argv[1]);
|
||||||
|
composeinit();
|
||||||
srvinit();
|
srvinit();
|
||||||
proccreate(drawthread, nil, 16384);
|
proccreate(drawthread, nil, 16384);
|
||||||
proccreate(srvthread, nil, 16384);
|
proccreate(srvthread, nil, 16384);
|
||||||
|
|||||||
61
map/README
61
map/README
@@ -3,18 +3,19 @@
|
|||||||
## Korean Hanja data
|
## Korean Hanja data
|
||||||
|
|
||||||
`hanja.src` is the tracked, reviewable source for Korean Hanja conversion.
|
`hanja.src` is the tracked, reviewable source for Korean Hanja conversion.
|
||||||
Every data row is exactly one Hanja character, a tab, and one modern Hangul
|
Every data row is Hanja, a tab, and its modern Hangul reading, a character
|
||||||
syllable:
|
or a word:
|
||||||
|
|
||||||
```
|
```
|
||||||
漢 한
|
漢 한
|
||||||
|
漢字 한자
|
||||||
```
|
```
|
||||||
|
|
||||||
It contains no word rows such as `견출지` or `방학`. `mkhanja` validates this
|
`mkhanja` validates that contract and groups rows by their reading to
|
||||||
contract and groups rows by their Hangul reading to produce the existing
|
produce the runtime dictionary. Candidate order follows source order. The
|
||||||
runtime dictionary format. Candidate order follows source order. The source
|
source keeps all retained pairs for review; the generated dictionary stores
|
||||||
keeps all retained pairs for review; the generated dictionary stores the first
|
the first 128 candidates per reading because that is the engine's lookup
|
||||||
128 candidates per reading because that is the engine's lookup limit.
|
limit.
|
||||||
|
|
||||||
The source is derived from libhangul release tag `libhangul-0.2.0`. The
|
The source is derived from libhangul release tag `libhangul-0.2.0`. The
|
||||||
annotated tag object is `20afc38922e3595ee3ed5b186f2ea05afe663763`, its
|
annotated tag object is `20afc38922e3595ee3ed5b186f2ea05afe663763`, its
|
||||||
@@ -30,14 +31,50 @@ map/libhangul2hanja upstream >map/hanja.src
|
|||||||
map/mkhanja >map/hanja.dict
|
map/mkhanja >map/hanja.dict
|
||||||
```
|
```
|
||||||
|
|
||||||
`libhangul2hanja` retains only rows whose reading is one modern Hangul
|
`libhangul2hanja` retains only rows whose reading is modern Hangul syllables
|
||||||
syllable and whose value is one Hanja character supported by the current
|
throughout and whose value is Hanja the popup can draw: U+3400–U+4DBF,
|
||||||
popup: U+3400–U+4DBF, U+4E00–U+9FFF, or U+F900–U+FAFF. Thus word readings,
|
U+4E00–U+9FFF, or U+F900–U+FAFF. Thus jamo readings, mixed values, and
|
||||||
multi-character values, jamo readings, and supplementary-plane ideographs
|
supplementary-plane ideographs are omitted. The import preserves the upstream license header and row order.
|
||||||
are omitted. The import preserves the upstream license header and row order.
|
|
||||||
The retained data is BSD 3-Clause licensed by Choe Hwanjin; the complete text
|
The retained data is BSD 3-Clause licensed by Choe Hwanjin; the complete text
|
||||||
is in `LICENSES/BSD-3-Clause-libhangul-hanja.txt`.
|
is in `LICENSES/BSD-3-Clause-libhangul-hanja.txt`.
|
||||||
|
|
||||||
|
## Emoji and symbol data
|
||||||
|
|
||||||
|
`symbol.src` is hand-written: a symbol, a tab, and its ASCII aliases, kept
|
||||||
|
so that a bare `1`-`9` picks the matching superscript or subscript from a
|
||||||
|
`^` or `_` prefix search. `emoji.src` is generated: every fully-qualified
|
||||||
|
emoji of Unicode's `emoji-test.txt` except the skin-tone variants, with
|
||||||
|
its CLDR names and keywords in English, Korean, and Japanese. `mkemoji`
|
||||||
|
folds the aliases, adds each Japanese one in the other kana so a query
|
||||||
|
typed in either mode finds it, and writes one row per alias to
|
||||||
|
`emoji.dict`; the engine searches that dictionary by prefix, so the rows
|
||||||
|
carry no prefixes.
|
||||||
|
|
||||||
|
The sources are Emoji 17.0 and CLDR 48.2.0. Regenerate them from the
|
||||||
|
repository root with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -L https://www.unicode.org/Public/17.0.0/emoji/emoji-test.txt -o emoji-test.txt
|
||||||
|
printf '%s %s\n' 1d8a944f88d7952f7ef7c5167fef3c67995bcae24543949710231b03a201acda emoji-test.txt | sha256sum -c -
|
||||||
|
for l in en ko ja; do
|
||||||
|
curl -L https://raw.githubusercontent.com/unicode-org/cldr-json/48.2.0/cldr-json/cldr-annotations-full/annotations/$l/annotations.json -o $l.json
|
||||||
|
curl -L https://raw.githubusercontent.com/unicode-org/cldr-json/48.2.0/cldr-json/cldr-annotations-derived-full/annotationsDerived/$l/annotations.json -o $l-derived.json
|
||||||
|
done
|
||||||
|
map/cldr2emoji emoji-test.txt en.json en-derived.json ko.json ko-derived.json ja.json ja-derived.json >map/emoji.src
|
||||||
|
map/mkemoji >map/emoji.dict
|
||||||
|
```
|
||||||
|
|
||||||
|
The data is under the Unicode License v3; the complete text is in
|
||||||
|
`LICENSES/Unicode-3.0.txt`.
|
||||||
|
|
||||||
|
## Japanese romaji
|
||||||
|
|
||||||
|
`hira.map` and `kata.map` are Mozc's default romaji table, one row per key,
|
||||||
|
in gojūon order; `kata.map` is the same table in Katakana, and
|
||||||
|
`make verify-map` checks that the two have the same keys. A doubled
|
||||||
|
consonant, or `tch`, maps to っ and keeps the consonant pending; the engine
|
||||||
|
knows that rule, the table only lists the rows.
|
||||||
|
|
||||||
## Japanese dictionary data
|
## Japanese dictionary data
|
||||||
|
|
||||||
`kanji.dict` is the historical dictionary bundled with strans. Its own header
|
`kanji.dict` is the historical dictionary bundled with strans. Its own header
|
||||||
|
|||||||
52
map/cldr2emoji
Executable file
52
map/cldr2emoji
Executable file
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Write emoji.src: every fully-qualified emoji of Unicode's emoji-test.txt
|
||||||
|
but the skin-tone variants, each with its CLDR names and keywords in the
|
||||||
|
languages given, as result-first TAB-separated rows for mkemoji."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def emoji(path):
|
||||||
|
out = []
|
||||||
|
for line in path.open(encoding="utf-8"):
|
||||||
|
if "; fully-qualified" not in line:
|
||||||
|
continue
|
||||||
|
cps = [int(cp, 16) for cp in line.split(";")[0].split()]
|
||||||
|
if any(0x1F3FB <= cp <= 0x1F3FF for cp in cps):
|
||||||
|
continue
|
||||||
|
out.append("".join(map(chr, cps)))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def annotations(path):
|
||||||
|
"""CLDR annotations.json, plain or derived: emoji -> names, keywords."""
|
||||||
|
root = json.load(path.open(encoding="utf-8"))
|
||||||
|
table = root[next(iter(root))]["annotations"]
|
||||||
|
return {e: a.get("tts", []) + a.get("default", []) for e, a in table.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print(f"usage: {sys.argv[0]} emoji-test.txt annotations.json...",
|
||||||
|
file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
tables = [annotations(Path(arg)) for arg in sys.argv[2:]]
|
||||||
|
print("# Result first, then its CLDR names and keywords; see map/README.")
|
||||||
|
for e in emoji(Path(sys.argv[1])):
|
||||||
|
aliases = []
|
||||||
|
for table in tables:
|
||||||
|
for alias in table.get(e.replace("️", ""), table.get(e, [])):
|
||||||
|
alias = alias.strip()
|
||||||
|
if alias and alias not in aliases:
|
||||||
|
aliases.append(alias)
|
||||||
|
if aliases:
|
||||||
|
print(e + "\t" + "\t".join(aliases))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
19131
map/emoji.dict
19131
map/emoji.dict
File diff suppressed because it is too large
Load Diff
2027
map/emoji.src
2027
map/emoji.src
File diff suppressed because it is too large
Load Diff
186733
map/hanja.dict
186733
map/hanja.dict
File diff suppressed because it is too large
Load Diff
238096
map/hanja.src
238096
map/hanja.src
File diff suppressed because it is too large
Load Diff
319
map/hira.map
319
map/hira.map
@@ -1,152 +1,321 @@
|
|||||||
a あ
|
a あ
|
||||||
- ー
|
|
||||||
i い
|
i い
|
||||||
u う
|
u う
|
||||||
e え
|
e え
|
||||||
o お
|
o お
|
||||||
ka か
|
|
||||||
ga が
|
|
||||||
ki き
|
|
||||||
kya きゃ
|
kya きゃ
|
||||||
|
kyi きぃ
|
||||||
kyu きゅ
|
kyu きゅ
|
||||||
|
kye きぇ
|
||||||
kyo きょ
|
kyo きょ
|
||||||
gi ぎ
|
ka か
|
||||||
gya ぎゃ
|
ki き
|
||||||
gyu ぎゅ
|
|
||||||
gyo ぎょ
|
|
||||||
ku く
|
ku く
|
||||||
gu ぐ
|
|
||||||
ke け
|
ke け
|
||||||
ge げ
|
|
||||||
ko こ
|
ko こ
|
||||||
|
kwa くぁ
|
||||||
|
kwi くぃ
|
||||||
|
kwu くぅ
|
||||||
|
kwe くぇ
|
||||||
|
kwo くぉ
|
||||||
|
gya ぎゃ
|
||||||
|
gyi ぎぃ
|
||||||
|
gyu ぎゅ
|
||||||
|
gye ぎぇ
|
||||||
|
gyo ぎょ
|
||||||
|
ga が
|
||||||
|
gi ぎ
|
||||||
|
gu ぐ
|
||||||
|
ge げ
|
||||||
go ご
|
go ご
|
||||||
sa さ
|
gwa ぐぁ
|
||||||
za ざ
|
gwi ぐぃ
|
||||||
si し
|
gwu ぐぅ
|
||||||
shi し
|
gwe ぐぇ
|
||||||
|
gwo ぐぉ
|
||||||
|
sya しゃ
|
||||||
|
syi しぃ
|
||||||
|
syu しゅ
|
||||||
|
sye しぇ
|
||||||
|
syo しょ
|
||||||
sha しゃ
|
sha しゃ
|
||||||
|
shi し
|
||||||
shu しゅ
|
shu しゅ
|
||||||
she しぇ
|
she しぇ
|
||||||
sho しょ
|
sho しょ
|
||||||
syo しょ
|
sa さ
|
||||||
zi ぢ
|
si し
|
||||||
|
su す
|
||||||
|
se せ
|
||||||
|
so そ
|
||||||
|
swa すぁ
|
||||||
|
swi すぃ
|
||||||
|
swu すぅ
|
||||||
|
swe すぇ
|
||||||
|
swo すぉ
|
||||||
|
z/ ・
|
||||||
|
z. …
|
||||||
|
z, ‥
|
||||||
|
zh ←
|
||||||
|
zj ↓
|
||||||
|
zk ↑
|
||||||
|
zl →
|
||||||
|
z- 〜
|
||||||
|
z[ 『
|
||||||
|
z] 』
|
||||||
|
zya じゃ
|
||||||
|
zyi じぃ
|
||||||
|
zyu じゅ
|
||||||
|
zye じぇ
|
||||||
|
zyo じょ
|
||||||
|
zwa ずぁ
|
||||||
|
zwi ずぃ
|
||||||
|
zwu ずぅ
|
||||||
|
zwe ずぇ
|
||||||
|
zwo ずぉ
|
||||||
|
za ざ
|
||||||
|
zi じ
|
||||||
|
zu ず
|
||||||
|
ze ぜ
|
||||||
|
zo ぞ
|
||||||
ja じゃ
|
ja じゃ
|
||||||
|
ji じ
|
||||||
ju じゅ
|
ju じゅ
|
||||||
je じぇ
|
je じぇ
|
||||||
jo じょ
|
jo じょ
|
||||||
su す
|
jya じゃ
|
||||||
zu ず
|
jyi じぃ
|
||||||
se せ
|
jyu じゅ
|
||||||
ze ぜ
|
jye じぇ
|
||||||
so そ
|
jyo じょ
|
||||||
zo ぞ
|
tya ちゃ
|
||||||
ta た
|
tyi ちぃ
|
||||||
da だ
|
|
||||||
di ぢ
|
|
||||||
ti ち
|
|
||||||
chi ち
|
|
||||||
cha ちゃ
|
|
||||||
chu ちゅ
|
|
||||||
che ちぇ
|
|
||||||
tyu ちゅ
|
tyu ちゅ
|
||||||
cho ちょ
|
tye ちぇ
|
||||||
ji じ
|
tyo ちょ
|
||||||
|
tsa つぁ
|
||||||
|
tsi つぃ
|
||||||
|
tse つぇ
|
||||||
|
tso つぉ
|
||||||
|
tha てゃ
|
||||||
|
thi てぃ
|
||||||
|
t'i てぃ
|
||||||
|
thu てゅ
|
||||||
|
the てぇ
|
||||||
|
tho てょ
|
||||||
|
t'yu てゅ
|
||||||
|
twa とぁ
|
||||||
|
twi とぃ
|
||||||
|
twu とぅ
|
||||||
|
twe とぇ
|
||||||
|
two とぉ
|
||||||
|
t'u とぅ
|
||||||
|
ta た
|
||||||
|
ti ち
|
||||||
tu つ
|
tu つ
|
||||||
tsu つ
|
tsu つ
|
||||||
du づ
|
|
||||||
te て
|
te て
|
||||||
de で
|
|
||||||
to と
|
to と
|
||||||
|
dya ぢゃ
|
||||||
|
dyi ぢぃ
|
||||||
|
dyu ぢゅ
|
||||||
|
dye ぢぇ
|
||||||
|
dyo ぢょ
|
||||||
|
dha でゃ
|
||||||
|
dhi でぃ
|
||||||
|
d'i でぃ
|
||||||
|
dhu でゅ
|
||||||
|
dhe でぇ
|
||||||
|
dho でょ
|
||||||
|
d'yu でゅ
|
||||||
|
dwa どぁ
|
||||||
|
dwi どぃ
|
||||||
|
dwu どぅ
|
||||||
|
dwe どぇ
|
||||||
|
dwo どぉ
|
||||||
|
d'u どぅ
|
||||||
|
da だ
|
||||||
|
di ぢ
|
||||||
|
du づ
|
||||||
|
de で
|
||||||
do ど
|
do ど
|
||||||
|
cha ちゃ
|
||||||
|
chi ち
|
||||||
|
chu ちゅ
|
||||||
|
che ちぇ
|
||||||
|
cho ちょ
|
||||||
|
cya ちゃ
|
||||||
|
cyi ちぃ
|
||||||
|
cyu ちゅ
|
||||||
|
cye ちぇ
|
||||||
|
cyo ちょ
|
||||||
|
ca か
|
||||||
|
ci し
|
||||||
|
cu く
|
||||||
|
ce せ
|
||||||
|
co こ
|
||||||
|
nya にゃ
|
||||||
|
nyi にぃ
|
||||||
|
nyu にゅ
|
||||||
|
nye にぇ
|
||||||
|
nyo にょ
|
||||||
|
n' ん
|
||||||
|
nn ん
|
||||||
|
n ん
|
||||||
na な
|
na な
|
||||||
ni に
|
ni に
|
||||||
nya にゃ
|
|
||||||
nyu にゅ
|
|
||||||
nyo にょ
|
|
||||||
nu ぬ
|
nu ぬ
|
||||||
ne ね
|
ne ね
|
||||||
no の
|
no の
|
||||||
ha は
|
|
||||||
ba ば
|
|
||||||
va ば
|
|
||||||
pa ぱ
|
|
||||||
hi ひ
|
|
||||||
hya ひゃ
|
hya ひゃ
|
||||||
|
hyi ひぃ
|
||||||
hyu ひゅ
|
hyu ひゅ
|
||||||
|
hye ひぇ
|
||||||
hyo ひょ
|
hyo ひょ
|
||||||
bi び
|
hwa ふぁ
|
||||||
bya びゃ
|
hwi ふぃ
|
||||||
byu びゅ
|
hwe ふぇ
|
||||||
byo びょ
|
hwo ふぉ
|
||||||
vi び
|
hwyu ふゅ
|
||||||
pi ぴ
|
ha は
|
||||||
pya ぴゃ
|
hi ひ
|
||||||
pyu ぴゅ
|
|
||||||
pyo ぴょ
|
|
||||||
hu ふ
|
hu ふ
|
||||||
fu ふ
|
he へ
|
||||||
|
ho ほ
|
||||||
fa ふぁ
|
fa ふぁ
|
||||||
fi ふぃ
|
fi ふぃ
|
||||||
|
fu ふ
|
||||||
fe ふぇ
|
fe ふぇ
|
||||||
fo ふぉ
|
fo ふぉ
|
||||||
|
fya ふゃ
|
||||||
|
fyu ふゅ
|
||||||
|
fyo ふょ
|
||||||
|
bya びゃ
|
||||||
|
byi びぃ
|
||||||
|
byu びゅ
|
||||||
|
bye びぇ
|
||||||
|
byo びょ
|
||||||
|
ba ば
|
||||||
|
bi び
|
||||||
bu ぶ
|
bu ぶ
|
||||||
vu ぶ
|
|
||||||
pu ぷ
|
|
||||||
he へ
|
|
||||||
be べ
|
be べ
|
||||||
ve べ
|
|
||||||
pe ぺ
|
|
||||||
ho ほ
|
|
||||||
bo ぼ
|
bo ぼ
|
||||||
vo ぼ
|
pya ぴゃ
|
||||||
|
pyi ぴぃ
|
||||||
|
pyu ぴゅ
|
||||||
|
pye ぴぇ
|
||||||
|
pyo ぴょ
|
||||||
|
pa ぱ
|
||||||
|
pi ぴ
|
||||||
|
pu ぷ
|
||||||
|
pe ぺ
|
||||||
po ぽ
|
po ぽ
|
||||||
|
mya みゃ
|
||||||
|
myi みぃ
|
||||||
|
myu みゅ
|
||||||
|
mye みぇ
|
||||||
|
myo みょ
|
||||||
ma ま
|
ma ま
|
||||||
mi み
|
mi み
|
||||||
mya みゃ
|
|
||||||
myu みゅ
|
|
||||||
myo みょ
|
|
||||||
mu む
|
mu む
|
||||||
me め
|
me め
|
||||||
mo も
|
mo も
|
||||||
|
ye いぇ
|
||||||
ya や
|
ya や
|
||||||
yu ゆ
|
yu ゆ
|
||||||
yo よ
|
yo よ
|
||||||
|
rya りゃ
|
||||||
|
ryi りぃ
|
||||||
|
ryu りゅ
|
||||||
|
rye りぇ
|
||||||
|
ryo りょ
|
||||||
ra ら
|
ra ら
|
||||||
ri り
|
ri り
|
||||||
rya りゃ
|
|
||||||
ryu りゅ
|
|
||||||
ryo りょ
|
|
||||||
ru る
|
ru る
|
||||||
re れ
|
re れ
|
||||||
ro ろ
|
ro ろ
|
||||||
|
wu う
|
||||||
|
wyi ゐ
|
||||||
|
wye ゑ
|
||||||
wa わ
|
wa わ
|
||||||
wi ゐ
|
wi うぃ
|
||||||
we ゑ
|
we うぇ
|
||||||
wo を
|
wo を
|
||||||
n ん
|
wha うぁ
|
||||||
|
whi うぃ
|
||||||
|
whu う
|
||||||
|
whe うぇ
|
||||||
|
who うぉ
|
||||||
|
va ゔぁ
|
||||||
|
vi ゔぃ
|
||||||
|
vu ゔ
|
||||||
|
ve ゔぇ
|
||||||
|
vo ゔぉ
|
||||||
|
vya ゔゃ
|
||||||
|
vyi ゔぃ
|
||||||
|
vyu ゔゅ
|
||||||
|
vye ゔぇ
|
||||||
|
vyo ゔょ
|
||||||
|
qa くぁ
|
||||||
|
qi くぃ
|
||||||
|
qu く
|
||||||
|
qe くぇ
|
||||||
|
qo くぉ
|
||||||
xn ん
|
xn ん
|
||||||
xa ぁ
|
xa ぁ
|
||||||
xi ぃ
|
xi ぃ
|
||||||
xu ぅ
|
xu ぅ
|
||||||
xe ぇ
|
xe ぇ
|
||||||
xo ぉ
|
xo ぉ
|
||||||
|
xyi ぃ
|
||||||
|
xye ぇ
|
||||||
|
xka ヵ
|
||||||
|
xke ヶ
|
||||||
|
xtu っ
|
||||||
|
xtsu っ
|
||||||
|
xya ゃ
|
||||||
|
xyu ゅ
|
||||||
|
xyo ょ
|
||||||
|
xwa ゎ
|
||||||
|
la ぁ
|
||||||
|
li ぃ
|
||||||
|
lu ぅ
|
||||||
|
le ぇ
|
||||||
|
lo ぉ
|
||||||
|
lyi ぃ
|
||||||
|
lye ぇ
|
||||||
|
lka ヵ
|
||||||
|
lke ヶ
|
||||||
|
ltu っ
|
||||||
|
ltsu っ
|
||||||
|
lya ゃ
|
||||||
|
lyu ゅ
|
||||||
|
lyo ょ
|
||||||
|
lwa ゎ
|
||||||
|
qq っ
|
||||||
|
vv っ
|
||||||
|
ll っ
|
||||||
|
xx っ
|
||||||
kk っ
|
kk っ
|
||||||
ss っ
|
|
||||||
tt っ
|
|
||||||
pp っ
|
|
||||||
cc っ
|
|
||||||
gg っ
|
gg っ
|
||||||
|
ss っ
|
||||||
zz っ
|
zz っ
|
||||||
jj っ
|
jj っ
|
||||||
|
tt っ
|
||||||
|
tch っ
|
||||||
dd っ
|
dd っ
|
||||||
bb っ
|
|
||||||
hh っ
|
hh っ
|
||||||
ff っ
|
ff っ
|
||||||
|
bb っ
|
||||||
|
pp っ
|
||||||
mm っ
|
mm っ
|
||||||
yy っ
|
yy っ
|
||||||
rr っ
|
rr っ
|
||||||
ww っ
|
ww っ
|
||||||
vv っ
|
cc っ
|
||||||
tch っ
|
- ー
|
||||||
|
~ 〜
|
||||||
. 。
|
. 。
|
||||||
, 、
|
, 、
|
||||||
|
[ 「
|
||||||
|
] 」
|
||||||
|
|||||||
358
map/kata.map
358
map/kata.map
@@ -1,153 +1,321 @@
|
|||||||
a ア
|
a ア
|
||||||
- ー
|
|
||||||
i イ
|
i イ
|
||||||
u ウ
|
u ウ
|
||||||
e エ
|
e エ
|
||||||
o オ
|
o オ
|
||||||
ka カ
|
|
||||||
ga ガ
|
|
||||||
ki キ
|
|
||||||
kya キャ
|
kya キャ
|
||||||
|
kyi キィ
|
||||||
kyu キュ
|
kyu キュ
|
||||||
|
kye キェ
|
||||||
kyo キョ
|
kyo キョ
|
||||||
gi ギ
|
ka カ
|
||||||
gya ギャ
|
ki キ
|
||||||
gyu ギュ
|
|
||||||
gyo ギョ
|
|
||||||
ku ク
|
ku ク
|
||||||
gu グ
|
|
||||||
ke ケ
|
ke ケ
|
||||||
ge ゲ
|
|
||||||
ko コ
|
ko コ
|
||||||
|
kwa クァ
|
||||||
|
kwi クィ
|
||||||
|
kwu クゥ
|
||||||
|
kwe クェ
|
||||||
|
kwo クォ
|
||||||
|
gya ギャ
|
||||||
|
gyi ギィ
|
||||||
|
gyu ギュ
|
||||||
|
gye ギェ
|
||||||
|
gyo ギョ
|
||||||
|
ga ガ
|
||||||
|
gi ギ
|
||||||
|
gu グ
|
||||||
|
ge ゲ
|
||||||
go ゴ
|
go ゴ
|
||||||
sa サ
|
gwa グァ
|
||||||
za ザ
|
gwi グィ
|
||||||
si シ
|
gwu グゥ
|
||||||
shi シ
|
gwe グェ
|
||||||
ji ジ
|
gwo グォ
|
||||||
sha シャ
|
sya シャ
|
||||||
she シェ
|
syi シィ
|
||||||
shu シュ
|
syu シュ
|
||||||
je ジェ
|
sye シェ
|
||||||
sho ショ
|
|
||||||
syo ショ
|
syo ショ
|
||||||
ja ジャ
|
sha シャ
|
||||||
ju ジュ
|
shi シ
|
||||||
jo ジョ
|
shu シュ
|
||||||
|
she シェ
|
||||||
|
sho ショ
|
||||||
|
sa サ
|
||||||
|
si シ
|
||||||
su ス
|
su ス
|
||||||
zu ズ
|
|
||||||
se セ
|
se セ
|
||||||
ze ゼ
|
|
||||||
so ソ
|
so ソ
|
||||||
|
swa スァ
|
||||||
|
swi スィ
|
||||||
|
swu スゥ
|
||||||
|
swe スェ
|
||||||
|
swo スォ
|
||||||
|
z/ ・
|
||||||
|
z. …
|
||||||
|
z, ‥
|
||||||
|
zh ←
|
||||||
|
zj ↓
|
||||||
|
zk ↑
|
||||||
|
zl →
|
||||||
|
z- 〜
|
||||||
|
z[ 『
|
||||||
|
z] 』
|
||||||
|
zya ジャ
|
||||||
|
zyi ジィ
|
||||||
|
zyu ジュ
|
||||||
|
zye ジェ
|
||||||
|
zyo ジョ
|
||||||
|
zwa ズァ
|
||||||
|
zwi ズィ
|
||||||
|
zwu ズゥ
|
||||||
|
zwe ズェ
|
||||||
|
zwo ズォ
|
||||||
|
za ザ
|
||||||
|
zi ジ
|
||||||
|
zu ズ
|
||||||
|
ze ゼ
|
||||||
zo ゾ
|
zo ゾ
|
||||||
ta タ
|
ja ジャ
|
||||||
da ダ
|
ji ジ
|
||||||
ti ティ
|
ju ジュ
|
||||||
chi チ
|
je ジェ
|
||||||
zi ヂ
|
jo ジョ
|
||||||
cha チャ
|
jya ジャ
|
||||||
chu チュ
|
jyi ジィ
|
||||||
|
jyu ジュ
|
||||||
|
jye ジェ
|
||||||
|
jyo ジョ
|
||||||
|
tya チャ
|
||||||
|
tyi チィ
|
||||||
tyu チュ
|
tyu チュ
|
||||||
che チェ
|
tye チェ
|
||||||
cho チョ
|
tyo チョ
|
||||||
|
tsa ツァ
|
||||||
|
tsi ツィ
|
||||||
|
tse ツェ
|
||||||
|
tso ツォ
|
||||||
|
tha テャ
|
||||||
|
thi ティ
|
||||||
|
t'i ティ
|
||||||
|
thu テュ
|
||||||
|
the テェ
|
||||||
|
tho テョ
|
||||||
|
t'yu テュ
|
||||||
|
twa トァ
|
||||||
|
twi トィ
|
||||||
|
twu トゥ
|
||||||
|
twe トェ
|
||||||
|
two トォ
|
||||||
|
t'u トゥ
|
||||||
|
ta タ
|
||||||
|
ti チ
|
||||||
tu ツ
|
tu ツ
|
||||||
tsu ツ
|
tsu ツ
|
||||||
du ヅ
|
|
||||||
te テ
|
te テ
|
||||||
de デ
|
|
||||||
to ト
|
to ト
|
||||||
|
dya ヂャ
|
||||||
|
dyi ヂィ
|
||||||
|
dyu ヂュ
|
||||||
|
dye ヂェ
|
||||||
|
dyo ヂョ
|
||||||
|
dha デャ
|
||||||
|
dhi ディ
|
||||||
|
d'i ディ
|
||||||
|
dhu デュ
|
||||||
|
dhe デェ
|
||||||
|
dho デョ
|
||||||
|
d'yu デュ
|
||||||
|
dwa ドァ
|
||||||
|
dwi ドィ
|
||||||
|
dwu ドゥ
|
||||||
|
dwe ドェ
|
||||||
|
dwo ドォ
|
||||||
|
d'u ドゥ
|
||||||
|
da ダ
|
||||||
|
di ヂ
|
||||||
|
du ヅ
|
||||||
|
de デ
|
||||||
do ド
|
do ド
|
||||||
|
cha チャ
|
||||||
|
chi チ
|
||||||
|
chu チュ
|
||||||
|
che チェ
|
||||||
|
cho チョ
|
||||||
|
cya チャ
|
||||||
|
cyi チィ
|
||||||
|
cyu チュ
|
||||||
|
cye チェ
|
||||||
|
cyo チョ
|
||||||
|
ca カ
|
||||||
|
ci シ
|
||||||
|
cu ク
|
||||||
|
ce セ
|
||||||
|
co コ
|
||||||
|
nya ニャ
|
||||||
|
nyi ニィ
|
||||||
|
nyu ニュ
|
||||||
|
nye ニェ
|
||||||
|
nyo ニョ
|
||||||
|
n' ン
|
||||||
|
nn ン
|
||||||
|
n ン
|
||||||
na ナ
|
na ナ
|
||||||
ni ニ
|
ni ニ
|
||||||
nya ニャ
|
|
||||||
nyu ニュ
|
|
||||||
nyo ニョ
|
|
||||||
nu ヌ
|
nu ヌ
|
||||||
ne ネ
|
ne ネ
|
||||||
no ノ
|
no ノ
|
||||||
ha ハ
|
|
||||||
ba バ
|
|
||||||
pa パ
|
|
||||||
hi ヒ
|
|
||||||
hya ヒャ
|
hya ヒャ
|
||||||
|
hyi ヒィ
|
||||||
hyu ヒュ
|
hyu ヒュ
|
||||||
|
hye ヒェ
|
||||||
hyo ヒョ
|
hyo ヒョ
|
||||||
bi ビ
|
hwa ファ
|
||||||
bya ビャ
|
hwi フィ
|
||||||
byu ビュ
|
hwe フェ
|
||||||
byo ビョ
|
hwo フォ
|
||||||
pi ピ
|
hwyu フュ
|
||||||
pya ピャ
|
ha ハ
|
||||||
pyu ピュ
|
hi ヒ
|
||||||
pyo ピョ
|
|
||||||
hu フ
|
hu フ
|
||||||
fu フ
|
|
||||||
bu ブ
|
|
||||||
pu プ
|
|
||||||
he ヘ
|
he ヘ
|
||||||
be ベ
|
|
||||||
pe ペ
|
|
||||||
ho ホ
|
ho ホ
|
||||||
|
fa ファ
|
||||||
|
fi フィ
|
||||||
|
fu フ
|
||||||
|
fe フェ
|
||||||
|
fo フォ
|
||||||
|
fya フャ
|
||||||
|
fyu フュ
|
||||||
|
fyo フョ
|
||||||
|
bya ビャ
|
||||||
|
byi ビィ
|
||||||
|
byu ビュ
|
||||||
|
bye ビェ
|
||||||
|
byo ビョ
|
||||||
|
ba バ
|
||||||
|
bi ビ
|
||||||
|
bu ブ
|
||||||
|
be ベ
|
||||||
bo ボ
|
bo ボ
|
||||||
|
pya ピャ
|
||||||
|
pyi ピィ
|
||||||
|
pyu ピュ
|
||||||
|
pye ピェ
|
||||||
|
pyo ピョ
|
||||||
|
pa パ
|
||||||
|
pi ピ
|
||||||
|
pu プ
|
||||||
|
pe ペ
|
||||||
po ポ
|
po ポ
|
||||||
|
mya ミャ
|
||||||
|
myi ミィ
|
||||||
|
myu ミュ
|
||||||
|
mye ミェ
|
||||||
|
myo ミョ
|
||||||
ma マ
|
ma マ
|
||||||
mi ミ
|
mi ミ
|
||||||
mya ミャ
|
|
||||||
myu ミュ
|
|
||||||
myo ミョ
|
|
||||||
mu ム
|
mu ム
|
||||||
me メ
|
me メ
|
||||||
mo モ
|
mo モ
|
||||||
|
ye イェ
|
||||||
ya ヤ
|
ya ヤ
|
||||||
yu ユ
|
yu ユ
|
||||||
yo ヨ
|
yo ヨ
|
||||||
|
rya リャ
|
||||||
|
ryi リィ
|
||||||
|
ryu リュ
|
||||||
|
rye リェ
|
||||||
|
ryo リョ
|
||||||
ra ラ
|
ra ラ
|
||||||
ri リ
|
ri リ
|
||||||
rya リャ
|
|
||||||
ryu リュ
|
|
||||||
ryo リョ
|
|
||||||
ru ル
|
ru ル
|
||||||
re レ
|
re レ
|
||||||
ro ロ
|
ro ロ
|
||||||
|
wu ウ
|
||||||
|
wyi ヰ
|
||||||
|
wye ヱ
|
||||||
wa ワ
|
wa ワ
|
||||||
wi ヰ
|
wi ウィ
|
||||||
we ヱ
|
we ウェ
|
||||||
wo ヲ
|
wo ヲ
|
||||||
n ン
|
wha ウァ
|
||||||
xn ン
|
whi ウィ
|
||||||
v ヴ
|
whu ウ
|
||||||
xa ァ
|
whe ウェ
|
||||||
xi ィ
|
who ウォ
|
||||||
xu ゥ
|
|
||||||
xe ェ
|
|
||||||
xo ォ
|
|
||||||
cc ッ
|
|
||||||
dd ッ
|
|
||||||
kk ッ
|
|
||||||
pp ッ
|
|
||||||
tt ッ
|
|
||||||
tch ッ
|
|
||||||
ss ッ
|
|
||||||
gg ッ
|
|
||||||
zz ッ
|
|
||||||
jj ッ
|
|
||||||
bb ッ
|
|
||||||
hh ッ
|
|
||||||
ff ッ
|
|
||||||
mm ッ
|
|
||||||
yy ッ
|
|
||||||
rr ッ
|
|
||||||
ww ッ
|
|
||||||
vv ッ
|
|
||||||
di ディ
|
|
||||||
fa ファ
|
|
||||||
fi フィ
|
|
||||||
fe フェ
|
|
||||||
fo フォ
|
|
||||||
va ヴァ
|
va ヴァ
|
||||||
vi ヴィ
|
vi ヴィ
|
||||||
vu ヴ
|
vu ヴ
|
||||||
ve ヴェ
|
ve ヴェ
|
||||||
vo ヴォ
|
vo ヴォ
|
||||||
|
vya ヴャ
|
||||||
|
vyi ヴィ
|
||||||
|
vyu ヴュ
|
||||||
|
vye ヴェ
|
||||||
|
vyo ヴョ
|
||||||
|
qa クァ
|
||||||
|
qi クィ
|
||||||
|
qu ク
|
||||||
|
qe クェ
|
||||||
|
qo クォ
|
||||||
|
xn ン
|
||||||
|
xa ァ
|
||||||
|
xi ィ
|
||||||
|
xu ゥ
|
||||||
|
xe ェ
|
||||||
|
xo ォ
|
||||||
|
xyi ィ
|
||||||
|
xye ェ
|
||||||
|
xka ヵ
|
||||||
|
xke ヶ
|
||||||
|
xtu ッ
|
||||||
|
xtsu ッ
|
||||||
|
xya ャ
|
||||||
|
xyu ュ
|
||||||
|
xyo ョ
|
||||||
|
xwa ヮ
|
||||||
|
la ァ
|
||||||
|
li ィ
|
||||||
|
lu ゥ
|
||||||
|
le ェ
|
||||||
|
lo ォ
|
||||||
|
lyi ィ
|
||||||
|
lye ェ
|
||||||
|
lka ヵ
|
||||||
|
lke ヶ
|
||||||
|
ltu ッ
|
||||||
|
ltsu ッ
|
||||||
|
lya ャ
|
||||||
|
lyu ュ
|
||||||
|
lyo ョ
|
||||||
|
lwa ヮ
|
||||||
|
qq ッ
|
||||||
|
vv ッ
|
||||||
|
ll ッ
|
||||||
|
xx ッ
|
||||||
|
kk ッ
|
||||||
|
gg ッ
|
||||||
|
ss ッ
|
||||||
|
zz ッ
|
||||||
|
jj ッ
|
||||||
|
tt ッ
|
||||||
|
tch ッ
|
||||||
|
dd ッ
|
||||||
|
hh ッ
|
||||||
|
ff ッ
|
||||||
|
bb ッ
|
||||||
|
pp ッ
|
||||||
|
mm ッ
|
||||||
|
yy ッ
|
||||||
|
rr ッ
|
||||||
|
ww ッ
|
||||||
|
cc ッ
|
||||||
|
- ー
|
||||||
|
~ 〜
|
||||||
. 。
|
. 。
|
||||||
, 、
|
, 、
|
||||||
|
[ 「
|
||||||
|
] 」
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Extract single-character Hanja readings from libhangul data."""
|
"""Extract Hanja readings from libhangul data."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def ishangul(s):
|
def ishangul(s):
|
||||||
return len(s) == 1 and 0xAC00 <= ord(s) <= 0xD7A3
|
return s != "" and all(0xAC00 <= ord(c) <= 0xD7A3 for c in s)
|
||||||
|
|
||||||
|
|
||||||
def ishanja(s):
|
def ishanja(s):
|
||||||
if len(s) != 1:
|
return s != "" and all(0x3400 <= ord(c) <= 0x4DBF
|
||||||
return False
|
or 0x4E00 <= ord(c) <= 0x9FFF
|
||||||
c = ord(s)
|
or 0xF900 <= ord(c) <= 0xFAFF for c in s)
|
||||||
return (0x3400 <= c <= 0x4DBF
|
|
||||||
or 0x4E00 <= c <= 0x9FFF
|
|
||||||
or 0xF900 <= c <= 0xFAFF)
|
|
||||||
|
|
||||||
|
|
||||||
def extract(src, name):
|
def extract(src, name):
|
||||||
@@ -44,7 +41,7 @@ def extract(src, name):
|
|||||||
seen.add(pair)
|
seen.add(pair)
|
||||||
entries.append(pair)
|
entries.append(pair)
|
||||||
if not entries:
|
if not entries:
|
||||||
raise ValueError(f"{name}: no single-character Hanja readings")
|
raise ValueError(f"{name}: no Hanja readings")
|
||||||
return comments, entries
|
return comments, entries
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
50
map/mkemoji
50
map/mkemoji
@@ -1,13 +1,19 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Write emoji.dict to stdout from a result-first UTF-8 TSV file."""
|
"""Write emoji.dict to stdout from result-first UTF-8 TSV files."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
SOURCE = Path(__file__).with_name("emoji.src")
|
SOURCES = [Path(__file__).with_name(name)
|
||||||
|
for name in ("symbol.src", "emoji.src")]
|
||||||
MAXRUNES = 64
|
MAXRUNES = 64
|
||||||
|
MAXCANDIDATES = 128
|
||||||
|
|
||||||
|
|
||||||
|
HIRA = {c: c + 0x60 for c in range(0x3041, 0x3097)}
|
||||||
|
KATA = {c: c - 0x60 for c in range(0x30A1, 0x30F7)}
|
||||||
|
|
||||||
|
|
||||||
def fold(s):
|
def fold(s):
|
||||||
@@ -15,6 +21,12 @@ def fold(s):
|
|||||||
return unicodedata.normalize("NFC", s)
|
return unicodedata.normalize("NFC", s)
|
||||||
|
|
||||||
|
|
||||||
|
def kana(alias):
|
||||||
|
"""The alias, and in the other kana where it has any: a query typed
|
||||||
|
in either Japanese mode finds it."""
|
||||||
|
return {alias, alias.translate(HIRA), alias.translate(KATA)}
|
||||||
|
|
||||||
|
|
||||||
def hascontrol(s):
|
def hascontrol(s):
|
||||||
return any(unicodedata.category(c) == "Cc" for c in s)
|
return any(unicodedata.category(c) == "Cc" for c in s)
|
||||||
|
|
||||||
@@ -40,35 +52,29 @@ def read(path):
|
|||||||
if (not alias or len(alias) > MAXRUNES or hascontrol(alias)
|
if (not alias or len(alias) > MAXRUNES or hascontrol(alias)
|
||||||
or alias != alias.strip() or alias.startswith(";")):
|
or alias != alias.strip() or alias.startswith(";")):
|
||||||
raise ValueError(f"{path}:{lineno}: bad alias")
|
raise ValueError(f"{path}:{lineno}: bad alias")
|
||||||
entries.append((result, alias))
|
entries.extend((result, a) for a in sorted(kana(alias)))
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
|
|
||||||
def add(table, key, result):
|
|
||||||
values = table.setdefault(key, [])
|
|
||||||
if result not in values:
|
|
||||||
values.append(result)
|
|
||||||
|
|
||||||
|
|
||||||
def build(entries):
|
def build(entries):
|
||||||
exact = {}
|
"""One row per alias, in order of first appearance; the engine searches
|
||||||
prefix = {}
|
the dictionary by prefix, so no prefix rows are needed."""
|
||||||
|
table = {}
|
||||||
for result, alias in entries:
|
for result, alias in entries:
|
||||||
for n in range(1, len(alias) + 1):
|
values = table.setdefault(alias, [])
|
||||||
add(exact if n == len(alias) else prefix, alias[:n], result)
|
if result not in values:
|
||||||
for key in sorted(exact.keys() | prefix.keys()):
|
values.append(result)
|
||||||
values = exact.get(key, []) + prefix.get(key, [])
|
for alias, values in table.items():
|
||||||
values = list(dict.fromkeys(values))
|
yield f"{alias}\t{' '.join(values[:MAXCANDIDATES])}"
|
||||||
yield f"{key}\t{' '.join(values)}"
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) > 2:
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
print(f"usage: {sys.argv[0]} [emoji.src]", file=sys.stderr)
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
return 2
|
paths = [Path(arg) for arg in sys.argv[1:]] or SOURCES
|
||||||
path = Path(sys.argv[1]) if len(sys.argv) == 2 else SOURCE
|
|
||||||
try:
|
try:
|
||||||
for line in build(read(path)):
|
entries = [entry for path in paths for entry in read(path)]
|
||||||
|
for line in build(entries):
|
||||||
print(line)
|
print(line)
|
||||||
except (OSError, UnicodeError, ValueError) as error:
|
except (OSError, UnicodeError, ValueError) as error:
|
||||||
print(error, file=sys.stderr)
|
print(error, file=sys.stderr)
|
||||||
|
|||||||
17
map/mkhanja
17
map/mkhanja
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Write hanja.dict from a one-Hanja-per-row UTF-8 source."""
|
"""Write hanja.dict from a Hanja-per-row UTF-8 source."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -10,16 +10,13 @@ MAXCANDIDATES = 128
|
|||||||
|
|
||||||
|
|
||||||
def ishangul(s):
|
def ishangul(s):
|
||||||
return len(s) == 1 and 0xAC00 <= ord(s) <= 0xD7A3
|
return s != "" and all(0xAC00 <= ord(c) <= 0xD7A3 for c in s)
|
||||||
|
|
||||||
|
|
||||||
def ishanja(s):
|
def ishanja(s):
|
||||||
if len(s) != 1:
|
return s != "" and all(0x3400 <= ord(c) <= 0x4DBF
|
||||||
return False
|
or 0x4E00 <= ord(c) <= 0x9FFF
|
||||||
c = ord(s)
|
or 0xF900 <= ord(c) <= 0xFAFF for c in s)
|
||||||
return (0x3400 <= c <= 0x4DBF
|
|
||||||
or 0x4E00 <= c <= 0x9FFF
|
|
||||||
or 0xF900 <= c <= 0xFAFF)
|
|
||||||
|
|
||||||
|
|
||||||
def read(path):
|
def read(path):
|
||||||
@@ -42,9 +39,9 @@ def read(path):
|
|||||||
raise ValueError(f"{path}:{lineno}: need Hanja<TAB>reading")
|
raise ValueError(f"{path}:{lineno}: need Hanja<TAB>reading")
|
||||||
hanja, reading = fields
|
hanja, reading = fields
|
||||||
if not ishanja(hanja):
|
if not ishanja(hanja):
|
||||||
raise ValueError(f"{path}:{lineno}: need one BMP Hanja character")
|
raise ValueError(f"{path}:{lineno}: need BMP Hanja")
|
||||||
if not ishangul(reading):
|
if not ishangul(reading):
|
||||||
raise ValueError(f"{path}:{lineno}: need one Hangul syllable")
|
raise ValueError(f"{path}:{lineno}: need Hangul syllables")
|
||||||
pair = (hanja, reading)
|
pair = (hanja, reading)
|
||||||
if pair in seen:
|
if pair in seen:
|
||||||
raise ValueError(f"{path}:{lineno}: duplicate Hanja reading")
|
raise ValueError(f"{path}:{lineno}: duplicate Hanja reading")
|
||||||
|
|||||||
@@ -36,10 +36,16 @@ function add(k, v, id) {
|
|||||||
row[k] = row[k] " "
|
row[k] = row[k] " "
|
||||||
row[k] = row[k] v
|
row[k] = row[k] v
|
||||||
}
|
}
|
||||||
/^;;/ || /^[[:space:]]*$/ {
|
/^;;/ {
|
||||||
|
if(!body) # the header, with its license notice, is kept
|
||||||
|
print
|
||||||
|
next
|
||||||
|
}
|
||||||
|
/^[[:space:]]*$/ {
|
||||||
next
|
next
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
body = 1
|
||||||
if(!match($0, /[[:space:]]+/))
|
if(!match($0, /[[:space:]]+/))
|
||||||
fail("missing candidate list")
|
fail("missing candidate list")
|
||||||
key = substr($0, 1, RSTART-1)
|
key = substr($0, 1, RSTART-1)
|
||||||
|
|||||||
103
map/symbol.src
Normal file
103
map/symbol.src
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# Result first, followed by one or more TAB-separated aliases.
|
||||||
|
⚠ !!
|
||||||
|
★ **
|
||||||
|
± +-
|
||||||
|
→ ->
|
||||||
|
· ..
|
||||||
|
… ...
|
||||||
|
÷ ./
|
||||||
|
☹ :(
|
||||||
|
☺ :)
|
||||||
|
|
||||||
|
# Bare 1-9 choose from a prefix search; keep digit aliases in matching slots.
|
||||||
|
← <-
|
||||||
|
≤ <=
|
||||||
|
♥ <3
|
||||||
|
≠ <> !=
|
||||||
|
≡ ==
|
||||||
|
⇒ =>
|
||||||
|
≥ >=
|
||||||
|
¹ ^1
|
||||||
|
² ^2
|
||||||
|
³ ^3
|
||||||
|
⁴ ^4
|
||||||
|
⁵ ^5
|
||||||
|
⁶ ^6
|
||||||
|
⁷ ^7
|
||||||
|
⁸ ^8
|
||||||
|
⁹ ^9
|
||||||
|
₁ _1
|
||||||
|
₂ _2
|
||||||
|
₃ _3
|
||||||
|
₄ _4
|
||||||
|
₅ _5
|
||||||
|
₆ _6
|
||||||
|
₇ _7
|
||||||
|
₈ _8
|
||||||
|
₉ _9
|
||||||
|
|
||||||
|
⁽ ^(
|
||||||
|
⁾ ^)
|
||||||
|
⁺ ^+
|
||||||
|
⁻ ^-
|
||||||
|
⁼ ^=
|
||||||
|
₍ _(
|
||||||
|
₎ _)
|
||||||
|
₊ _+
|
||||||
|
₋ _-
|
||||||
|
₌ _=
|
||||||
|
≈ ~=
|
||||||
|
⁰ ^0
|
||||||
|
₀ _0
|
||||||
|
ₐ _a
|
||||||
|
α alpha
|
||||||
|
β beta
|
||||||
|
χ chi
|
||||||
|
° deg degree
|
||||||
|
δ delta
|
||||||
|
Δ De Delta
|
||||||
|
↓ dn down
|
||||||
|
ₑ _e
|
||||||
|
ε eps
|
||||||
|
η eta
|
||||||
|
凸 fuck
|
||||||
|
γ gamma
|
||||||
|
Γ Ga Gamma
|
||||||
|
ⁱ ^i
|
||||||
|
∫ II integral
|
||||||
|
∞ inf infinity
|
||||||
|
ι iota
|
||||||
|
κ kappa
|
||||||
|
λ lambda
|
||||||
|
Λ La Lambda
|
||||||
|
× mul times
|
||||||
|
μ mu
|
||||||
|
ⁿ ^n
|
||||||
|
ν nu
|
||||||
|
ω omega
|
||||||
|
Ω Om Omega
|
||||||
|
● oo circle
|
||||||
|
ₒ _o
|
||||||
|
φ phi
|
||||||
|
Φ Ph Phi
|
||||||
|
π pi
|
||||||
|
Π Pi
|
||||||
|
∏ PP prod
|
||||||
|
ψ psi
|
||||||
|
Ψ Ps Psi
|
||||||
|
ρ rho
|
||||||
|
σ sigma
|
||||||
|
Σ Si Sigma
|
||||||
|
√ sq sqrt
|
||||||
|
∑ SS sum
|
||||||
|
τ tau
|
||||||
|
θ theta
|
||||||
|
Θ Th Theta
|
||||||
|
↑ up
|
||||||
|
υ ups
|
||||||
|
✓ vv check
|
||||||
|
ξ xi
|
||||||
|
Ξ Xi
|
||||||
|
✗ xx cross
|
||||||
|
ₓ _x
|
||||||
|
ζ zeta
|
||||||
@@ -119,7 +119,7 @@ void
|
|||||||
popuplayout(Drawcmd *dc, int areaw, int areah, Popup *p)
|
popuplayout(Drawcmd *dc, int areaw, int areah, Popup *p)
|
||||||
{
|
{
|
||||||
char buf[32];
|
char buf[32];
|
||||||
int first, i, markrow, nall, nmark, npre, pad, total, width, y;
|
int first, i, markrow, nall, nmark, npre, total, width, y;
|
||||||
|
|
||||||
memset(p, 0, sizeof *p);
|
memset(p, 0, sizeof *p);
|
||||||
p->prey = -1;
|
p->prey = -1;
|
||||||
@@ -127,14 +127,12 @@ popuplayout(Drawcmd *dc, int areaw, int areah, Popup *p)
|
|||||||
p->rowsy = -1;
|
p->rowsy = -1;
|
||||||
p->marky = -1;
|
p->marky = -1;
|
||||||
p->sely = -1;
|
p->sely = -1;
|
||||||
pad = PopupPad;
|
|
||||||
nall = min(max(dc->nkouho, 0), Maxdisp);
|
nall = min(max(dc->nkouho, 0), Maxdisp);
|
||||||
npre = dc->pre.n != 0;
|
npre = dc->pre.n != 0;
|
||||||
if((nall == 0 && !npre) || areaw <= 0 || areah <= 0)
|
if((nall == 0 && !npre) || areaw <= 0 || areah <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Rows first; a page marker when they do not all fit; and in a
|
/* As many rows as fit, and a page marker when they do not all. */
|
||||||
* sliver of a work area the selected row alone, unpadded. */
|
|
||||||
total = max(dc->total, dc->first + nall);
|
total = max(dc->total, dc->first + nall);
|
||||||
markrow = dc->first > 0 || nall < total;
|
markrow = dc->first > 0 || nall < total;
|
||||||
p->n = fitrows(areah, npre, markrow, nall);
|
p->n = fitrows(areah, npre, markrow, nall);
|
||||||
@@ -142,15 +140,8 @@ popuplayout(Drawcmd *dc, int areaw, int areah, Popup *p)
|
|||||||
markrow = 1;
|
markrow = 1;
|
||||||
p->n = fitrows(areah, npre, markrow, nall);
|
p->n = fitrows(areah, npre, markrow, nall);
|
||||||
}
|
}
|
||||||
if(p->n == 0 && markrow){
|
if(p->n == 0 && !npre)
|
||||||
markrow = 0;
|
return;
|
||||||
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)
|
if(p->n != 0 && dc->sel >= p->n)
|
||||||
p->row0 = min(dc->sel - p->n + 1, nall - p->n);
|
p->row0 = min(dc->sel - p->n + 1, nall - p->n);
|
||||||
first = dc->first + p->row0;
|
first = dc->first + p->row0;
|
||||||
@@ -158,7 +149,8 @@ popuplayout(Drawcmd *dc, int areaw, int areah, Popup *p)
|
|||||||
if(nmark == 0)
|
if(nmark == 0)
|
||||||
markrow = 0;
|
markrow = 0;
|
||||||
|
|
||||||
width = PopupBasew;
|
/* Rows share one steady width; a preedit alone hugs its text. */
|
||||||
|
width = p->n != 0 ? PopupBasew : 2*PopupPad;
|
||||||
if(npre)
|
if(npre)
|
||||||
width = max(width, textwidth(&dc->pre) + 2*PopupPad);
|
width = max(width, textwidth(&dc->pre) + 2*PopupPad);
|
||||||
for(i = 0; i < p->n; i++)
|
for(i = 0; i < p->n; i++)
|
||||||
@@ -171,7 +163,7 @@ popuplayout(Drawcmd *dc, int areaw, int areah, Popup *p)
|
|||||||
}
|
}
|
||||||
p->w = min(width, areaw);
|
p->w = min(width, areaw);
|
||||||
|
|
||||||
y = pad;
|
y = PopupPad;
|
||||||
if(npre){
|
if(npre){
|
||||||
p->prey = y;
|
p->prey = y;
|
||||||
y += Fontsz;
|
y += Fontsz;
|
||||||
@@ -188,7 +180,7 @@ popuplayout(Drawcmd *dc, int areaw, int areah, Popup *p)
|
|||||||
p->marky = y;
|
p->marky = y;
|
||||||
y += Fontsz;
|
y += Fontsz;
|
||||||
}
|
}
|
||||||
p->h = min(y + pad, areah);
|
p->h = min(y + PopupPad, areah);
|
||||||
p->textw = max(p->w - 2*PopupPad - PopupNumw, 0);
|
p->textw = max(p->w - 2*PopupPad - PopupNumw, 0);
|
||||||
if(markrow){
|
if(markrow){
|
||||||
p->markw = min(p->markw, max(p->w - 2*PopupPad, 0));
|
p->markw = min(p->markw, max(p->w - 2*PopupPad, 0));
|
||||||
@@ -206,10 +198,6 @@ popupdraw(u32int *img, Drawcmd *dc, Popup *p)
|
|||||||
u32int color;
|
u32int color;
|
||||||
int i, j, y;
|
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);
|
fill(img, p->w*p->h, Colbg);
|
||||||
if(p->prey >= 0)
|
if(p->prey >= 0)
|
||||||
textdraw(img, p->w, p->h, PopupPad, p->prey,
|
textdraw(img, p->w, p->h, PopupPad, p->prey,
|
||||||
|
|||||||
6
srv.c
6
srv.c
@@ -67,7 +67,7 @@ srvreadreq(int fd, Keyreq *kr, int *want)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* One Keyreq persists for the connection: the negotiated capability and
|
* One Keyreq persists for the connection: the negotiated preedit and
|
||||||
* the last caret ride along with every request, and the socket closing
|
* the last caret ride along with every request, and the socket closing
|
||||||
* releases the engine.
|
* releases the engine.
|
||||||
*/
|
*/
|
||||||
@@ -85,10 +85,10 @@ clientthread(void *arg)
|
|||||||
memset(&kr, 0, sizeof kr);
|
memset(&kr, 0, sizeof kr);
|
||||||
kr.reply = chancreate(sizeof(Keyres), 0);
|
kr.reply = chancreate(sizeof(Keyres), 0);
|
||||||
kr.owner = &fd;
|
kr.owner = &fd;
|
||||||
kr.cap = Cclientpreedit;
|
kr.clientpre = 1;
|
||||||
while(srvreadreq(fd, &kr, &want) >= 0){
|
while(srvreadreq(fd, &kr, &want) >= 0){
|
||||||
if(kr.op != Keycaret)
|
if(kr.op != Keycaret)
|
||||||
kr.cap = want ? Cclientpreedit : 0;
|
kr.clientpre = want;
|
||||||
chansend(keyc, &kr);
|
chansend(keyc, &kr);
|
||||||
chanrecv(kr.reply, &res);
|
chanrecv(kr.reply, &res);
|
||||||
if(kr.op == Keycaret)
|
if(kr.op == Keycaret)
|
||||||
|
|||||||
550
strans.c
550
strans.c
@@ -3,9 +3,12 @@
|
|||||||
|
|
||||||
static Im im;
|
static Im im;
|
||||||
static void *activeowner;
|
static void *activeowner;
|
||||||
static int activecap;
|
static void *lostowner; /* the context the engine was taken from */
|
||||||
|
static Str lost;
|
||||||
|
static int clientpre; /* the owner draws its own preedit */
|
||||||
static Caret caret;
|
static Caret caret;
|
||||||
static int candidatechosen;
|
static Rune modemark; /* the mode the last key switched to */
|
||||||
|
static int okuriat = -1; /* where Shift marked the okurigana, or -1 */
|
||||||
static Drawcmd lastdraw;
|
static Drawcmd lastdraw;
|
||||||
static Emit transjp(Im*, Rune);
|
static Emit transjp(Im*, Rune);
|
||||||
static void backjp(Im*);
|
static void backjp(Im*);
|
||||||
@@ -54,12 +57,12 @@ mapget(Trie *t, Str *key, Str *out)
|
|||||||
return mapmatch(t, key, out) == TrieExact;
|
return mapmatch(t, key, out) == TrieExact;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* sel is the chosen candidate; -1 while the user has not moved to one. */
|
||||||
static void
|
static void
|
||||||
clearkouho(void)
|
clearkouho(void)
|
||||||
{
|
{
|
||||||
im.nkouho = 0;
|
im.nkouho = 0;
|
||||||
im.sel = -1;
|
im.sel = -1;
|
||||||
candidatechosen = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -99,7 +102,28 @@ movekouho(int delta)
|
|||||||
im.sel = 0;
|
im.sel = 0;
|
||||||
if(im.sel >= im.nkouho)
|
if(im.sel >= im.nkouho)
|
||||||
im.sel = im.nkouho - 1;
|
im.sel = im.nkouho - 1;
|
||||||
candidatechosen = 1;
|
}
|
||||||
|
|
||||||
|
/* Space and Tab step through the candidates and wrap around. */
|
||||||
|
static void
|
||||||
|
cyclekouho(int delta)
|
||||||
|
{
|
||||||
|
if(im.sel < 0)
|
||||||
|
im.sel = delta > 0 ? 0 : im.nkouho - 1;
|
||||||
|
else
|
||||||
|
im.sel = (im.sel + delta + im.nkouho) % im.nkouho;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
addkouho(Str *s)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < im.nkouho; i++)
|
||||||
|
if(scmp(&im.kouho[i], s) == 0)
|
||||||
|
return;
|
||||||
|
if(im.nkouho < Maxkouho)
|
||||||
|
im.kouho[im.nkouho++] = *s;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -122,10 +146,11 @@ isjp(Im *p)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Japanese keeps completed kana in pre and an ambiguous romaji syllable in
|
* Japanese keeps completed kana, and any romaji that never became kana,
|
||||||
* raw. A mapped raw suffix is part of the reading even while it remains
|
* in pre, and the romaji still being typed in raw. A mapped raw suffix
|
||||||
* pending (notably n, which may still grow into nya). Returns whether
|
* is part of the reading even while it remains pending (notably n,
|
||||||
* the reading is complete.
|
* which may still grow into nya). Returns whether the reading is
|
||||||
|
* complete.
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
jpreading(Trie *map, Str *pre, Str *raw, Str *s)
|
jpreading(Trie *map, Str *pre, Str *raw, Str *s)
|
||||||
@@ -170,8 +195,12 @@ snapshot(Drawcmd *dc)
|
|||||||
pre = search.text;
|
pre = search.text;
|
||||||
else
|
else
|
||||||
impre(&im, &pre);
|
impre(&im, &pre);
|
||||||
if(!(activecap & Cclientpreedit))
|
if(!clientpre)
|
||||||
dc->pre = pre;
|
dc->pre = pre;
|
||||||
|
if(search.lang && pre.n == 0) /* a search just begun shows itself */
|
||||||
|
sputr(&dc->pre, search.lang == LangEMOJI ? L'☺' : L'漢');
|
||||||
|
else if(modemark != 0 && pre.n == 0 && im.nkouho == 0)
|
||||||
|
sputr(&dc->pre, modemark);
|
||||||
first = pagefirst();
|
first = pagefirst();
|
||||||
n = im.nkouho - first;
|
n = im.nkouho - first;
|
||||||
if(n > Maxdisp) n = Maxdisp;
|
if(n > Maxdisp) n = Maxdisp;
|
||||||
@@ -230,17 +259,107 @@ redraw(void)
|
|||||||
channbsend(drawc, &dc);
|
channbsend(drawc, &dc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Kanji candidates for a complete reading. */
|
/* SKK's letter for the okurigana a kana begins: its row, or its vowel. */
|
||||||
|
static Rune
|
||||||
|
okuriletter(Rune r)
|
||||||
|
{
|
||||||
|
static char *rows[] = {
|
||||||
|
"aあ", "iい", "uう", "eえ", "oお",
|
||||||
|
"kかきくけこ", "gがぎぐげご", "sさしすせそ", "zざじずぜぞ",
|
||||||
|
"tたちつてとっ", "dだぢづでど", "nなにぬねのん", "hはひふへほ",
|
||||||
|
"bばびぶべぼ", "pぱぴぷぺぽ", "mまみむめも", "yやゆよ",
|
||||||
|
"rらりるれろ", "wわ",
|
||||||
|
};
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < nelem(rows); i++)
|
||||||
|
if(utfrune(rows[i]+1, r) != nil)
|
||||||
|
return rows[i][0];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SKK keys a verb or adjective by its stem and the letter of the
|
||||||
|
* okurigana that follows (かk: 書, 描, ...). The split the user marked
|
||||||
|
* is tried alone; otherwise every split is, longest stem first. The
|
||||||
|
* okurigana is put back, and a っ ending the stem (いっt: 言, 行) is
|
||||||
|
* written in kana too.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
dictqokuri(Str *reading, int split)
|
||||||
|
{
|
||||||
|
Str key, okuri, kouho[Maxkouho];
|
||||||
|
Rune c;
|
||||||
|
int i, j, n;
|
||||||
|
|
||||||
|
for(i = split >= 0 ? split : reading->n - 1; i > 0; i--){
|
||||||
|
c = okuriletter(reading->r[i]);
|
||||||
|
if(c == 0)
|
||||||
|
break;
|
||||||
|
key = *reading;
|
||||||
|
key.n = i;
|
||||||
|
sputr(&key, c);
|
||||||
|
n = dictlookup(im.l->dict, &key, kouho, Maxkouho);
|
||||||
|
sclear(&okuri);
|
||||||
|
for(j = i - (reading->r[i-1] == L'っ'); j < reading->n; j++)
|
||||||
|
sputr(&okuri, reading->r[j]);
|
||||||
|
for(j = 0; j < n; j++){
|
||||||
|
sappend(&kouho[j], &okuri);
|
||||||
|
addkouho(&kouho[j]);
|
||||||
|
}
|
||||||
|
if(split >= 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Kanji candidates for a complete Japanese reading; none is chosen yet. */
|
||||||
static void
|
static void
|
||||||
dictqjp(void)
|
dictqjp(void)
|
||||||
{
|
{
|
||||||
Str reading;
|
Str reading, exact[Maxkouho];
|
||||||
|
int i, n;
|
||||||
|
|
||||||
clearkouho();
|
clearkouho();
|
||||||
if(!jpreading(im.l->map, &im.pre, &im.raw, &reading) || reading.n == 0)
|
if(!isjp(&im) || !jpreading(im.l->map, &im.pre, &im.raw, &reading) ||
|
||||||
|
reading.n == 0)
|
||||||
return;
|
return;
|
||||||
im.nkouho = dictlookup(im.l, &reading, im.kouho, Maxkouho);
|
/* What Shift marked comes first; the dictionary's own readings next. */
|
||||||
selectfirst();
|
if(okuriat > 0 && okuriat < reading.n)
|
||||||
|
dictqokuri(&reading, okuriat);
|
||||||
|
n = dictlookup(im.l->dict, &reading, exact, Maxkouho);
|
||||||
|
for(i = 0; i < n; i++)
|
||||||
|
addkouho(&exact[i]);
|
||||||
|
dictqokuri(&reading, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The reading in Katakana is the last candidate, as in every IME. */
|
||||||
|
static void
|
||||||
|
katakouho(void)
|
||||||
|
{
|
||||||
|
Str reading, k;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if(!jpreading(im.l->map, &im.pre, &im.raw, &reading))
|
||||||
|
return;
|
||||||
|
k = reading;
|
||||||
|
for(i = 0; i < k.n; i++)
|
||||||
|
if(k.r[i] >= L'ぁ' && k.r[i] <= L'ゖ')
|
||||||
|
k.r[i] += L'ァ' - L'ぁ';
|
||||||
|
if(scmp(&k, &reading) != 0)
|
||||||
|
addkouho(&k);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* What the popup shows for a moment when the mode changes. */
|
||||||
|
static Rune
|
||||||
|
langmark(int lang)
|
||||||
|
{
|
||||||
|
switch(lang){
|
||||||
|
case LangJP: return L'あ';
|
||||||
|
case LangJPK: return L'ア';
|
||||||
|
case LangKO: return L'한';
|
||||||
|
case LangVI: return L'ă';
|
||||||
|
}
|
||||||
|
return L'A';
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -251,6 +370,8 @@ setlang(int c)
|
|||||||
l = getlang(c);
|
l = getlang(c);
|
||||||
if(l == nil)
|
if(l == nil)
|
||||||
return 0;
|
return 0;
|
||||||
|
if(l != im.l)
|
||||||
|
modemark = langmark(l->lang);
|
||||||
im.l = l;
|
im.l = l;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -260,34 +381,37 @@ commitim(Im *p, Str *com)
|
|||||||
{
|
{
|
||||||
Str val;
|
Str val;
|
||||||
|
|
||||||
if(isjp(p))
|
impre(p, &val);
|
||||||
jpreading(p->l->map, &p->pre, &p->raw, &val);
|
|
||||||
else if(!mapget(p->l->map, &p->pre, &val))
|
|
||||||
val = p->pre;
|
|
||||||
sappend(com, &val);
|
sappend(com, &val);
|
||||||
sclear(&p->pre);
|
sclear(&p->pre);
|
||||||
sclear(&p->raw);
|
sclear(&p->raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
/* Deletes the last kana as shown, not the last key: kya loses ゃ. */
|
||||||
commit(Str *com)
|
|
||||||
{
|
|
||||||
commitim(&im, com);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
backjp(Im *p)
|
backjp(Im *p)
|
||||||
{
|
{
|
||||||
if(p->raw.n != 0)
|
Str v;
|
||||||
spopr(&p->raw);
|
|
||||||
else
|
if(p->raw.n == 0)
|
||||||
spopr(&p->pre);
|
spopr(&p->pre);
|
||||||
|
else if(mapget(p->l->map, &p->raw, &v)){
|
||||||
|
spopr(&v);
|
||||||
|
sappend(&p->pre, &v);
|
||||||
|
sclear(&p->raw);
|
||||||
|
}else
|
||||||
|
spopr(&p->raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* kk and the like are a sokuon: っ, and the consonant stays pending, as
|
||||||
|
* ch does after tch. xtu and ltu are っ itself.
|
||||||
|
*/
|
||||||
static int
|
static int
|
||||||
issmalltsu(Str *s)
|
issokuon(Str *key, Str *mapped)
|
||||||
{
|
{
|
||||||
return s->n == 1 && (s->r[0] == L'っ' || s->r[0] == L'ッ');
|
return mapped->n == 1 && (mapped->r[0] == L'っ' || mapped->r[0] == L'ッ') &&
|
||||||
|
(key->r[0] == key->r[1] || (key->r[0] == 't' && key->r[1] == 'c'));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -311,36 +435,31 @@ transjp(Im *p, Rune c)
|
|||||||
e.eat = 1;
|
e.eat = 1;
|
||||||
e.next = p->pre;
|
e.next = p->pre;
|
||||||
e.raw = p->raw;
|
e.raw = p->raw;
|
||||||
if(c == '\'' && e.raw.n == 1 && e.raw.r[0] == 'n'){
|
|
||||||
if(mapget(p->l->map, &e.raw, &mapped))
|
|
||||||
sappend(&e.next, &mapped);
|
|
||||||
sclear(&e.raw);
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
key = e.raw;
|
key = e.raw;
|
||||||
sputr(&key, c);
|
sputr(&key, c);
|
||||||
if(mapmatch(p->l->map, &key, &mapped) != TrieMiss){
|
if(mapmatch(p->l->map, &key, &mapped) != TrieMiss){
|
||||||
e.raw = key;
|
e.raw = key;
|
||||||
if(issmalltsu(&mapped)){
|
if(issokuon(&key, &mapped)){
|
||||||
sappend(&e.next, &mapped);
|
sappend(&e.next, &mapped);
|
||||||
keeptail(&e.raw);
|
keeptail(&e.raw);
|
||||||
}
|
}
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
if(e.raw.n != 0 && mapget(p->l->map, &e.raw, &mapped)){
|
/* The pending romaji is done: kana if it is complete, else the
|
||||||
sappend(&e.next, &mapped);
|
* letters as typed, for Backspace to mend. */
|
||||||
sclear(&e.raw);
|
if(!mapget(p->l->map, &e.raw, &mapped))
|
||||||
sclear(&key);
|
mapped = e.raw;
|
||||||
sputr(&key, c);
|
sappend(&e.next, &mapped);
|
||||||
if(mapmatch(p->l->map, &key, &mapped) != TrieMiss){
|
sclear(&e.raw);
|
||||||
e.raw = key;
|
sclear(&key);
|
||||||
return e;
|
sputr(&key, c);
|
||||||
}
|
if(mapmatch(p->l->map, &key, &mapped) != TrieMiss){
|
||||||
|
e.raw = key;
|
||||||
|
return e;
|
||||||
}
|
}
|
||||||
/* The key starts no syllable: flush the reading and pass it on. */
|
/* The key starts no syllable: flush the reading and pass it on. */
|
||||||
jpreading(p->l->map, &e.next, &e.raw, &e.s);
|
e.s = e.next;
|
||||||
sclear(&e.next);
|
sclear(&e.next);
|
||||||
sclear(&e.raw);
|
|
||||||
e.eat = 0;
|
e.eat = 0;
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -354,10 +473,7 @@ dotrans(Rune c, Str *com)
|
|||||||
sappend(com, &e.s);
|
sappend(com, &e.s);
|
||||||
im.pre = e.next;
|
im.pre = e.next;
|
||||||
im.raw = e.raw;
|
im.raw = e.raw;
|
||||||
if(e.eat && isjp(&im))
|
dictqjp();
|
||||||
dictqjp();
|
|
||||||
else
|
|
||||||
clearkouho();
|
|
||||||
return e.eat;
|
return e.eat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,37 +518,39 @@ transmap(Im *p, Rune c)
|
|||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ASCII only: what the maps and dictionaries fold too. */
|
||||||
static Rune
|
static Rune
|
||||||
ctrlkey(Rune c)
|
lower(Rune c)
|
||||||
{
|
{
|
||||||
if(c >= 'A' && c <= 'Z')
|
if(c >= 'A' && c <= 'Z')
|
||||||
c += 'a' - 'A';
|
c += 'a' - 'A';
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rune
|
||||||
|
ctrlkey(Rune c)
|
||||||
|
{
|
||||||
|
c = lower(c);
|
||||||
if(c >= 'a' && c <= 'z')
|
if(c >= 'a' && c <= 'z')
|
||||||
return c - 'a' + 1;
|
return c - 'a' + 1;
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Caps Lock does not shift a Korean key; Shift does. */
|
||||||
static Rune
|
static Rune
|
||||||
kokey(Rune c, u32int mod)
|
kokey(Rune c, u32int mod)
|
||||||
{
|
{
|
||||||
if(c >= 'A' && c <= 'Z')
|
c = lower(c);
|
||||||
c += 'a' - 'A';
|
|
||||||
if((mod & Mshift) && c >= 'a' && c <= 'z')
|
if((mod & Mshift) && c >= 'a' && c <= 'z')
|
||||||
c -= 'a' - 'A';
|
c -= 'a' - 'A';
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
/* A modifier press or an unmapped key is key 0: it engages nothing. */
|
||||||
ismodkey(u32int ks)
|
|
||||||
{
|
|
||||||
return ks >= Kmodfirst && ks <= Kmodlast;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Modifier presses and unmapped keys never engage the engine. */
|
|
||||||
int
|
int
|
||||||
keymeaningful(u32int ks)
|
keymeaningful(u32int ks)
|
||||||
{
|
{
|
||||||
return ks != 0 && !ismodkey(ks);
|
return ks != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -441,25 +559,30 @@ foldascii(Str *s)
|
|||||||
int i;
|
int i;
|
||||||
|
|
||||||
for(i = 0; i < s->n; i++)
|
for(i = 0; i < s->n; i++)
|
||||||
if(s->r[i] >= 'A' && s->r[i] <= 'Z')
|
s->r[i] = lower(s->r[i]);
|
||||||
s->r[i] += 'a' - 'A';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* What typing raw in language l would produce, composition included. */
|
/*
|
||||||
|
* What typing raw in language l would produce, composition included,
|
||||||
|
* after pre, which may be nil, was already pending.
|
||||||
|
*/
|
||||||
void
|
void
|
||||||
transstr(Lang *l, Str *raw, Str *out)
|
transstr(Lang *l, Str *pre, Str *raw, Str *out)
|
||||||
{
|
{
|
||||||
Im q;
|
Im q;
|
||||||
Emit e;
|
Emit e;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
sclear(out);
|
|
||||||
if(l->trans == nil){
|
|
||||||
*out = *raw;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
memset(&q, 0, sizeof q);
|
memset(&q, 0, sizeof q);
|
||||||
q.l = l;
|
q.l = l;
|
||||||
|
if(pre != nil)
|
||||||
|
q.pre = *pre;
|
||||||
|
sclear(out);
|
||||||
|
if(l->trans == nil){
|
||||||
|
sappend(out, &q.pre);
|
||||||
|
sappend(out, raw);
|
||||||
|
return;
|
||||||
|
}
|
||||||
for(i = 0; i < raw->n; i++){
|
for(i = 0; i < raw->n; i++){
|
||||||
e = l->trans(&q, raw->r[i]);
|
e = l->trans(&q, raw->r[i]);
|
||||||
sappend(out, &e.s);
|
sappend(out, &e.s);
|
||||||
@@ -471,56 +594,45 @@ transstr(Lang *l, Str *raw, Str *out)
|
|||||||
commitim(&q, out);
|
commitim(&q, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
/*
|
||||||
addkouho(Str *s)
|
* The typed keys and their transliteration are both emoji queries, folded
|
||||||
{
|
* as the dictionary is; the query shown is the keys while they match
|
||||||
int i;
|
* something, else the transliteration, as typed.
|
||||||
|
*/
|
||||||
for(i = 0; i < im.nkouho; i++)
|
|
||||||
if(scmp(&im.kouho[i], s) == 0)
|
|
||||||
return;
|
|
||||||
if(im.nkouho < Maxkouho)
|
|
||||||
im.kouho[im.nkouho++] = *s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The typed keys and their transliteration are both emoji queries. */
|
|
||||||
static void
|
static void
|
||||||
emojiquery(void)
|
emojiquery(void)
|
||||||
{
|
{
|
||||||
Str kouho[Maxkouho], raw, local;
|
Str kouho[Maxkouho], raw, local;
|
||||||
int i, n, rawhit, localhit;
|
int i, n, rawhit;
|
||||||
|
|
||||||
raw = search.raw;
|
raw = search.raw;
|
||||||
foldascii(&raw);
|
foldascii(&raw);
|
||||||
transstr(im.l, &search.raw, &local);
|
transstr(im.l, nil, &search.raw, &search.text);
|
||||||
|
local = search.text;
|
||||||
foldascii(&local);
|
foldascii(&local);
|
||||||
clearkouho();
|
clearkouho();
|
||||||
n = dictlookup(getlang(LangEMOJI), &raw, kouho, Maxkouho);
|
n = dictprefix(getlang(LangEMOJI)->dict, &raw, kouho, Maxkouho);
|
||||||
rawhit = n != 0;
|
rawhit = n != 0;
|
||||||
for(i = 0; i < n; i++)
|
for(i = 0; i < n; i++)
|
||||||
addkouho(&kouho[i]);
|
addkouho(&kouho[i]);
|
||||||
localhit = 0;
|
|
||||||
if(scmp(&raw, &local) != 0){
|
if(scmp(&raw, &local) != 0){
|
||||||
n = dictlookup(getlang(LangEMOJI), &local, kouho, Maxkouho);
|
n = dictprefix(getlang(LangEMOJI)->dict, &local, kouho, Maxkouho);
|
||||||
localhit = n != 0;
|
|
||||||
for(i = 0; i < n; i++)
|
for(i = 0; i < n; i++)
|
||||||
addkouho(&kouho[i]);
|
addkouho(&kouho[i]);
|
||||||
}
|
}
|
||||||
search.text = rawhit || !localhit ? raw : local;
|
if(rawhit)
|
||||||
|
search.text = search.raw;
|
||||||
selectfirst();
|
selectfirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The keys typed since Ctrl+H go on composing from the seed. */
|
||||||
static void
|
static void
|
||||||
hanjaquery(void)
|
hanjaquery(void)
|
||||||
{
|
{
|
||||||
Str key;
|
transstr(im.l, &search.seed, &search.raw, &search.text);
|
||||||
|
|
||||||
transstr(im.l, &search.raw, &key);
|
|
||||||
search.text = search.seed;
|
|
||||||
sappend(&search.text, &key);
|
|
||||||
clearkouho();
|
clearkouho();
|
||||||
im.nkouho = dictlookup(getlang(LangHANJA), &search.text, im.kouho,
|
im.nkouho = dictlookup(getlang(LangHANJA)->dict, &search.text,
|
||||||
Maxkouho);
|
im.kouho, Maxkouho);
|
||||||
selectfirst();
|
selectfirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,13 +651,20 @@ searchlang(Rune c)
|
|||||||
return c == LangEMOJI || c == LangHANJA ? c : 0;
|
return c == LangEMOJI || c == LangHANJA ? c : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A chord with Alt or Super, or Ctrl with Shift, is the application's
|
||||||
|
* (Ctrl+Shift+V pastes); only a plain Ctrl+letter is a strans command.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
chord(u32int mod)
|
||||||
|
{
|
||||||
|
return (mod & ~Mshift) != 0 && mod != Mctrl;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
endsearch(void)
|
endsearch(void)
|
||||||
{
|
{
|
||||||
search.lang = 0;
|
memset(&search, 0, sizeof search);
|
||||||
sclear(&search.seed);
|
|
||||||
sclear(&search.raw);
|
|
||||||
sclear(&search.text);
|
|
||||||
clearkouho();
|
clearkouho();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,15 +673,39 @@ reset(void)
|
|||||||
{
|
{
|
||||||
sclear(&im.pre);
|
sclear(&im.pre);
|
||||||
sclear(&im.raw);
|
sclear(&im.raw);
|
||||||
|
okuriat = -1;
|
||||||
endsearch();
|
endsearch();
|
||||||
memset(&caret, 0, sizeof caret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Whatever is pending becomes committed text: the search query, a chosen
|
||||||
|
* candidate, else the reading. Enter does this, and so do a reset and a
|
||||||
|
* lost focus.
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
commitsearch(Str *com)
|
flush(Str *com)
|
||||||
{
|
{
|
||||||
sappend(com, &search.text);
|
if(search.lang)
|
||||||
endsearch();
|
sappend(com, &search.text);
|
||||||
|
else if(im.sel >= 0)
|
||||||
|
sappend(com, &im.kouho[im.sel]);
|
||||||
|
else
|
||||||
|
commitim(&im, com);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Focus can be lost after the next context's keys arrive, so the engine
|
||||||
|
* changes hands with text still pending. The context it was taken from
|
||||||
|
* gets that text back when it is next heard from.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
takelost(void *owner, Str *com)
|
||||||
|
{
|
||||||
|
if(owner != lostowner)
|
||||||
|
return;
|
||||||
|
sappend(com, &lost);
|
||||||
|
lostowner = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -572,36 +715,18 @@ commitsearch(Str *com)
|
|||||||
static void
|
static void
|
||||||
startsearch(int lang, Str *com)
|
startsearch(int lang, Str *com)
|
||||||
{
|
{
|
||||||
Str pending;
|
Str seed;
|
||||||
|
|
||||||
impre(&im, &pending);
|
sclear(&seed);
|
||||||
if(lang == LangHANJA){
|
if(lang == LangHANJA && !search.lang && im.sel < 0)
|
||||||
sclear(&im.pre);
|
impre(&im, &seed);
|
||||||
sclear(&im.raw);
|
|
||||||
}else
|
|
||||||
commit(com);
|
|
||||||
commitsearch(com);
|
|
||||||
search.lang = lang;
|
|
||||||
if(lang == LangHANJA){
|
|
||||||
search.seed = pending;
|
|
||||||
searchquery();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Whatever is pending becomes committed text, a moved-to candidate
|
|
||||||
* first: Enter does this, and so do a reset and a lost focus.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
flush(Str *com)
|
|
||||||
{
|
|
||||||
if(search.lang)
|
|
||||||
commitsearch(com);
|
|
||||||
else if(candidatechosen && im.sel >= 0 && im.sel < im.nkouho)
|
|
||||||
sappend(com, &im.kouho[im.sel]);
|
|
||||||
else
|
else
|
||||||
commit(com);
|
flush(com);
|
||||||
reset();
|
reset();
|
||||||
|
search.lang = lang;
|
||||||
|
search.seed = seed;
|
||||||
|
if(lang == LangHANJA)
|
||||||
|
searchquery();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -630,24 +755,15 @@ searchkey(u32int ks, u32int mod, Str *com)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(ks == Ktab){
|
if(ks == Ktab){
|
||||||
if(im.nkouho == 0)
|
if(im.nkouho != 0)
|
||||||
return 1;
|
cyclekouho(mod & Mshift ? -1 : 1);
|
||||||
if(mod & Mshift){
|
|
||||||
if(im.sel <= 0)
|
|
||||||
im.sel = im.nkouho - 1;
|
|
||||||
else
|
|
||||||
im.sel--;
|
|
||||||
}else if(im.sel >= im.nkouho - 1)
|
|
||||||
im.sel = 0;
|
|
||||||
else
|
|
||||||
im.sel++;
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(ks == Kret){
|
if(ks == Kret || ks == ' '){
|
||||||
if(im.nkouho > 0)
|
if(im.nkouho > 0)
|
||||||
picksearch(max(im.sel, 0), com);
|
picksearch(max(im.sel, 0), com);
|
||||||
else
|
else
|
||||||
commitsearch(com);
|
flush(com);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(ks == Kback){
|
if(ks == Kback){
|
||||||
@@ -663,26 +779,26 @@ searchkey(u32int ks, u32int mod, Str *com)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(ks == Kesc){
|
if(ks == Kesc){
|
||||||
|
im.pre = search.seed; /* the syllable Ctrl+H took is pending again */
|
||||||
endsearch();
|
endsearch();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(ks >= Kspec || (ks < ' ' && !(mod & Mctrl)) ||
|
if(ks >= Kspec || (ks < ' ' && !(mod & Mctrl)) || chord(mod)){
|
||||||
(mod & (Malt|Msuper))){
|
flush(com);
|
||||||
commitsearch(com);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if(mod & Mctrl){
|
if(mod == Mctrl){
|
||||||
c = ctrlkey(ks);
|
c = ctrlkey(ks);
|
||||||
n = searchlang(c);
|
n = searchlang(c);
|
||||||
if(n != 0 && n != search.lang){
|
if(n != 0 && n != search.lang){
|
||||||
startsearch(n, com);
|
startsearch(n, com);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
commitsearch(com);
|
flush(com);
|
||||||
return n != 0 || setlang(c);
|
return n != 0 || setlang(c);
|
||||||
}
|
}
|
||||||
if(search.raw.n >= Maxrunes){
|
if(search.raw.n >= Maxrunes){
|
||||||
commitsearch(com);
|
flush(com);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
sputr(&search.raw, ks);
|
sputr(&search.raw, ks);
|
||||||
@@ -693,15 +809,38 @@ searchkey(u32int ks, u32int mod, Str *com)
|
|||||||
static int
|
static int
|
||||||
transition(u32int ks, u32int mod, Str *com)
|
transition(u32int ks, u32int mod, Str *com)
|
||||||
{
|
{
|
||||||
|
Str mark;
|
||||||
int n;
|
int n;
|
||||||
Rune c;
|
Rune c;
|
||||||
|
|
||||||
if(ismodkey(ks))
|
modemark = 0; /* any key after the switch takes its mark away */
|
||||||
return 0;
|
/* Shift on a romaji letter marks the okurigana, as SKK's capital does. */
|
||||||
|
if(isjp(&im) && (mod & Mshift) && ks >= 'A' && ks <= 'Z' && haspre(&im)){
|
||||||
|
impre(&im, &mark);
|
||||||
|
okuriat = mark.n;
|
||||||
|
}
|
||||||
if(im.l->lang == LangKO)
|
if(im.l->lang == LangKO)
|
||||||
ks = kokey(ks, mod);
|
ks = kokey(ks, mod);
|
||||||
else if(isjp(&im) && ks >= 'A' && ks <= 'Z')
|
else if(isjp(&im))
|
||||||
ks += 'a' - 'A'; /* romaji is case-blind, Caps Lock included */
|
ks = lower(ks); /* romaji is case-blind, Caps Lock included */
|
||||||
|
/* The Korean and Japanese keyboards' own keys, as the Ctrl chords
|
||||||
|
* and Space they stand for. */
|
||||||
|
switch(ks){
|
||||||
|
case Khangul: ks = im.l->lang == LangKO ? 't' : 's'; mod = Mctrl; break;
|
||||||
|
case Khanja: ks = 'h'; mod = Mctrl; break;
|
||||||
|
case Kzenkaku: ks = isjp(&im) ? 't' : 'n'; mod = Mctrl; break;
|
||||||
|
case Kkana: ks = im.l->lang == LangJP ? 'k' : 'n'; mod = Mctrl; break;
|
||||||
|
case Khenkan:
|
||||||
|
if(isjp(&im)){
|
||||||
|
ks = ' ';
|
||||||
|
mod &= Mshift;
|
||||||
|
}else{
|
||||||
|
ks = 'n';
|
||||||
|
mod = Mctrl;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Kmuhenkan: ks = 't'; mod = Mctrl; break;
|
||||||
|
}
|
||||||
if(search.lang)
|
if(search.lang)
|
||||||
return searchkey(ks, mod, com);
|
return searchkey(ks, mod, com);
|
||||||
n = movedelta(ks);
|
n = movedelta(ks);
|
||||||
@@ -709,79 +848,77 @@ transition(u32int ks, u32int mod, Str *com)
|
|||||||
movekouho(n);
|
movekouho(n);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
n = numberedkouho(ks, mod);
|
/* Digits pick a candidate only once the list has been engaged. */
|
||||||
|
n = im.sel >= 0 ? numberedkouho(ks, mod) : -1;
|
||||||
if(n >= 0){
|
if(n >= 0){
|
||||||
sappend(com, &im.kouho[n]);
|
sappend(com, &im.kouho[n]);
|
||||||
reset();
|
reset();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(ks == Ktab || ks == Kret){
|
if((ks == ' ' || ks == Ktab) && isjp(&im) && haspre(&im) &&
|
||||||
|
!(mod & ~Mshift)){
|
||||||
|
/* Space and Tab convert: they step through the candidates,
|
||||||
|
* Katakana last; a reading with none is committed. */
|
||||||
|
katakouho();
|
||||||
|
if(im.nkouho != 0)
|
||||||
|
cyclekouho(mod & Mshift ? -1 : 1);
|
||||||
|
else
|
||||||
|
flush(com);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if(ks == Ktab || ks == Kret || (ks == Kesc && !isjp(&im))){
|
||||||
if(!haspre(&im))
|
if(!haspre(&im))
|
||||||
return 0;
|
return 0;
|
||||||
flush(com);
|
flush(com);
|
||||||
/* Japanese confirms with the key; Korean and Telex let it on. */
|
/* Japanese confirms with the key; Korean and Telex let it on. */
|
||||||
return isjp(&im);
|
return isjp(&im);
|
||||||
}
|
}
|
||||||
if(ks == Kback){
|
if(ks == Kback || ks == Kesc){
|
||||||
if(!haspre(&im))
|
if(!haspre(&im))
|
||||||
return 0;
|
return 0;
|
||||||
im.l->back(&im);
|
if(im.sel >= 0)
|
||||||
if(!haspre(&im)){
|
im.sel = -1; /* back from the candidate to the reading */
|
||||||
|
else if(ks == Kesc)
|
||||||
reset();
|
reset();
|
||||||
return 1;
|
else{
|
||||||
|
im.l->back(&im);
|
||||||
|
if(haspre(&im))
|
||||||
|
dictqjp();
|
||||||
|
else
|
||||||
|
reset();
|
||||||
}
|
}
|
||||||
if(isjp(&im))
|
|
||||||
dictqjp();
|
|
||||||
else
|
|
||||||
clearkouho();
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(ks == Kesc){
|
if(ks >= Kspec || chord(mod)){
|
||||||
if(!haspre(&im))
|
flush(com);
|
||||||
return 0;
|
|
||||||
reset();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(ks >= Kspec || (mod & (Malt|Msuper))){
|
|
||||||
commit(com);
|
|
||||||
reset();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if(mod & Mctrl){
|
if(mod == Mctrl){
|
||||||
c = ctrlkey(ks);
|
c = ctrlkey(ks);
|
||||||
n = searchlang(c);
|
n = searchlang(c);
|
||||||
if(n != 0){
|
if(n != 0){
|
||||||
startsearch(n, com);
|
startsearch(n, com);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
commit(com);
|
flush(com);
|
||||||
reset();
|
|
||||||
return setlang(c);
|
return setlang(c);
|
||||||
}
|
}
|
||||||
if(ks == '0' && im.nkouho > 0){
|
if(ks == '0' && isjp(&im) && haspre(&im)){
|
||||||
commit(com);
|
im.sel = -1; /* 0 commits the reading, as 1-9 pick candidates */
|
||||||
reset();
|
flush(com);
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(ks > 0x7f || ks == ' '){
|
|
||||||
if(!haspre(&im))
|
|
||||||
return 0;
|
|
||||||
commit(com);
|
|
||||||
reset();
|
|
||||||
if(com->n >= Maxrunes)
|
|
||||||
return 0;
|
|
||||||
sputr(com, ks);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(im.l->trans == nil)
|
if(im.l->trans == nil)
|
||||||
return 0;
|
return 0;
|
||||||
|
if(im.sel >= 0)
|
||||||
|
flush(com); /* typing on takes the chosen candidate */
|
||||||
if(im.pre.n >= Maxrunes ||
|
if(im.pre.n >= Maxrunes ||
|
||||||
(isjp(&im) && im.pre.n + im.raw.n >= Maxrunes)){
|
(isjp(&im) && im.pre.n + im.raw.n >= Maxrunes)){
|
||||||
/* A full reading commits; a pending romaji syllable stays. */
|
/* A full reading commits; a pending romaji syllable stays. */
|
||||||
if(isjp(&im))
|
if(isjp(&im))
|
||||||
sappend(com, &im.pre);
|
sappend(com, &im.pre);
|
||||||
else
|
else
|
||||||
commit(com);
|
commitim(&im, com);
|
||||||
sclear(&im.pre);
|
sclear(&im.pre);
|
||||||
clearkouho();
|
clearkouho();
|
||||||
}
|
}
|
||||||
@@ -796,9 +933,11 @@ init(void)
|
|||||||
im.sel = -1;
|
im.sel = -1;
|
||||||
memset(&search, 0, sizeof search);
|
memset(&search, 0, sizeof search);
|
||||||
memset(&caret, 0, sizeof caret);
|
memset(&caret, 0, sizeof caret);
|
||||||
|
modemark = 0;
|
||||||
|
okuriat = -1;
|
||||||
activeowner = nil;
|
activeowner = nil;
|
||||||
activecap = 0;
|
lostowner = nil;
|
||||||
candidatechosen = 0;
|
clientpre = 0;
|
||||||
memset(&lastdraw, 0, sizeof lastdraw);
|
memset(&lastdraw, 0, sizeof lastdraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,13 +958,15 @@ imhandlekey(Keyreq *kr)
|
|||||||
res.eaten = 1;
|
res.eaten = 1;
|
||||||
switch(kr->op){
|
switch(kr->op){
|
||||||
case Keyrelease:
|
case Keyrelease:
|
||||||
|
takelost(kr->owner, &res.commit);
|
||||||
if(kr->owner == activeowner){
|
if(kr->owner == activeowner){
|
||||||
flush(&res.commit);
|
flush(&res.commit);
|
||||||
activeowner = nil;
|
activeowner = nil;
|
||||||
activecap = 0;
|
clientpre = 0;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Keyreset:
|
case Keyreset:
|
||||||
|
takelost(kr->owner, &res.commit);
|
||||||
if(kr->owner == activeowner)
|
if(kr->owner == activeowner)
|
||||||
flush(&res.commit);
|
flush(&res.commit);
|
||||||
break;
|
break;
|
||||||
@@ -834,22 +975,23 @@ imhandlekey(Keyreq *kr)
|
|||||||
caret = kr->caret;
|
caret = kr->caret;
|
||||||
break;
|
break;
|
||||||
case Keycap:
|
case Keycap:
|
||||||
res.eaten = activeowner != nil && kr->owner == activeowner;
|
res.eaten = kr->owner == activeowner;
|
||||||
if(res.eaten)
|
if(res.eaten)
|
||||||
activecap = kr->cap & Cclientpreedit;
|
clientpre = kr->clientpre;
|
||||||
break;
|
break;
|
||||||
case Keypress:
|
case Keypress:
|
||||||
res.eaten = 0;
|
res.eaten = 0;
|
||||||
if(activeowner != nil && kr->owner == activeowner)
|
if(kr->owner == activeowner)
|
||||||
activecap = kr->cap & Cclientpreedit;
|
clientpre = kr->clientpre;
|
||||||
if(keymeaningful(kr->ks)){
|
if(keymeaningful(kr->ks)){
|
||||||
if(kr->owner != activeowner){
|
if(kr->owner != activeowner){
|
||||||
reset();
|
sclear(&lost);
|
||||||
|
flush(&lost);
|
||||||
|
lostowner = lost.n > 0 ? activeowner : nil;
|
||||||
activeowner = kr->owner;
|
activeowner = kr->owner;
|
||||||
activecap = kr->cap & Cclientpreedit;
|
clientpre = kr->clientpre;
|
||||||
caret = kr->caret;
|
}
|
||||||
}else if(kr->caret.valid)
|
caret = kr->caret;
|
||||||
caret = kr->caret;
|
|
||||||
res.eaten = transition(kr->ks, kr->mod, &res.commit);
|
res.eaten = transition(kr->ks, kr->mod, &res.commit);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ LD = 9l
|
|||||||
HOSTCC = cc
|
HOSTCC = cc
|
||||||
PKG_CONFIG ?= pkg-config
|
PKG_CONFIG ?= pkg-config
|
||||||
CFLAGS ?= -O2 -g
|
CFLAGS ?= -O2 -g
|
||||||
UNIT_CPPFLAGS = -I.. -I../cutest
|
UNIT_CPPFLAGS = -I..
|
||||||
UNIT_CFLAGS = -std=c99 -Wall -Wextra
|
UNIT_CFLAGS = -std=c99 -Wall -Wextra
|
||||||
HOST_CFLAGS = -std=c99 -Wall -Wextra
|
HOST_CFLAGS = -std=c99 -Wall -Wextra
|
||||||
TEXT_CFLAGS = $(shell $(PKG_CONFIG) --cflags pangocairo cairo fontconfig)
|
TEXT_CFLAGS = $(shell $(PKG_CONFIG) --cflags pangocairo cairo fontconfig)
|
||||||
@@ -18,8 +18,8 @@ IBUS_CLIENT_CFLAGS = $(shell $(PKG_CONFIG) --cflags ibus-1.0)
|
|||||||
IBUS_CLIENT_LIBS = $(shell $(PKG_CONFIG) --libs ibus-1.0)
|
IBUS_CLIENT_LIBS = $(shell $(PKG_CONFIG) --libs ibus-1.0)
|
||||||
X11_CFLAGS = $(shell $(PKG_CONFIG) --cflags x11)
|
X11_CFLAGS = $(shell $(PKG_CONFIG) --cflags x11)
|
||||||
X11_LIBS = $(shell $(PKG_CONFIG) --libs x11)
|
X11_LIBS = $(shell $(PKG_CONFIG) --libs x11)
|
||||||
XIM_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-imdkit xcb-xkb xkbcommon-x11)
|
XIM_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
||||||
XIM_LIBS = $(shell $(PKG_CONFIG) --libs xcb-imdkit xcb-xkb xkbcommon-x11)
|
XIM_LIBS = $(shell $(PKG_CONFIG) --libs xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
||||||
UNIT_LDLIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS)
|
UNIT_LDLIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS)
|
||||||
|
|
||||||
PROG = unit_test
|
PROG = unit_test
|
||||||
@@ -32,14 +32,13 @@ FAULT = daemon_collision_test daemon_failure_test daemon_restart_test
|
|||||||
LIVE = $(SMOKE) $(FAULT)
|
LIVE = $(SMOKE) $(FAULT)
|
||||||
TESTSRC = test_util.c str_test.c trie_test.c \
|
TESTSRC = test_util.c str_test.c trie_test.c \
|
||||||
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
||||||
popup_test.c font_test.c ibus_test.c server_test.c keymap_test.c \
|
popup_test.c font_test.c ibus_test.c server_test.c compose_test.c \
|
||||||
xim_adapter_test.c
|
xim_adapter_test.c
|
||||||
TESTOBJ = $(TESTSRC:.c=.o)
|
TESTOBJ = $(TESTSRC:.c=.o)
|
||||||
PARENTSRC = str.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
|
PARENTSRC = str.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
|
||||||
font.c
|
font.c compose.c
|
||||||
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
||||||
XIMOBJ = unit_xim_keymap.o
|
COMMONOBJ = $(TESTOBJ) $(PARENTOBJ)
|
||||||
COMMONOBJ = $(TESTOBJ) $(PARENTOBJ) $(XIMOBJ)
|
|
||||||
OBJS = unit_test.o stress_test.o $(COMMONOBJ)
|
OBJS = unit_test.o stress_test.o $(COMMONOBJ)
|
||||||
|
|
||||||
all: $(PROG) $(STRESS) $(LIVE)
|
all: $(PROG) $(STRESS) $(LIVE)
|
||||||
@@ -123,14 +122,11 @@ unit_test.o stress_test.o $(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h \
|
|||||||
engine_test.o: ../strans.c
|
engine_test.o: ../strans.c
|
||||||
ibus_test.o: UNIT_CPPFLAGS += $(IBUS_CFLAGS)
|
ibus_test.o: UNIT_CPPFLAGS += $(IBUS_CFLAGS)
|
||||||
ibus_test.o: ../ibus.c
|
ibus_test.o: ../ibus.c
|
||||||
keymap_test.o xim_adapter_test.o: UNIT_CPPFLAGS += $(XIM_CFLAGS)
|
xim_adapter_test.o: UNIT_CPPFLAGS += $(XIM_CFLAGS)
|
||||||
xim_adapter_test.o: ../xim/xim.c
|
xim_adapter_test.o: ../xim.c
|
||||||
server_test.o: ../srv.c
|
server_test.o: ../srv.c
|
||||||
unit_font.o: UNIT_CPPFLAGS += $(TEXT_CFLAGS)
|
unit_font.o: UNIT_CPPFLAGS += $(TEXT_CFLAGS)
|
||||||
|
unit_compose.o compose_test.o: UNIT_CPPFLAGS += $(IBUS_CFLAGS)
|
||||||
unit_xim_keymap.o: ../xim/keymap.c
|
|
||||||
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(XIM_CFLAGS) $(UNIT_CFLAGS) \
|
|
||||||
$(CFLAGS) -c -o $@ $<
|
|
||||||
|
|
||||||
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
||||||
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -c -o $@ $<
|
||||||
|
|||||||
37
tests/compose_test.c
Normal file
37
tests/compose_test.c
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
|
#include "test.h"
|
||||||
|
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||||
|
|
||||||
|
/* A sequence swallows its keys and hands its text back at the end. */
|
||||||
|
void
|
||||||
|
compose_sequences(struct ct *t)
|
||||||
|
{
|
||||||
|
static const struct {
|
||||||
|
u32int sym;
|
||||||
|
int composing;
|
||||||
|
char *text;
|
||||||
|
} keys[] = {
|
||||||
|
{ XKB_KEY_a, 0, "" },
|
||||||
|
{ XKB_KEY_dead_acute, 1, "" },
|
||||||
|
{ XKB_KEY_e, 0, "é" },
|
||||||
|
{ XKB_KEY_Multi_key, 1, "" },
|
||||||
|
{ XKB_KEY_o, 1, "" },
|
||||||
|
{ XKB_KEY_c, 0, "©" },
|
||||||
|
{ XKB_KEY_dead_acute, 1, "" },
|
||||||
|
{ XKB_KEY_Escape, 1, "" },
|
||||||
|
{ XKB_KEY_a, 0, "" },
|
||||||
|
};
|
||||||
|
char buf[Maxutf];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if(!CT_CHECK(t, setenv("XCOMPOSEFILE", "data/compose", 1) == 0))
|
||||||
|
return;
|
||||||
|
composeinit();
|
||||||
|
for(i = 0; i < nelem(keys); i++){
|
||||||
|
CT_EQ_INT(t, keys[i].composing,
|
||||||
|
composekey(keys[i].sym, buf, sizeof buf));
|
||||||
|
CT_EQ_STR(t, keys[i].text, buf);
|
||||||
|
}
|
||||||
|
unsetenv("XCOMPOSEFILE");
|
||||||
|
}
|
||||||
2
tests/data/compose
Normal file
2
tests/data/compose
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<dead_acute> <e> : "é"
|
||||||
|
<Multi_key> <o> <c> : "©"
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
;; converter fixture; the test transcodes this file to EUC-JP
|
||||||
かんじ 漢字 幹事 感じ
|
かんじ 漢字 幹事 感じ
|
||||||
えがお 笑顔
|
えがお 笑顔
|
||||||
きごう 記号 普通
|
きごう 記号 普通
|
||||||
|
|||||||
|
@@ -15,10 +15,10 @@ dictionary_candidates(struct ct *t)
|
|||||||
lang = getlang(LangJP);
|
lang = getlang(LangJP);
|
||||||
saved = lang->dict;
|
saved = lang->dict;
|
||||||
lang->dict = trienew();
|
lang->dict = trienew();
|
||||||
trieput(lang->dict, "かな", strlen("かな"), " 候補1 かな 候補2 ",
|
trieput(lang->dict, "かな", strlen("かな"), " 候補1 候補2 ",
|
||||||
strlen(" 候補1 かな 候補2 "));
|
strlen(" 候補1 候補2 "));
|
||||||
key = mkstr("かな");
|
key = mkstr("かな");
|
||||||
n = dictlookup(lang, &key, kouho, Maxkouho);
|
n = dictlookup(lang->dict, &key, kouho, Maxkouho);
|
||||||
if(CT_EQ_INT(t, 2, n)){
|
if(CT_EQ_INT(t, 2, n)){
|
||||||
checkstr(t, "candidate 1", "候補1", &kouho[0]);
|
checkstr(t, "candidate 1", "候補1", &kouho[0]);
|
||||||
checkstr(t, "candidate 2", "候補2", &kouho[1]);
|
checkstr(t, "candidate 2", "候補2", &kouho[1]);
|
||||||
@@ -34,13 +34,13 @@ dictionary_candidates(struct ct *t)
|
|||||||
*p = '\0';
|
*p = '\0';
|
||||||
trieput(lang->dict, "key", 3, many, strlen(many));
|
trieput(lang->dict, "key", 3, many, strlen(many));
|
||||||
key = mkstr("key");
|
key = mkstr("key");
|
||||||
n = dictlookup(lang, &key, kouho, Maxkouho);
|
n = dictlookup(lang->dict, &key, kouho, Maxkouho);
|
||||||
if(CT_EQ_INT(t, Maxkouho, n)){
|
if(CT_EQ_INT(t, Maxkouho, n)){
|
||||||
checkstr(t, "first capped candidate", "c00", &kouho[0]);
|
checkstr(t, "first capped candidate", "c00", &kouho[0]);
|
||||||
snprint(item, sizeof item, "c%02d", Maxkouho-1);
|
snprint(item, sizeof item, "c%02d", Maxkouho-1);
|
||||||
checkstr(t, "last capped candidate", item, &kouho[Maxkouho-1]);
|
checkstr(t, "last capped candidate", item, &kouho[Maxkouho-1]);
|
||||||
}
|
}
|
||||||
CT_EQ_INT(t, 3, dictlookup(lang, &key, kouho, 3));
|
CT_EQ_INT(t, 3, dictlookup(lang->dict, &key, kouho, 3));
|
||||||
trieclose(lang->dict);
|
trieclose(lang->dict);
|
||||||
lang->dict = saved;
|
lang->dict = saved;
|
||||||
}
|
}
|
||||||
@@ -56,28 +56,33 @@ dictionary_misses(struct ct *t)
|
|||||||
saved = lang->dict;
|
saved = lang->dict;
|
||||||
lang->dict = trienew();
|
lang->dict = trienew();
|
||||||
key = mkstr("");
|
key = mkstr("");
|
||||||
CT_EQ_INT(t, 0, dictlookup(lang, &key, kouho, Maxkouho));
|
CT_EQ_INT(t, 0, dictlookup(lang->dict, &key, kouho, Maxkouho));
|
||||||
key = mkstr("missing");
|
key = mkstr("missing");
|
||||||
CT_EQ_INT(t, 0, dictlookup(lang, &key, kouho, Maxkouho));
|
CT_EQ_INT(t, 0, dictlookup(lang->dict, &key, kouho, Maxkouho));
|
||||||
CT_EQ_INT(t, 0, dictlookup(getlang(LangKO), &key, kouho, Maxkouho));
|
CT_EQ_INT(t, 0, dictlookup(getlang(LangKO)->dict, &key, kouho, Maxkouho));
|
||||||
trieclose(lang->dict);
|
trieclose(lang->dict);
|
||||||
lang->dict = saved;
|
lang->dict = saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
dictionary_emoji_identity(struct ct *t)
|
dictionary_prefix(struct ct *t)
|
||||||
{
|
{
|
||||||
Str kouho[Maxkouho], key;
|
Str kouho[Maxkouho], key;
|
||||||
Trie *saved;
|
Trie *dict;
|
||||||
Lang *lang;
|
|
||||||
|
|
||||||
lang = getlang(LangEMOJI);
|
dict = trienew();
|
||||||
saved = lang->dict;
|
trieput(dict, "smile", 5, "A B", 3);
|
||||||
lang->dict = trienew();
|
trieput(dict, "smiley", 6, "B C", 3);
|
||||||
trieput(lang->dict, "é", strlen("é"), "é", strlen("é"));
|
trieput(dict, "sad", 3, "D", 1);
|
||||||
key = mkstr("é");
|
key = mkstr("smile");
|
||||||
if(CT_EQ_INT(t, 1, dictlookup(lang, &key, kouho, Maxkouho)))
|
if(CT_EQ_INT(t, 4, dictprefix(dict, &key, kouho, Maxkouho))){
|
||||||
checkstr(t, "emoji identity candidate", "é", &kouho[0]);
|
checkstr(t, "own entry first", "A", &kouho[0]);
|
||||||
trieclose(lang->dict);
|
checkstr(t, "entry below", "C", &kouho[3]);
|
||||||
lang->dict = saved;
|
}
|
||||||
|
key = mkstr("s");
|
||||||
|
CT_EQ_INT(t, 5, dictprefix(dict, &key, kouho, Maxkouho));
|
||||||
|
CT_EQ_INT(t, 2, dictprefix(dict, &key, kouho, 2));
|
||||||
|
key = mkstr("x");
|
||||||
|
CT_EQ_INT(t, 0, dictprefix(dict, &key, kouho, Maxkouho));
|
||||||
|
trieclose(dict);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ keystroke(u32int ks, u32int mod, Str *com)
|
|||||||
{
|
{
|
||||||
int eaten;
|
int eaten;
|
||||||
|
|
||||||
eaten = transition(ks, mod, com);
|
eaten = keymeaningful(ks) && transition(ks, mod, com);
|
||||||
redraw();
|
redraw();
|
||||||
return eaten;
|
return eaten;
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ vietnamese_state_lifetime(struct ct *t)
|
|||||||
Str com;
|
Str com;
|
||||||
|
|
||||||
init();
|
init();
|
||||||
im.l = &testvi;
|
im.l = getlang(LangVI);
|
||||||
im.pre = mkstr("as");
|
im.pre = mkstr("as");
|
||||||
im.raw = mkstr("as");
|
im.raw = mkstr("as");
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
@@ -62,17 +62,17 @@ vietnamese_state_lifetime(struct ct *t)
|
|||||||
CT_EQ_INT(t, 0, im.raw.n);
|
CT_EQ_INT(t, 0, im.raw.n);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
im.l = &testvi;
|
im.l = getlang(LangVI);
|
||||||
im.pre = mkstr("as");
|
im.pre = mkstr("as");
|
||||||
im.raw = mkstr("as");
|
im.raw = mkstr("as");
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
CT_CHECK(t, !keystroke(Kesc, 0, &com));
|
||||||
CT_EQ_INT(t, 0, com.n);
|
checkstr(t, "Escape commits Telex", "á", &com);
|
||||||
CT_EQ_INT(t, 0, im.pre.n);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
CT_EQ_INT(t, 0, im.raw.n);
|
CT_EQ_INT(t, 0, im.raw.n);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
im.l = &testvi;
|
im.l = getlang(LangVI);
|
||||||
im.pre = mkstr("as");
|
im.pre = mkstr("as");
|
||||||
im.raw = mkstr("as");
|
im.raw = mkstr("as");
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
@@ -116,10 +116,13 @@ engine_backspace_clears_candidates(struct ct *t)
|
|||||||
im.nkouho = 1;
|
im.nkouho = 1;
|
||||||
im.sel = 0;
|
im.sel = 0;
|
||||||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
CT_EQ_INT(t, -1, im.sel);
|
||||||
|
checkstr(t, "chosen candidate backspace", "na", &im.raw);
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
checkstr(t, "backspace commit", "", &com);
|
checkstr(t, "backspace commit", "", &com);
|
||||||
checkstr(t, "pending romaji after backspace", "n", &im.raw);
|
checkstr(t, "backspace deletes the kana shown", "", &im.raw);
|
||||||
impre(&im, &shown);
|
impre(&im, &shown);
|
||||||
checkstr(t, "shown preedit after backspace", "ん", &shown);
|
checkstr(t, "shown preedit after backspace", "", &shown);
|
||||||
CT_EQ_INT(t, 0, im.nkouho);
|
CT_EQ_INT(t, 0, im.nkouho);
|
||||||
CT_EQ_INT(t, -1, im.sel);
|
CT_EQ_INT(t, -1, im.sel);
|
||||||
}
|
}
|
||||||
@@ -162,7 +165,7 @@ engine_candidate_shortcut_modifiers(struct ct *t)
|
|||||||
init();
|
init();
|
||||||
im.kouho[9] = mkstr("must-not-select");
|
im.kouho[9] = mkstr("must-not-select");
|
||||||
im.nkouho = 10;
|
im.nkouho = 10;
|
||||||
im.sel = 9;
|
im.sel = -1;
|
||||||
selected = im.kouho[9];
|
selected = im.kouho[9];
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
CT_CHECK(t, !keystroke('1', mods[i], &com));
|
CT_CHECK(t, !keystroke('1', mods[i], &com));
|
||||||
@@ -181,7 +184,7 @@ ownerrequestatcap(void *owner, int cap, int op, Rune key, u32int mod,
|
|||||||
reply = chancreate(sizeof(Keyres), 1);
|
reply = chancreate(sizeof(Keyres), 1);
|
||||||
memset(&req, 0, sizeof req);
|
memset(&req, 0, sizeof req);
|
||||||
req.owner = owner;
|
req.owner = owner;
|
||||||
req.cap = cap;
|
req.clientpre = cap;
|
||||||
req.op = op;
|
req.op = op;
|
||||||
req.ks = key;
|
req.ks = key;
|
||||||
req.mod = mod;
|
req.mod = mod;
|
||||||
@@ -231,6 +234,7 @@ engine_active_owner_lifecycle(struct ct *t)
|
|||||||
checkstr(t, "takeover resets old preedit", "ん", &res.preedit);
|
checkstr(t, "takeover resets old preedit", "ん", &res.preedit);
|
||||||
CT_EQ_PTR(t, &b, activeowner);
|
CT_EQ_PTR(t, &b, activeowner);
|
||||||
res = ownerrequest(&a, Keyreset, 0, 0);
|
res = ownerrequest(&a, Keyreset, 0, 0);
|
||||||
|
checkstr(t, "the taken owner gets its reading back", "か", &res.commit);
|
||||||
CT_EQ_INT(t, 0, res.preedit.n);
|
CT_EQ_INT(t, 0, res.preedit.n);
|
||||||
impre(&im, &shown);
|
impre(&im, &shown);
|
||||||
checkstr(t, "stale reset preserves owner", "ん", &shown);
|
checkstr(t, "stale reset preserves owner", "ん", &shown);
|
||||||
@@ -249,7 +253,7 @@ engine_active_owner_lifecycle(struct ct *t)
|
|||||||
CT_EQ_PTR(t, nil, activeowner);
|
CT_EQ_PTR(t, nil, activeowner);
|
||||||
CT_EQ_INT(t, 0, im.pre.n);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
CT_EQ_INT(t, 0, im.raw.n);
|
CT_EQ_INT(t, 0, im.raw.n);
|
||||||
ownerrequest(&c, Keypress, Kmodfirst, 0);
|
ownerrequest(&c, Keypress, 0, 0);
|
||||||
CT_EQ_PTR(t, nil, activeowner);
|
CT_EQ_PTR(t, nil, activeowner);
|
||||||
res = ownerrequest(&c, Keypress, 'k', 0);
|
res = ownerrequest(&c, Keypress, 'k', 0);
|
||||||
CT_EQ_PTR(t, &c, activeowner);
|
CT_EQ_PTR(t, &c, activeowner);
|
||||||
@@ -277,7 +281,6 @@ engine_active_owner_reset(struct ct *t)
|
|||||||
CT_EQ_PTR(t, &owner, activeowner);
|
CT_EQ_PTR(t, &owner, activeowner);
|
||||||
checkstr(t, "reset hands the reading back", "か", &res.commit);
|
checkstr(t, "reset hands the reading back", "か", &res.commit);
|
||||||
CT_EQ_INT(t, 0, res.preedit.n);
|
CT_EQ_INT(t, 0, res.preedit.n);
|
||||||
CT_CHECK(t, !caret.valid);
|
|
||||||
res = ownerrequest(&owner, Keypress, 'n', 0);
|
res = ownerrequest(&owner, Keypress, 'n', 0);
|
||||||
checkstr(t, "same owner after reset", "ん", &res.preedit);
|
checkstr(t, "same owner after reset", "ん", &res.preedit);
|
||||||
}
|
}
|
||||||
@@ -354,11 +357,11 @@ engine_vietnamese_client_preedit(struct ct *t)
|
|||||||
|
|
||||||
for(i = 0; i < nelem(cases); i++){
|
for(i = 0; i < nelem(cases); i++){
|
||||||
init();
|
init();
|
||||||
im.l = &testvi;
|
im.l = getlang(LangVI);
|
||||||
draindraw(nil);
|
draindraw(nil);
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
for(k = cases[i].keys; *k != '\0'; k++){
|
for(k = cases[i].keys; *k != '\0'; k++){
|
||||||
res = ownerrequestcap(&owner, Cclientpreedit,
|
res = ownerrequestcap(&owner, 1,
|
||||||
Keypress, *k, 0);
|
Keypress, *k, 0);
|
||||||
sappend(&com, &res.commit);
|
sappend(&com, &res.commit);
|
||||||
if(!res.eaten)
|
if(!res.eaten)
|
||||||
@@ -369,7 +372,7 @@ engine_vietnamese_client_preedit(struct ct *t)
|
|||||||
shown = com;
|
shown = com;
|
||||||
sappend(&shown, &res.preedit);
|
sappend(&shown, &res.preedit);
|
||||||
checkstr(t, "client preedit", cases[i].shown, &shown);
|
checkstr(t, "client preedit", cases[i].shown, &shown);
|
||||||
activecap = 0;
|
clientpre = 0;
|
||||||
redraw();
|
redraw();
|
||||||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||||||
CT_EQ_INT(t, 0, scmp(&dc.pre, &res.preedit));
|
CT_EQ_INT(t, 0, scmp(&dc.pre, &res.preedit));
|
||||||
@@ -401,7 +404,7 @@ engine_commit_contract(struct ct *t)
|
|||||||
im.l = getlang(LangKO);
|
im.l = getlang(LangKO);
|
||||||
sclear(&all);
|
sclear(&all);
|
||||||
for(i = 0; i < 3; i++){
|
for(i = 0; i < 3; i++){
|
||||||
res = ownerrequestcap(&owner, Cclientpreedit,
|
res = ownerrequestcap(&owner, 1,
|
||||||
Keypress, cases[j].key, 0);
|
Keypress, cases[j].key, 0);
|
||||||
CT_CHECK(t, res.eaten);
|
CT_CHECK(t, res.eaten);
|
||||||
checkstr(t, "repeated jamo commit",
|
checkstr(t, "repeated jamo commit",
|
||||||
@@ -449,7 +452,7 @@ engine_commit_contract(struct ct *t)
|
|||||||
return;
|
return;
|
||||||
im.nkouho = 1;
|
im.nkouho = 1;
|
||||||
im.sel = -1;
|
im.sel = -1;
|
||||||
CT_CHECK(t, !keystroke('q', 0, &com));
|
CT_CHECK(t, !keystroke('!', 0, &com));
|
||||||
checkstr(t, "uneaten-key commit", "か", &com);
|
checkstr(t, "uneaten-key commit", "か", &com);
|
||||||
CT_EQ_INT(t, 0, im.pre.n);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
CT_EQ_INT(t, 0, im.raw.n);
|
CT_EQ_INT(t, 0, im.raw.n);
|
||||||
@@ -471,10 +474,9 @@ engine_commit_contract(struct ct *t)
|
|||||||
sclear(&com);
|
sclear(&com);
|
||||||
if(!typekeys(t, "ka", &com))
|
if(!typekeys(t, "ka", &com))
|
||||||
return;
|
return;
|
||||||
CT_CHECK(t, keystroke(0xf008, 0, &com));
|
/* A key outside the map commits the reading and passes on. */
|
||||||
CT_EQ_INT(t, 2, com.n);
|
CT_CHECK(t, !keystroke(0xf008, 0, &com));
|
||||||
CT_EQ_UINT(t, 0x304b, com.r[0]);
|
checkstr(t, "non-ASCII key commit", "か", &com);
|
||||||
CT_EQ_UINT(t, 0xf008, com.r[1]);
|
|
||||||
CT_EQ_INT(t, 0, im.pre.n);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,7 +488,7 @@ engine_telex_history_bound(struct ct *t)
|
|||||||
int i;
|
int i;
|
||||||
|
|
||||||
init();
|
init();
|
||||||
im.l = &testvi;
|
im.l = getlang(LangVI);
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
CT_CHECK(t, keystroke('a', 0, &com));
|
CT_CHECK(t, keystroke('a', 0, &com));
|
||||||
for(i = 0; i < Maxrunes; i++){
|
for(i = 0; i < Maxrunes; i++){
|
||||||
@@ -563,11 +565,25 @@ engine_korean_modifiers_and_backspace(struct ct *t)
|
|||||||
void
|
void
|
||||||
engine_direct_language_modes(struct ct *t)
|
engine_direct_language_modes(struct ct *t)
|
||||||
{
|
{
|
||||||
|
Drawcmd dc;
|
||||||
Str com;
|
Str com;
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
draindraw(nil);
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
CT_EQ_INT(t, LangEN, im.l->lang);
|
CT_EQ_INT(t, LangEN, im.l->lang);
|
||||||
|
/* A switch shows the mode it switched to until the next key. */
|
||||||
|
CT_CHECK(t, keystroke('s', Mctrl, &com));
|
||||||
|
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||||||
|
checkstr(t, "mode mark", "한", &dc.pre);
|
||||||
|
CT_CHECK(t, keystroke('t', Mctrl, &com));
|
||||||
|
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||||||
|
checkstr(t, "mode mark", "A", &dc.pre);
|
||||||
|
CT_CHECK(t, !keystroke('a', 0, &com));
|
||||||
|
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||||||
|
CT_EQ_INT(t, 0, dc.pre.n);
|
||||||
|
CT_CHECK(t, !keystroke('a', 0, &com));
|
||||||
|
CT_EQ_INT(t, 0, draindraw(nil));
|
||||||
CT_EQ_PTR(t, nil, im.l->mapname);
|
CT_EQ_PTR(t, nil, im.l->mapname);
|
||||||
CT_EQ_PTR(t, nil, im.l->map);
|
CT_EQ_PTR(t, nil, im.l->map);
|
||||||
CT_EQ_PTR(t, nil, im.l->trans);
|
CT_EQ_PTR(t, nil, im.l->trans);
|
||||||
@@ -618,22 +634,15 @@ engine_language_switch_state(struct ct *t)
|
|||||||
{ "commit Vietnamese", 't', Mctrl, LangEN, "", "", "á", 0 },
|
{ "commit Vietnamese", 't', Mctrl, LangEN, "", "", "á", 0 },
|
||||||
};
|
};
|
||||||
char where[80];
|
char where[80];
|
||||||
Lang *vi;
|
|
||||||
Trie *saved;
|
|
||||||
Str com;
|
Str com;
|
||||||
int eaten, i;
|
int eaten, i;
|
||||||
|
|
||||||
vi = getlang(LangVI);
|
|
||||||
if(!CT_CHECK(t, vi != nil))
|
|
||||||
return;
|
|
||||||
saved = vi->map;
|
|
||||||
vi->map = testvi.map;
|
|
||||||
init();
|
init();
|
||||||
for(i = 0; i < nelem(steps); i++){
|
for(i = 0; i < nelem(steps); i++){
|
||||||
if(steps[i].stale){
|
if(steps[i].stale){
|
||||||
im.kouho[0] = mkstr("stale");
|
im.kouho[0] = mkstr("stale");
|
||||||
im.nkouho = 1;
|
im.nkouho = 1;
|
||||||
im.sel = 0;
|
im.sel = -1;
|
||||||
}
|
}
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
eaten = keystroke(steps[i].key, steps[i].mod, &com);
|
eaten = keystroke(steps[i].key, steps[i].mod, &com);
|
||||||
@@ -673,7 +682,7 @@ engine_language_switch_state(struct ct *t)
|
|||||||
CT_EQ_INT(t, 0, im.pre.n);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
}
|
}
|
||||||
init();
|
init();
|
||||||
im.l = &testvi;
|
im.l = getlang(LangVI);
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
if(typekeys(t, "as", &com)){
|
if(typekeys(t, "as", &com)){
|
||||||
CT_CHECK(t, !keystroke('p', Mctrl, &com));
|
CT_CHECK(t, !keystroke('p', Mctrl, &com));
|
||||||
@@ -681,7 +690,6 @@ engine_language_switch_state(struct ct *t)
|
|||||||
CT_EQ_INT(t, 0, im.pre.n);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
CT_EQ_INT(t, 0, im.raw.n);
|
CT_EQ_INT(t, 0, im.raw.n);
|
||||||
}
|
}
|
||||||
vi->map = saved;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct Searchfix Searchfix;
|
typedef struct Searchfix Searchfix;
|
||||||
@@ -691,9 +699,8 @@ struct Searchfix
|
|||||||
Search search;
|
Search search;
|
||||||
Lang *dictlang;
|
Lang *dictlang;
|
||||||
Trie *dict;
|
Trie *dict;
|
||||||
int activecap;
|
int clientpre;
|
||||||
Drawcmd lastdraw;
|
Drawcmd lastdraw;
|
||||||
int candidatechosen;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -723,7 +730,6 @@ setcandidates(int n)
|
|||||||
im.kouho[i] = mkstr(name);
|
im.kouho[i] = mkstr(name);
|
||||||
}
|
}
|
||||||
im.nkouho = n;
|
im.nkouho = n;
|
||||||
selectfirst();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -853,26 +859,34 @@ engine_candidate_page_movement(struct ct *t)
|
|||||||
void
|
void
|
||||||
engine_candidate_completion(struct ct *t)
|
engine_candidate_completion(struct ct *t)
|
||||||
{
|
{
|
||||||
static Rune keys[] = { Kret, Ktab };
|
|
||||||
Str com;
|
Str com;
|
||||||
int i;
|
|
||||||
|
|
||||||
for(i = 0; i < nelem(keys); i++){
|
init();
|
||||||
init();
|
im.l = getlang(LangJP);
|
||||||
im.l = getlang(LangJP);
|
im.pre = mkstr("reading");
|
||||||
im.pre = mkstr("reading");
|
setcandidates(1);
|
||||||
setcandidates(1);
|
sclear(&com);
|
||||||
sclear(&com);
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
CT_CHECK(t, keystroke(keys[i], 0, &com));
|
checkstr(t, "unselected candidate commits reading", "reading", &com);
|
||||||
checkstr(t, "unselected candidate commits reading",
|
CT_EQ_INT(t, 0, im.nkouho);
|
||||||
"reading", &com);
|
|
||||||
CT_EQ_INT(t, 0, im.nkouho);
|
init();
|
||||||
}
|
im.l = getlang(LangJP);
|
||||||
|
im.pre = mkstr("reading");
|
||||||
|
setcandidates(2);
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke(Ktab, 0, &com));
|
||||||
|
CT_EQ_INT(t, 0, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(Ktab, Mshift, &com));
|
||||||
|
CT_EQ_INT(t, 1, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "Tab steps through the candidates", "c2", &com);
|
||||||
|
|
||||||
candidatebegin(2);
|
candidatebegin(2);
|
||||||
im.pre = mkstr("reading");
|
im.pre = mkstr("reading");
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||||||
|
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||||||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
checkstr(t, "explicit candidate", "c2", &com);
|
checkstr(t, "explicit candidate", "c2", &com);
|
||||||
|
|
||||||
@@ -888,13 +902,28 @@ engine_candidate_completion(struct ct *t)
|
|||||||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
checkstr(t, "Enter without candidates", "reading", &com);
|
checkstr(t, "Enter without candidates", "reading", &com);
|
||||||
|
|
||||||
/* A digit with no such row on the page is an ordinary key. */
|
/* A digit types on until Space engages the list. */
|
||||||
|
candidatebegin(3);
|
||||||
|
im.pre = mkstr("reading");
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, !keystroke('2', 0, &com));
|
||||||
|
checkstr(t, "digit before the list is engaged", "reading", &com);
|
||||||
|
CT_EQ_INT(t, 0, im.nkouho);
|
||||||
|
candidatebegin(3);
|
||||||
|
im.pre = mkstr("reading");
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke(' ', 0, &com));
|
||||||
|
CT_CHECK(t, keystroke('2', 0, &com));
|
||||||
|
checkstr(t, "digit after the list is engaged", "c2", &com);
|
||||||
|
|
||||||
|
/* A digit with no such row on the page is an ordinary key: it
|
||||||
|
* confirms the candidate paged to and passes on. */
|
||||||
candidatebegin(32);
|
candidatebegin(32);
|
||||||
im.pre = mkstr("reading");
|
im.pre = mkstr("reading");
|
||||||
im.sel = 27;
|
im.sel = 27;
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
CT_CHECK(t, !keystroke('9', 0, &com));
|
CT_CHECK(t, !keystroke('9', 0, &com));
|
||||||
checkstr(t, "unavailable short-page digit", "reading", &com);
|
checkstr(t, "unavailable short-page digit", "c28", &com);
|
||||||
CT_EQ_INT(t, 0, im.nkouho);
|
CT_EQ_INT(t, 0, im.nkouho);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -911,9 +940,8 @@ searchsave(Searchfix *f, int lang)
|
|||||||
f->search = search;
|
f->search = search;
|
||||||
f->dictlang = getlang(lang);
|
f->dictlang = getlang(lang);
|
||||||
f->dict = f->dictlang->dict;
|
f->dict = f->dictlang->dict;
|
||||||
f->activecap = activecap;
|
f->clientpre = clientpre;
|
||||||
f->lastdraw = lastdraw;
|
f->lastdraw = lastdraw;
|
||||||
f->candidatechosen = candidatechosen;
|
|
||||||
draindraw(nil);
|
draindraw(nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -940,7 +968,7 @@ emojibegin(Searchfix *f, int showpre)
|
|||||||
"c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12");
|
"c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12");
|
||||||
init();
|
init();
|
||||||
im.l = getlang(LangKO);
|
im.l = getlang(LangKO);
|
||||||
activecap = showpre ? 0 : Cclientpreedit;
|
clientpre = showpre ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -954,9 +982,8 @@ searchend(Searchfix *f)
|
|||||||
draindraw(nil);
|
draindraw(nil);
|
||||||
im = f->im;
|
im = f->im;
|
||||||
search = f->search;
|
search = f->search;
|
||||||
activecap = f->activecap;
|
clientpre = f->clientpre;
|
||||||
lastdraw = f->lastdraw;
|
lastdraw = f->lastdraw;
|
||||||
candidatechosen = f->candidatechosen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -975,14 +1002,14 @@ engine_popup_preedit_capability(struct ct *t)
|
|||||||
init();
|
init();
|
||||||
im.l = jp;
|
im.l = jp;
|
||||||
draindraw(nil);
|
draindraw(nil);
|
||||||
ownerrequestcap(&client, Cclientpreedit, Keypress, 'k', 0);
|
ownerrequestcap(&client, 1, Keypress, 'k', 0);
|
||||||
ownerrequestcap(&client, Cclientpreedit, Keypress, 'a', 0);
|
ownerrequestcap(&client, 1, Keypress, 'a', 0);
|
||||||
CT_EQ_INT(t, 1, im.nkouho);
|
CT_EQ_INT(t, 1, im.nkouho);
|
||||||
redraw();
|
redraw();
|
||||||
if(CT_CHECK(t, draindraw(&dc) > 0)){
|
if(CT_CHECK(t, draindraw(&dc) > 0)){
|
||||||
CT_EQ_INT(t, 0, dc.pre.n);
|
CT_EQ_INT(t, 0, dc.pre.n);
|
||||||
CT_EQ_INT(t, 1, dc.nkouho);
|
CT_EQ_INT(t, 1, dc.nkouho);
|
||||||
CT_EQ_INT(t, 0, dc.sel);
|
CT_EQ_INT(t, -1, dc.sel);
|
||||||
checkstr(t, "client-preedit candidate", "家", &dc.kouho[0]);
|
checkstr(t, "client-preedit candidate", "家", &dc.kouho[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -992,37 +1019,37 @@ engine_popup_preedit_capability(struct ct *t)
|
|||||||
if(CT_CHECK(t, draindraw(&dc) > 0)){
|
if(CT_CHECK(t, draindraw(&dc) > 0)){
|
||||||
checkstr(t, "popup preedit", "か", &dc.pre);
|
checkstr(t, "popup preedit", "か", &dc.pre);
|
||||||
CT_EQ_INT(t, 1, dc.nkouho);
|
CT_EQ_INT(t, 1, dc.nkouho);
|
||||||
CT_EQ_INT(t, 0, dc.sel);
|
CT_EQ_INT(t, -1, dc.sel);
|
||||||
checkstr(t, "popup candidate", "家", &dc.kouho[0]);
|
checkstr(t, "popup candidate", "家", &dc.kouho[0]);
|
||||||
}
|
}
|
||||||
CT_EQ_INT(t, 1, im.nkouho);
|
CT_EQ_INT(t, 1, im.nkouho);
|
||||||
CT_EQ_INT(t, 0, im.sel);
|
CT_EQ_INT(t, -1, im.sel);
|
||||||
checkstr(t, "retained popup candidate", "家", &im.kouho[0]);
|
checkstr(t, "retained popup candidate", "家", &im.kouho[0]);
|
||||||
|
|
||||||
kres = ownerrequestcap(&client, 0, Keycap, 0, 0);
|
kres = ownerrequestcap(&client, 0, Keycap, 0, 0);
|
||||||
CT_CHECK(t, kres.eaten);
|
CT_CHECK(t, kres.eaten);
|
||||||
CT_EQ_INT(t, 0, draindraw(nil));
|
CT_EQ_INT(t, 0, draindraw(nil));
|
||||||
|
|
||||||
kres = ownerrequestcap(&inactive, Cclientpreedit, Keycap, 0, 0);
|
kres = ownerrequestcap(&inactive, 1, Keycap, 0, 0);
|
||||||
CT_CHECK(t, !kres.eaten);
|
CT_CHECK(t, !kres.eaten);
|
||||||
CT_EQ_INT(t, 0, kres.preedit.n);
|
CT_EQ_INT(t, 0, kres.preedit.n);
|
||||||
CT_EQ_PTR(t, &client, activeowner);
|
CT_EQ_PTR(t, &client, activeowner);
|
||||||
CT_EQ_INT(t, 0, activecap & Cclientpreedit);
|
CT_EQ_INT(t, 0, clientpre);
|
||||||
CT_EQ_INT(t, 0, draindraw(nil));
|
CT_EQ_INT(t, 0, draindraw(nil));
|
||||||
|
|
||||||
kres = ownerrequestcap(&client, Cclientpreedit, Keycap, 0, 0);
|
kres = ownerrequestcap(&client, 1, Keycap, 0, 0);
|
||||||
CT_CHECK(t, kres.eaten);
|
CT_CHECK(t, kres.eaten);
|
||||||
checkstr(t, "restored client preedit", "か", &kres.preedit);
|
checkstr(t, "restored client preedit", "か", &kres.preedit);
|
||||||
if(CT_CHECK(t, draindraw(&dc) > 0)){
|
if(CT_CHECK(t, draindraw(&dc) > 0)){
|
||||||
CT_EQ_INT(t, 0, dc.pre.n);
|
CT_EQ_INT(t, 0, dc.pre.n);
|
||||||
CT_EQ_INT(t, 1, dc.nkouho);
|
CT_EQ_INT(t, 1, dc.nkouho);
|
||||||
CT_EQ_INT(t, 0, dc.sel);
|
CT_EQ_INT(t, -1, dc.sel);
|
||||||
checkstr(t, "restored client candidate", "家", &dc.kouho[0]);
|
checkstr(t, "restored client candidate", "家", &dc.kouho[0]);
|
||||||
}
|
}
|
||||||
CT_EQ_INT(t, 1, im.nkouho);
|
CT_EQ_INT(t, 1, im.nkouho);
|
||||||
CT_EQ_INT(t, 0, im.sel);
|
CT_EQ_INT(t, -1, im.sel);
|
||||||
checkstr(t, "retained client candidate", "家", &im.kouho[0]);
|
checkstr(t, "retained client candidate", "家", &im.kouho[0]);
|
||||||
kres = ownerrequestcap(&client, Cclientpreedit, Keycap, 0, 0);
|
kres = ownerrequestcap(&client, 1, Keycap, 0, 0);
|
||||||
CT_CHECK(t, kres.eaten);
|
CT_CHECK(t, kres.eaten);
|
||||||
CT_EQ_INT(t, 0, draindraw(nil));
|
CT_EQ_INT(t, 0, draindraw(nil));
|
||||||
|
|
||||||
@@ -1061,7 +1088,17 @@ engine_japanese_readings(struct ct *t)
|
|||||||
} cases[] = {
|
} cases[] = {
|
||||||
{ "nya", "にゃ" },
|
{ "nya", "にゃ" },
|
||||||
{ "n'ya", "んや" },
|
{ "n'ya", "んや" },
|
||||||
{ "nnya", "んにゃ" },
|
{ "nnya", "んや" },
|
||||||
|
{ "nnnya", "んにゃ" },
|
||||||
|
{ "konnnichiha", "こんにちは" },
|
||||||
|
{ "wi", "うぃ" },
|
||||||
|
{ "vu", "ゔ" },
|
||||||
|
{ "xtu", "っ" },
|
||||||
|
{ "axtu", "あっ" },
|
||||||
|
{ "lla", "っぁ" },
|
||||||
|
{ "matcha", "まっちゃ" },
|
||||||
|
{ "zi", "じ" },
|
||||||
|
{ "[", "「" },
|
||||||
{ "kin'youbi", "きんようび" },
|
{ "kin'youbi", "きんようび" },
|
||||||
};
|
};
|
||||||
static const struct {
|
static const struct {
|
||||||
@@ -1091,7 +1128,7 @@ engine_japanese_readings(struct ct *t)
|
|||||||
}
|
}
|
||||||
for(i = 0; i < nelem(replay); i++){
|
for(i = 0; i < nelem(replay); i++){
|
||||||
raw = mkstr(replay[i].raw);
|
raw = mkstr(replay[i].raw);
|
||||||
transstr(getlang(LangJP), &raw, &shown);
|
transstr(getlang(LangJP), nil, &raw, &shown);
|
||||||
checkstr(t, replay[i].raw, replay[i].want, &shown);
|
checkstr(t, replay[i].raw, replay[i].want, &shown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1116,21 +1153,143 @@ engine_japanese_candidates(struct ct *t)
|
|||||||
checkstr(t, "complete Japanese reading", "かんじ", &shown);
|
checkstr(t, "complete Japanese reading", "かんじ", &shown);
|
||||||
if(!CT_EQ_INT(t, 2, im.nkouho))
|
if(!CT_EQ_INT(t, 2, im.nkouho))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
CT_EQ_INT(t, 0, im.sel);
|
CT_EQ_INT(t, -1, im.sel);
|
||||||
checkstr(t, "candidate one", "漢字", &im.kouho[0]);
|
checkstr(t, "candidate one", "漢字", &im.kouho[0]);
|
||||||
checkstr(t, "candidate two", "幹事", &im.kouho[1]);
|
checkstr(t, "candidate two", "幹事", &im.kouho[1]);
|
||||||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||||||
|
CT_EQ_INT(t, 0, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||||||
CT_EQ_INT(t, 1, im.sel);
|
CT_EQ_INT(t, 1, im.sel);
|
||||||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
checkstr(t, "selected Kanji", "幹事", &com);
|
checkstr(t, "selected Kanji", "幹事", &com);
|
||||||
CT_EQ_INT(t, 0, im.nkouho);
|
CT_EQ_INT(t, 0, im.nkouho);
|
||||||
|
/* Space converts: it steps through the candidates and wraps;
|
||||||
|
* typing on takes the chosen one; Backspace goes back. */
|
||||||
|
if(!typekeys(t, "kanji", &com))
|
||||||
|
goto cleanup;
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke(' ', 0, &com));
|
||||||
|
CT_EQ_INT(t, 0, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(' ', 0, &com));
|
||||||
|
CT_EQ_INT(t, 1, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(' ', 0, &com));
|
||||||
|
CT_EQ_INT(t, 2, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(' ', 0, &com));
|
||||||
|
CT_EQ_INT(t, 0, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(' ', Mshift, &com));
|
||||||
|
CT_EQ_INT(t, 2, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
CT_EQ_INT(t, -1, im.sel);
|
||||||
|
CT_EQ_INT(t, 3, im.nkouho);
|
||||||
|
CT_CHECK(t, keystroke(' ', 0, &com));
|
||||||
|
CT_CHECK(t, keystroke('w', 0, &com));
|
||||||
|
CT_CHECK(t, keystroke('o', 0, &com));
|
||||||
|
checkstr(t, "typing on takes the candidate", "漢字", &com);
|
||||||
|
impre(&im, &shown);
|
||||||
|
checkstr(t, "reading after the candidate", "を", &shown);
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke(' ', 0, &com));
|
||||||
|
if(CT_EQ_INT(t, 1, im.nkouho))
|
||||||
|
checkstr(t, "Space offers Katakana without candidates", "ヲ",
|
||||||
|
&im.kouho[0]);
|
||||||
|
CT_EQ_INT(t, 0, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "Katakana committed", "ヲ", &com);
|
||||||
|
CT_CHECK(t, !keystroke(' ', 0, &com));
|
||||||
|
sclear(&com);
|
||||||
|
if(!typekeys(t, "kanji", &com))
|
||||||
|
goto cleanup;
|
||||||
|
CT_EQ_INT(t, 2, im.nkouho);
|
||||||
|
CT_CHECK(t, keystroke(' ', 0, &com));
|
||||||
|
if(CT_EQ_INT(t, 3, im.nkouho))
|
||||||
|
checkstr(t, "Katakana after the candidates", "カンジ",
|
||||||
|
&im.kouho[2]);
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
/* SKK's okuri-ari entries convert verbs: stem plus okurigana. */
|
||||||
|
setdict(jp->dict, "かk", "書 描");
|
||||||
|
setdict(jp->dict, "かく", "確");
|
||||||
|
if(!typekeys(t, "kaku", &com))
|
||||||
|
goto cleanup;
|
||||||
|
if(CT_EQ_INT(t, 3, im.nkouho)){
|
||||||
|
checkstr(t, "exact candidate first", "確", &im.kouho[0]);
|
||||||
|
checkstr(t, "okurigana put back", "書く", &im.kouho[1]);
|
||||||
|
checkstr(t, "second okuri candidate", "描く", &im.kouho[2]);
|
||||||
|
}
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
setdict(jp->dict, "おおk", "大");
|
||||||
|
if(!typekeys(t, "ookii", &com))
|
||||||
|
goto cleanup;
|
||||||
|
if(CT_EQ_INT(t, 1, im.nkouho))
|
||||||
|
checkstr(t, "two-kana okurigana", "大きい", &im.kouho[0]);
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
/* Shift on the okurigana asks for that split alone, first. */
|
||||||
|
sclear(&com);
|
||||||
|
if(!typekeys(t, "ka", &com))
|
||||||
|
goto cleanup;
|
||||||
|
CT_CHECK(t, keystroke('K', Mshift, &com));
|
||||||
|
CT_CHECK(t, keystroke('u', 0, &com));
|
||||||
|
if(CT_EQ_INT(t, 3, im.nkouho)){
|
||||||
|
checkstr(t, "marked okurigana first", "書く", &im.kouho[0]);
|
||||||
|
checkstr(t, "then the second", "描く", &im.kouho[1]);
|
||||||
|
checkstr(t, "then the reading's own", "確", &im.kouho[2]);
|
||||||
|
}
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
setdict(jp->dict, "いt", "言 行");
|
||||||
|
setdict(jp->dict, "いっt", "言 行");
|
||||||
|
if(!typekeys(t, "itta", &com))
|
||||||
|
goto cleanup;
|
||||||
|
if(CT_EQ_INT(t, 2, im.nkouho)){
|
||||||
|
checkstr(t, "sokuon written in kana", "言った", &im.kouho[0]);
|
||||||
|
checkstr(t, "sokuon written in kana", "行った", &im.kouho[1]);
|
||||||
|
}
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
/* A romaji typo stays in the reading for Backspace to mend. */
|
||||||
|
if(!typekeys(t, "kanjr", &com))
|
||||||
|
goto cleanup;
|
||||||
|
impre(&im, &shown);
|
||||||
|
checkstr(t, "typo kept", "かんjr", &shown);
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
impre(&im, &shown);
|
||||||
|
checkstr(t, "typo half mended", "かんj", &shown);
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
if(!typekeys(t, "ji", &com))
|
||||||
|
goto cleanup;
|
||||||
|
impre(&im, &shown);
|
||||||
|
checkstr(t, "typo mended", "かんじ", &shown);
|
||||||
|
CT_EQ_INT(t, 2, im.nkouho);
|
||||||
|
CT_CHECK(t, keystroke('0', 0, &com));
|
||||||
|
checkstr(t, "0 commits the reading", "かんじ", &com);
|
||||||
|
sclear(&com);
|
||||||
|
if(!typekeys(t, "wo", &com))
|
||||||
|
goto cleanup;
|
||||||
|
CT_CHECK(t, keystroke('0', 0, &com));
|
||||||
|
checkstr(t, "0 commits a reading without candidates", "を", &com);
|
||||||
|
sclear(&com);
|
||||||
|
if(!typekeys(t, "kanji", &com))
|
||||||
|
goto cleanup;
|
||||||
|
CT_CHECK(t, keystroke(' ', 0, &com));
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
CT_EQ_INT(t, -1, im.sel);
|
||||||
|
impre(&im, &shown);
|
||||||
|
checkstr(t, "Escape goes back to the reading", "かんじ", &shown);
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
|
CT_EQ_INT(t, 0, im.raw.n);
|
||||||
|
CT_EQ_INT(t, 0, im.nkouho);
|
||||||
|
sclear(&com);
|
||||||
|
if(!typekeys(t, "kanji", &com))
|
||||||
|
goto cleanup;
|
||||||
|
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||||||
|
CT_CHECK(t, keystroke('s', Mctrl, &com));
|
||||||
|
checkstr(t, "language switch takes the candidate", "漢字", &com);
|
||||||
|
CT_EQ_INT(t, LangKO, im.l->lang);
|
||||||
|
im.l = jp;
|
||||||
/* Backspace looks a complete shorter reading up again. */
|
/* Backspace looks a complete shorter reading up again. */
|
||||||
setdict(jp->dict, "かん", "缶");
|
setdict(jp->dict, "かん", "缶");
|
||||||
if(!typekeys(t, "kanji", &com))
|
if(!typekeys(t, "kanji", &com))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
CT_EQ_INT(t, 0, im.nkouho);
|
|
||||||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
|
||||||
if(CT_EQ_INT(t, 1, im.nkouho))
|
if(CT_EQ_INT(t, 1, im.nkouho))
|
||||||
checkstr(t, "shorter reading candidate", "缶", &im.kouho[0]);
|
checkstr(t, "shorter reading candidate", "缶", &im.kouho[0]);
|
||||||
cleanup:
|
cleanup:
|
||||||
@@ -1149,13 +1308,18 @@ engine_japanese_backspace_and_boundaries(struct ct *t)
|
|||||||
if(typekeys(t, "kanji", &com)){
|
if(typekeys(t, "kanji", &com)){
|
||||||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
impre(&im, &shown);
|
impre(&im, &shown);
|
||||||
checkstr(t, "backspace pending i", "かんj", &shown);
|
checkstr(t, "backspace deletes じ", "かん", &shown);
|
||||||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
impre(&im, &shown);
|
impre(&im, &shown);
|
||||||
checkstr(t, "backspace pending j", "かん", &shown);
|
checkstr(t, "backspace deletes ん", "か", &shown);
|
||||||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
if(typekeys(t, "kyu", &com)){
|
||||||
impre(&im, &shown);
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
checkstr(t, "backspace accumulated kana", "か", &shown);
|
impre(&im, &shown);
|
||||||
|
checkstr(t, "backspace deletes ゅ", "かき", &shown);
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
CT_CHECK(t, !haspre(&im));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
@@ -1179,8 +1343,8 @@ engine_japanese_backspace_and_boundaries(struct ct *t)
|
|||||||
im.l = getlang(LangJP);
|
im.l = getlang(LangJP);
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
if(typekeys(t, "kanji", &com)){
|
if(typekeys(t, "kanji", &com)){
|
||||||
CT_CHECK(t, keystroke(' ', 0, &com));
|
CT_CHECK(t, !keystroke(L'日', 0, &com));
|
||||||
checkstr(t, "real input boundary", "かんじ ", &com);
|
checkstr(t, "real input boundary", "かんじ", &com);
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
@@ -1238,6 +1402,8 @@ engine_emoji_single_candidate(struct ct *t)
|
|||||||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
CT_CHECK(t, search.lang);
|
CT_CHECK(t, search.lang);
|
||||||
CT_EQ_INT(t, LangKO, im.l->lang);
|
CT_EQ_INT(t, LangKO, im.l->lang);
|
||||||
|
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||||||
|
checkstr(t, "empty search shows itself", "☺", &dc.pre);
|
||||||
if(typekeys(t, "alpha", &com)){
|
if(typekeys(t, "alpha", &com)){
|
||||||
checkstr(t, "no automatic commit", "", &com);
|
checkstr(t, "no automatic commit", "", &com);
|
||||||
checkstr(t, "raw English query", "alpha", &search.text);
|
checkstr(t, "raw English query", "alpha", &search.text);
|
||||||
@@ -1271,13 +1437,14 @@ engine_emoji_queries(struct ct *t)
|
|||||||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
CT_CHECK(t, keystroke('a', 0, &com));
|
CT_CHECK(t, keystroke('a', 0, &com));
|
||||||
checkstr(t, "raw query wins", "a", &search.text);
|
checkstr(t, "raw query wins", "a", &search.text);
|
||||||
CT_EQ_INT(t, 3, im.nkouho);
|
CT_EQ_INT(t, 4, im.nkouho);
|
||||||
checkstr(t, "raw candidate", "A", &im.kouho[0]);
|
checkstr(t, "raw candidate", "A", &im.kouho[0]);
|
||||||
checkstr(t, "deduplicated candidate", "B", &im.kouho[1]);
|
checkstr(t, "deduplicated candidate", "B", &im.kouho[1]);
|
||||||
checkstr(t, "local candidate", "C", &im.kouho[2]);
|
checkstr(t, "raw prefix candidate", "α", &im.kouho[2]);
|
||||||
|
checkstr(t, "local candidate", "C", &im.kouho[3]);
|
||||||
CT_CHECK(t, draindraw(&dc) > 0);
|
CT_CHECK(t, draindraw(&dc) > 0);
|
||||||
checkstr(t, "prefix popup query", "a", &dc.pre);
|
checkstr(t, "prefix popup query", "a", &dc.pre);
|
||||||
CT_EQ_INT(t, 3, dc.nkouho);
|
CT_EQ_INT(t, 4, dc.nkouho);
|
||||||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
|
||||||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
@@ -1404,6 +1571,7 @@ engine_emoji_digit_aliases(struct ct *t)
|
|||||||
checkstr(t, "zero alias", "⁰", &com);
|
checkstr(t, "zero alias", "⁰", &com);
|
||||||
|
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
|
im.l = getlang(LangEN);
|
||||||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
if(typekeys(t, "smile", &com)){
|
if(typekeys(t, "smile", &com)){
|
||||||
CT_CHECK(t, keystroke('9', 0, &com));
|
CT_CHECK(t, keystroke('9', 0, &com));
|
||||||
@@ -1460,6 +1628,7 @@ engine_search_candidate_keys(struct ct *t)
|
|||||||
Str com;
|
Str com;
|
||||||
|
|
||||||
emojibegin(&f, 0);
|
emojibegin(&f, 0);
|
||||||
|
im.l = getlang(LangEN);
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
if(typekeys(t, "smile", &com)){
|
if(typekeys(t, "smile", &com)){
|
||||||
@@ -1510,22 +1679,26 @@ engine_emoji_preedit_languages(struct ct *t)
|
|||||||
im.l = getlang(LangJP);
|
im.l = getlang(LangJP);
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
if(typekeys(t, "ka", &com)){
|
if(typekeys(t, "ka", &com)){
|
||||||
CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com));
|
CT_CHECK(t, !keystroke(0, 0, &com));
|
||||||
impre(&im, &shown);
|
impre(&im, &shown);
|
||||||
checkstr(t, "modifier preserves Japanese", "か", &shown);
|
checkstr(t, "modifier preserves Japanese", "か", &shown);
|
||||||
CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com));
|
CT_CHECK(t, keystroke('E', Mctrl, &com));
|
||||||
checkstr(t, "committed Japanese preedit", "か", &com);
|
checkstr(t, "committed Japanese preedit", "か", &com);
|
||||||
CT_CHECK(t, search.lang);
|
CT_CHECK(t, search.lang);
|
||||||
CT_EQ_INT(t, LangJP, im.l->lang);
|
CT_EQ_INT(t, LangJP, im.l->lang);
|
||||||
CT_CHECK(t, !keystroke('E', Mctrl|Malt, &com));
|
CT_CHECK(t, !keystroke('E', Mctrl|Malt, &com));
|
||||||
CT_EQ_INT(t, 0, search.lang);
|
CT_EQ_INT(t, 0, search.lang);
|
||||||
CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com));
|
CT_CHECK(t, keystroke('E', Mctrl, &com));
|
||||||
CT_CHECK(t, !keystroke('E', Mctrl|Msuper, &com));
|
CT_CHECK(t, !keystroke('E', Mctrl|Msuper, &com));
|
||||||
CT_EQ_INT(t, 0, search.lang);
|
CT_EQ_INT(t, 0, search.lang);
|
||||||
CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com));
|
CT_CHECK(t, keystroke('E', Mctrl, &com));
|
||||||
|
CT_CHECK(t, !keystroke('V', Mctrl|Mshift, &com));
|
||||||
|
CT_EQ_INT(t, 0, search.lang);
|
||||||
|
CT_EQ_INT(t, LangJP, im.l->lang);
|
||||||
|
CT_CHECK(t, keystroke('E', Mctrl, &com));
|
||||||
CT_EQ_INT(t, LangJP, im.l->lang);
|
CT_EQ_INT(t, LangJP, im.l->lang);
|
||||||
CT_CHECK(t, keystroke('a', 0, &com));
|
CT_CHECK(t, keystroke('a', 0, &com));
|
||||||
CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com));
|
CT_CHECK(t, !keystroke(0, 0, &com));
|
||||||
CT_CHECK(t, search.lang);
|
CT_CHECK(t, search.lang);
|
||||||
checkstr(t, "modifier preserves search", "a", &search.text);
|
checkstr(t, "modifier preserves search", "a", &search.text);
|
||||||
CT_CHECK(t, !keystroke('P', Mctrl|Mshift, &com));
|
CT_CHECK(t, !keystroke('P', Mctrl|Mshift, &com));
|
||||||
@@ -1553,14 +1726,14 @@ engine_emoji_preedit_languages(struct ct *t)
|
|||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
im.l = &testvi;
|
im.l = getlang(LangVI);
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
if(typekeys(t, "as", &com)){
|
if(typekeys(t, "as", &com)){
|
||||||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
checkstr(t, "committed Telex preedit", "á", &com);
|
checkstr(t, "committed Telex preedit", "á", &com);
|
||||||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
CT_CHECK(t, !search.lang);
|
CT_CHECK(t, !search.lang);
|
||||||
CT_EQ_PTR(t, &testvi, im.l);
|
CT_EQ_PTR(t, getlang(LangVI), im.l);
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
if(typekeys(t, "as", &com)){
|
if(typekeys(t, "as", &com)){
|
||||||
impre(&im, &shown);
|
impre(&im, &shown);
|
||||||
@@ -1583,11 +1756,13 @@ engine_emoji_start_and_unknown(struct ct *t)
|
|||||||
sclear(&com);
|
sclear(&com);
|
||||||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||||||
if(typekeys(t, "xyz", &com)){
|
if(typekeys(t, "xyz", &com)){
|
||||||
|
/* Keys that match nothing show what they type in the language. */
|
||||||
CT_CHECK(t, !keystroke(Kspec|0x51, 0, &com));
|
CT_CHECK(t, !keystroke(Kspec|0x51, 0, &com));
|
||||||
checkstr(t, "special commits shown query", "xyz", &com);
|
checkstr(t, "special commits shown query", "툨", &com);
|
||||||
CT_CHECK(t, !search.lang);
|
CT_CHECK(t, !search.lang);
|
||||||
CT_EQ_INT(t, LangKO, im.l->lang);
|
CT_EQ_INT(t, LangKO, im.l->lang);
|
||||||
}
|
}
|
||||||
|
im.l = getlang(LangEN);
|
||||||
|
|
||||||
/* A language switch commits the query like any other pending text. */
|
/* A language switch commits the query like any other pending text. */
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
@@ -1597,7 +1772,6 @@ engine_emoji_start_and_unknown(struct ct *t)
|
|||||||
checkstr(t, "language switch commits query", "xy", &com);
|
checkstr(t, "language switch commits query", "xy", &com);
|
||||||
CT_CHECK(t, !search.lang);
|
CT_CHECK(t, !search.lang);
|
||||||
CT_EQ_INT(t, LangEN, im.l->lang);
|
CT_EQ_INT(t, LangEN, im.l->lang);
|
||||||
im.l = getlang(LangKO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* So do the search keys, ending or changing the search. */
|
/* So do the search keys, ending or changing the search. */
|
||||||
@@ -1768,6 +1942,25 @@ engine_hanja_backspace(struct ct *t)
|
|||||||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
CT_EQ_INT(t, 0, search.lang);
|
CT_EQ_INT(t, 0, search.lang);
|
||||||
CT_EQ_INT(t, LangKO, im.l->lang);
|
CT_EQ_INT(t, LangKO, im.l->lang);
|
||||||
|
/* Escape gives the seed back; keys typed compose from it. */
|
||||||
|
if(!typekeys(t, "gks", &com))
|
||||||
|
goto cleanup;
|
||||||
|
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
CT_EQ_INT(t, 0, search.lang);
|
||||||
|
checkstr(t, "seed pending again", "한", &im.pre);
|
||||||
|
checkstr(t, "nothing committed", "", &com);
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||||||
|
checkstr(t, "seed 하", "하", &search.text);
|
||||||
|
CT_CHECK(t, keystroke('s', 0, &com));
|
||||||
|
checkstr(t, "key composes with the seed", "한", &search.text);
|
||||||
|
CT_EQ_INT(t, 2, im.nkouho);
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
checkstr(t, "seed alone comes back", "하", &im.pre);
|
||||||
|
CT_CHECK(t, !keystroke(Kesc, 0, &com));
|
||||||
|
checkstr(t, "Korean Escape commits", "하", &com);
|
||||||
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
cleanup:
|
cleanup:
|
||||||
searchend(&f);
|
searchend(&f);
|
||||||
}
|
}
|
||||||
@@ -1805,6 +1998,40 @@ engine_hanja_input_languages(struct ct *t)
|
|||||||
checkstr(t, "Japanese Hanja commit", "か", &com);
|
checkstr(t, "Japanese Hanja commit", "か", &com);
|
||||||
CT_EQ_INT(t, LangJP, im.l->lang);
|
CT_EQ_INT(t, LangJP, im.l->lang);
|
||||||
|
|
||||||
|
/* The keyboard's own keys: 한자 seeds like Ctrl+H, 한/영 toggles. */
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangKO);
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "gks", &com)){
|
||||||
|
CT_CHECK(t, keystroke(Khanja, 0, &com));
|
||||||
|
checkstr(t, "Hanja key seed", "한", &search.text);
|
||||||
|
CT_EQ_INT(t, 2, im.nkouho);
|
||||||
|
CT_CHECK(t, keystroke(Khangul, 0, &com));
|
||||||
|
checkstr(t, "Hangul key commits the query", "한", &com);
|
||||||
|
CT_EQ_INT(t, 0, search.lang);
|
||||||
|
CT_EQ_INT(t, LangEN, im.l->lang);
|
||||||
|
CT_CHECK(t, keystroke(Khangul, 0, &com));
|
||||||
|
CT_EQ_INT(t, LangKO, im.l->lang);
|
||||||
|
CT_CHECK(t, keystroke(Kzenkaku, 0, &com));
|
||||||
|
CT_EQ_INT(t, LangJP, im.l->lang);
|
||||||
|
CT_CHECK(t, keystroke(Kkana, 0, &com));
|
||||||
|
CT_EQ_INT(t, LangJPK, im.l->lang);
|
||||||
|
CT_CHECK(t, keystroke(Kkana, 0, &com));
|
||||||
|
CT_EQ_INT(t, LangJP, im.l->lang);
|
||||||
|
CT_CHECK(t, keystroke(Kzenkaku, 0, &com));
|
||||||
|
CT_EQ_INT(t, LangEN, im.l->lang);
|
||||||
|
CT_CHECK(t, keystroke(Khenkan, 0, &com));
|
||||||
|
CT_EQ_INT(t, LangJP, im.l->lang);
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "ka", &com)){
|
||||||
|
CT_CHECK(t, keystroke(Khenkan, 0, &com));
|
||||||
|
CT_EQ_INT(t, 0, im.sel);
|
||||||
|
CT_CHECK(t, keystroke(Kmuhenkan, 0, &com));
|
||||||
|
checkstr(t, "無変換 commits", "カ", &com);
|
||||||
|
CT_EQ_INT(t, LangEN, im.l->lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
im.l = getlang(LangEN);
|
im.l = getlang(LangEN);
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
@@ -1856,8 +2083,6 @@ engine_randomized_stress(struct ct *t)
|
|||||||
};
|
};
|
||||||
static Rune modes[] = { 't', 'n', 'k', 's', 'v' };
|
static Rune modes[] = { 't', 'n', 'k', 's', 'v' };
|
||||||
Searchfix f;
|
Searchfix f;
|
||||||
Lang *vi;
|
|
||||||
Trie *vimap;
|
|
||||||
Keyres res;
|
Keyres res;
|
||||||
Caret pos;
|
Caret pos;
|
||||||
u32int rnd, mod;
|
u32int rnd, mod;
|
||||||
@@ -1865,11 +2090,6 @@ engine_randomized_stress(struct ct *t)
|
|||||||
void *owner, *owners[3];
|
void *owner, *owners[3];
|
||||||
int i, j, op;
|
int i, j, op;
|
||||||
|
|
||||||
vi = getlang(LangVI);
|
|
||||||
if(!CT_CHECK(t, vi != nil))
|
|
||||||
return;
|
|
||||||
vimap = vi->map;
|
|
||||||
vi->map = testvi.map;
|
|
||||||
emojibegin(&f, 0);
|
emojibegin(&f, 0);
|
||||||
owners[0] = &contexts[0];
|
owners[0] = &contexts[0];
|
||||||
owners[1] = &contexts[1];
|
owners[1] = &contexts[1];
|
||||||
@@ -1914,7 +2134,7 @@ engine_randomized_stress(struct ct *t)
|
|||||||
res = ownerrequestat(owner, Keycaret, 0, 0, &pos);
|
res = ownerrequestat(owner, Keycaret, 0, 0, &pos);
|
||||||
else if(op == 24)
|
else if(op == 24)
|
||||||
res = ownerrequest(owner, Keypress,
|
res = ownerrequest(owner, Keypress,
|
||||||
Kmodfirst + ((rnd >> 20) & 7), 0);
|
0, 0);
|
||||||
else if(op == 25)
|
else if(op == 25)
|
||||||
res = ownerrequestat(owner, Keypress, 'p', Mctrl, &pos);
|
res = ownerrequestat(owner, Keypress, 'p', Mctrl, &pos);
|
||||||
else if(op == 26)
|
else if(op == 26)
|
||||||
@@ -1945,7 +2165,6 @@ engine_randomized_stress(struct ct *t)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
searchend(&f);
|
searchend(&f);
|
||||||
vi->map = vimap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -1960,7 +2179,7 @@ engine_full_boundary_passthrough(struct ct *t)
|
|||||||
im.pre.r[i] = L'あ';
|
im.pre.r[i] = L'あ';
|
||||||
im.pre.n = Maxrunes;
|
im.pre.n = Maxrunes;
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
CT_CHECK(t, !keystroke(' ', 0, &com));
|
CT_CHECK(t, !keystroke(L'日', 0, &com));
|
||||||
CT_EQ_INT(t, Maxrunes, com.n);
|
CT_EQ_INT(t, Maxrunes, com.n);
|
||||||
for(i = 0; i < Maxrunes; i++)
|
for(i = 0; i < Maxrunes; i++)
|
||||||
CT_EQ_UINT(t, L'あ', com.r[i]);
|
CT_EQ_UINT(t, L'あ', com.r[i]);
|
||||||
@@ -1973,9 +2192,9 @@ engine_full_boundary_passthrough(struct ct *t)
|
|||||||
im.pre.r[i] = L'あ';
|
im.pre.r[i] = L'あ';
|
||||||
im.pre.n = Maxrunes-1;
|
im.pre.n = Maxrunes-1;
|
||||||
sclear(&com);
|
sclear(&com);
|
||||||
CT_CHECK(t, keystroke(' ', 0, &com));
|
CT_CHECK(t, !keystroke(L'日', 0, &com));
|
||||||
CT_EQ_INT(t, Maxrunes, com.n);
|
CT_EQ_INT(t, Maxrunes-1, com.n);
|
||||||
CT_EQ_UINT(t, ' ', com.r[Maxrunes-1]);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
|
|
||||||
/* Typing on at the limit commits the kana and keeps the syllable. */
|
/* Typing on at the limit commits the kana and keeps the syllable. */
|
||||||
init();
|
init();
|
||||||
|
|||||||
@@ -189,10 +189,7 @@ font_render(struct ct *t)
|
|||||||
buf = mem + Guard;
|
buf = mem + Guard;
|
||||||
fillpixels(buf, Testn, Colbg);
|
fillpixels(buf, Testn, Colbg);
|
||||||
a = mkstr("A");
|
a = mkstr("A");
|
||||||
if(!CT_CHECK(t, textinit())){
|
textinit();
|
||||||
free(mem);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for(i = 0; i < nelem(plain); i++)
|
for(i = 0; i < nelem(plain); i++)
|
||||||
checktext(t, buf, plain[i]);
|
checktext(t, buf, plain[i]);
|
||||||
|
|
||||||
@@ -257,6 +254,4 @@ font_render(struct ct *t)
|
|||||||
checkguards(t, mem);
|
checkguards(t, mem);
|
||||||
free(mem);
|
free(mem);
|
||||||
textclose();
|
textclose();
|
||||||
textclose();
|
|
||||||
CT_EQ_INT(t, 0, textwidth(&a));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -437,73 +437,6 @@ runquery(char *module, char *cache)
|
|||||||
return 1;
|
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
|
static void
|
||||||
pump(void)
|
pump(void)
|
||||||
@@ -656,16 +589,17 @@ main(int argc, char **argv)
|
|||||||
static const int badresponses[] = { Rbadcommit, Rbadpreedit };
|
static const int badresponses[] = { Rbadcommit, Rbadpreedit };
|
||||||
char root[] = "/tmp/strans-gtk.XXXXXX";
|
char root[] = "/tmp/strans-gtk.XXXXXX";
|
||||||
char runtime[320] = "", socket[384] = "", cache[384] = "";
|
char runtime[320] = "", socket[384] = "", cache[384] = "";
|
||||||
char display[32];
|
|
||||||
char *module;
|
char *module;
|
||||||
pid_t xvfb;
|
Daemon xvfb;
|
||||||
|
Live l;
|
||||||
int first, i, ox, oy, scale, closes, ok, rootmade, serverup;
|
int first, i, ox, oy, scale, closes, ok, rootmade, serverup;
|
||||||
int64_t stalledms, started;
|
int64_t stalledms, started;
|
||||||
|
|
||||||
testname = "gtk_live_test";
|
testname = "gtk_live_test";
|
||||||
ctx = oldctx = badctx = NULL;
|
ctx = oldctx = badctx = NULL;
|
||||||
top = NULL;
|
top = NULL;
|
||||||
xvfb = -1;
|
memset(&l, 0, sizeof l);
|
||||||
|
daemoninit(&xvfb, "Xvfb");
|
||||||
rootmade = 0;
|
rootmade = 0;
|
||||||
serverup = 0;
|
serverup = 0;
|
||||||
ok = 0;
|
ok = 0;
|
||||||
@@ -687,8 +621,8 @@ main(int argc, char **argv)
|
|||||||
Check(snprintf(cache, sizeof cache, "%s/immodules.cache", root) <
|
Check(snprintf(cache, sizeof cache, "%s/immodules.cache", root) <
|
||||||
(int)sizeof cache, "cache path is too long");
|
(int)sizeof cache, "cache path is too long");
|
||||||
Check(mkdir(runtime, 0700) == 0, "mkdir runtime: %s", strerror(errno));
|
Check(mkdir(runtime, 0700) == 0, "mkdir runtime: %s", strerror(errno));
|
||||||
Check(startxvfb(&xvfb, display, sizeof display), "start private Xvfb");
|
Check(startxvfb(&l, &xvfb, "Xvfb"), "start private Xvfb");
|
||||||
Check(setenv("DISPLAY", display, 1) == 0 &&
|
Check(setenv("DISPLAY", l.display, 1) == 0 &&
|
||||||
setenv("GDK_BACKEND", "x11", 1) == 0 &&
|
setenv("GDK_BACKEND", "x11", 1) == 0 &&
|
||||||
setenv("GDK_SCALE", "2", 1) == 0 &&
|
setenv("GDK_SCALE", "2", 1) == 0 &&
|
||||||
setenv("XDG_RUNTIME_DIR", runtime, 1) == 0,
|
setenv("XDG_RUNTIME_DIR", runtime, 1) == 0,
|
||||||
@@ -764,6 +698,26 @@ main(int argc, char **argv)
|
|||||||
Check(key(ctx, win, GDK_KEY_dead_acute) && key(ctx, win, GDK_KEY_e),
|
Check(key(ctx, win, GDK_KEY_dead_acute) && key(ctx, win, GDK_KEY_e),
|
||||||
"dead-key sequence was not consumed");
|
"dead-key sequence was not consumed");
|
||||||
Check(strcmp(log.commit, "é") == 0, "dead key committed %s", log.commit);
|
Check(strcmp(log.commit, "é") == 0, "dead key committed %s", log.commit);
|
||||||
|
|
||||||
|
/* A dead key over a preedit hands the pending text back first. */
|
||||||
|
setreply(&srv, 1, Rnormal, 0);
|
||||||
|
setpre(&srv, "하");
|
||||||
|
clearlog(&log);
|
||||||
|
Check(key(ctx, win, GDK_KEY_h), "dead-key setup key was not eaten");
|
||||||
|
clearevent(&log);
|
||||||
|
first = eventcount(&srv);
|
||||||
|
Check(!key(ctx, win, GDK_KEY_Shift_L), "a modifier press was eaten");
|
||||||
|
Check(eventcount(&srv) == first && strcmp(log.pre, "하") == 0,
|
||||||
|
"a modifier press disturbed the preedit");
|
||||||
|
setreply(&srv, 0, Rnormal, 0);
|
||||||
|
setpre(&srv, "");
|
||||||
|
Check(key(ctx, win, GDK_KEY_dead_acute) && key(ctx, win, GDK_KEY_e),
|
||||||
|
"dead key over a preedit was not consumed");
|
||||||
|
Check(getevent(&srv, first).type == Ereset,
|
||||||
|
"dead key did not hand the pending text back");
|
||||||
|
Check(strcmp(log.commit, "é") == 0 && log.pre[0] == '\0',
|
||||||
|
"dead key over a preedit committed %s (preedit %s)",
|
||||||
|
log.commit, log.pre);
|
||||||
clearlog(&log);
|
clearlog(&log);
|
||||||
Check(keystate(ctx, win, GDK_KEY_U, GDK_CONTROL_MASK|GDK_SHIFT_MASK),
|
Check(keystate(ctx, win, GDK_KEY_U, GDK_CONTROL_MASK|GDK_SHIFT_MASK),
|
||||||
"Unicode prefix was not consumed");
|
"Unicode prefix was not consumed");
|
||||||
@@ -931,7 +885,8 @@ out:
|
|||||||
rmdir(runtime);
|
rmdir(runtime);
|
||||||
if(rootmade)
|
if(rootmade)
|
||||||
rmdir(root);
|
rmdir(root);
|
||||||
stopxvfb(&xvfb);
|
stopdaemon(&xvfb);
|
||||||
|
closeerrors(&xvfb);
|
||||||
free(module);
|
free(module);
|
||||||
if(ok)
|
if(ok)
|
||||||
printf("gtk_live_test: ok (scale %d, stalled peer %lld ms)\n",
|
printf("gtk_live_test: ok (scale %d, stalled peer %lld ms)\n",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Testctrlmask = 1<<2,
|
Testctrlmask = 1<<2,
|
||||||
@@ -53,6 +52,9 @@ ibus_machine_id_fallback(struct ct *t)
|
|||||||
path[1] = fallback;
|
path[1] = fallback;
|
||||||
machineidfiles(got, sizeof got, path, nelem(path));
|
machineidfiles(got, sizeof got, path, nelem(path));
|
||||||
CT_EQ_STR(t, "fallback-machine-id", got);
|
CT_EQ_STR(t, "fallback-machine-id", got);
|
||||||
|
path[1] = primary;
|
||||||
|
machineidfiles(got, sizeof got, path, nelem(path));
|
||||||
|
CT_EQ_STR(t, "", got);
|
||||||
|
|
||||||
old = getenv("IBUS_ADDRESS_FILE");
|
old = getenv("IBUS_ADDRESS_FILE");
|
||||||
saved = old == nil ? nil : strdup(old);
|
saved = old == nil ? nil : strdup(old);
|
||||||
@@ -79,85 +81,6 @@ cleanup:
|
|||||||
CT_CHECK(t, rmdir(root) == 0);
|
CT_CHECK(t, rmdir(root) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
ibus_startup_requires_ownership(struct ct *t)
|
|
||||||
{
|
|
||||||
char root[] = "/tmp/strans-ibus-startup.XXXXXX";
|
|
||||||
char busdir[128], ibusdir[128], path[512], *old, *saved;
|
|
||||||
struct stat st;
|
|
||||||
int i, made, rv;
|
|
||||||
|
|
||||||
made = 0;
|
|
||||||
busdir[0] = '\0';
|
|
||||||
ibusdir[0] = '\0';
|
|
||||||
path[0] = '\0';
|
|
||||||
old = getenv("XDG_CONFIG_HOME");
|
|
||||||
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;
|
|
||||||
if(!CT_CHECK(t, snprintf(busdir, sizeof busdir, "%s/ibus/bus", root)
|
|
||||||
< (int)sizeof busdir) ||
|
|
||||||
!CT_CHECK(t, snprintf(ibusdir, sizeof ibusdir, "%s/ibus", root)
|
|
||||||
< (int)sizeof ibusdir) ||
|
|
||||||
!CT_CHECK(t, setenv("XDG_CONFIG_HOME", root, 1) == 0) ||
|
|
||||||
!CT_CHECK(t, buildaddrpath(path, sizeof path) == 0))
|
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
memset(watches, 0, sizeof watches);
|
|
||||||
for(i = 0; i < nelem(watches); i++)
|
|
||||||
watches[i] = (DBusWatch*)1;
|
|
||||||
nwatches = nelem(watches);
|
|
||||||
rv = ibusinit();
|
|
||||||
CT_EQ_INT(t, -1, rv);
|
|
||||||
CT_EQ_PTR(t, nil, srv);
|
|
||||||
CT_CHECK(t, lstat(path, &st) < 0 && errno == ENOENT);
|
|
||||||
if(rv == 0){
|
|
||||||
dbus_server_disconnect(srv);
|
|
||||||
dbus_server_unref(srv);
|
|
||||||
srv = nil;
|
|
||||||
unlinkaddr();
|
|
||||||
atexitdont(unlinkaddr);
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(watches, 0, sizeof watches);
|
|
||||||
nwatches = 0;
|
|
||||||
while(atexit(unlinkaddr))
|
|
||||||
continue;
|
|
||||||
CT_EQ_INT(t, -1, ibusinit());
|
|
||||||
CT_EQ_PTR(t, nil, srv);
|
|
||||||
CT_CHECK(t, lstat(path, &st) < 0 && errno == ENOENT);
|
|
||||||
cleanup:
|
|
||||||
if(srv != nil){
|
|
||||||
dbus_server_disconnect(srv);
|
|
||||||
dbus_server_unref(srv);
|
|
||||||
srv = nil;
|
|
||||||
}
|
|
||||||
unlinkaddr();
|
|
||||||
atexitdont(unlinkaddr);
|
|
||||||
memset(watches, 0, sizeof watches);
|
|
||||||
nwatches = 0;
|
|
||||||
addrfile[0] = '\0';
|
|
||||||
if(saved != nil){
|
|
||||||
setenv("XDG_CONFIG_HOME", saved, 1);
|
|
||||||
free(saved);
|
|
||||||
}else
|
|
||||||
unsetenv("XDG_CONFIG_HOME");
|
|
||||||
if(made){
|
|
||||||
if(path[0] != '\0')
|
|
||||||
unlink(path);
|
|
||||||
if(busdir[0] != '\0')
|
|
||||||
CT_CHECK(t, rmdir(busdir) == 0 || errno == ENOENT);
|
|
||||||
if(ibusdir[0] != '\0')
|
|
||||||
CT_CHECK(t, rmdir(ibusdir) == 0 || errno == ENOENT);
|
|
||||||
CT_CHECK(t, rmdir(root) == 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
ibusbegin(struct ct *t, Ibusfix *f)
|
ibusbegin(struct ct *t, Ibusfix *f)
|
||||||
{
|
{
|
||||||
@@ -213,7 +136,7 @@ nexttrace(struct ct *t, Ibusfix *f, int op, Ictx *ctx)
|
|||||||
CT_EQ_INT(t, op, req.op);
|
CT_EQ_INT(t, op, req.op);
|
||||||
CT_EQ_PTR(t, ctx, req.owner);
|
CT_EQ_PTR(t, ctx, req.owner);
|
||||||
if(ctx != nil)
|
if(ctx != nil)
|
||||||
CT_EQ_INT(t, ctx->cap & 1 ? Cclientpreedit : 0, req.cap);
|
CT_EQ_INT(t, clientpreedit(ctx), req.clientpre);
|
||||||
CT_EQ_PTR(t, replyc, req.reply);
|
CT_EQ_PTR(t, replyc, req.reply);
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
@@ -230,9 +153,10 @@ static Keyres
|
|||||||
contextkey(struct ct *t, Ibusfix *f, Ictx *ctx, u32int sym, u32int state)
|
contextkey(struct ct *t, Ibusfix *f, Ictx *ctx, u32int sym, u32int state)
|
||||||
{
|
{
|
||||||
Keyres res;
|
Keyres res;
|
||||||
|
char text[Maxutf];
|
||||||
|
|
||||||
memset(&res, 0, sizeof res);
|
memset(&res, 0, sizeof res);
|
||||||
CT_CHECK(t, processkey(ctx, sym, state, &res));
|
CT_CHECK(t, processkey(ctx, sym, state, text, sizeof text, &res));
|
||||||
nexttrace(t, f, Keypress, ctx);
|
nexttrace(t, f, Keypress, ctx);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -258,7 +182,7 @@ ibus_capability_policy(struct ct *t)
|
|||||||
checkstr(t, "preedit", "k", &res.preedit);
|
checkstr(t, "preedit", "k", &res.preedit);
|
||||||
CT_EQ_PTR(t, a, testengineowner());
|
CT_EQ_PTR(t, a, testengineowner());
|
||||||
|
|
||||||
a->cap = Cclientpreedit;
|
a->cap = Ibuscappreedit;
|
||||||
memset(&res, 0, sizeof res);
|
memset(&res, 0, sizeof res);
|
||||||
sendrequest(a, Keycap, 0, 0, &res);
|
sendrequest(a, Keycap, 0, 0, &res);
|
||||||
nexttrace(t, &f, Keycap, a);
|
nexttrace(t, &f, Keycap, a);
|
||||||
@@ -266,7 +190,7 @@ ibus_capability_policy(struct ct *t)
|
|||||||
checkstr(t, "preedit", "k", &res.preedit);
|
checkstr(t, "preedit", "k", &res.preedit);
|
||||||
|
|
||||||
b->focused = 1;
|
b->focused = 1;
|
||||||
b->cap = Cclientpreedit;
|
b->cap = Ibuscappreedit;
|
||||||
memset(&res, 0, sizeof res);
|
memset(&res, 0, sizeof res);
|
||||||
sendrequest(b, Keycap, 0, 0, &res);
|
sendrequest(b, Keycap, 0, 0, &res);
|
||||||
nexttrace(t, &f, Keycap, b);
|
nexttrace(t, &f, Keycap, b);
|
||||||
@@ -296,6 +220,7 @@ ibus_private_input_policy(struct ct *t)
|
|||||||
Ibusfix f;
|
Ibusfix f;
|
||||||
Ictx *ctx;
|
Ictx *ctx;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
|
char text[Maxutf];
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if(!ibusbegin(t, &f))
|
if(!ibusbegin(t, &f))
|
||||||
@@ -309,14 +234,14 @@ ibus_private_input_policy(struct ct *t)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx->focused = 1;
|
ctx->focused = 1;
|
||||||
ctx->cap = Cclientpreedit;
|
ctx->cap = 1;
|
||||||
ctx->purpose = 0;
|
ctx->purpose = 0;
|
||||||
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
||||||
res = contextkey(t, &f, ctx, 'k', 0);
|
res = contextkey(t, &f, ctx, 'k', 0);
|
||||||
checkstr(t, "preedit", "k", &res.preedit);
|
checkstr(t, "preedit", "k", &res.preedit);
|
||||||
ctx->purpose = Ibuspurposepassword;
|
ctx->purpose = Ibuspurposepassword;
|
||||||
memset(&res, 0, sizeof res);
|
memset(&res, 0, sizeof res);
|
||||||
CT_CHECK(t, !processkey(ctx, 'x', 0, &res));
|
CT_CHECK(t, !processkey(ctx, 'x', 0, text, sizeof text, &res));
|
||||||
CT_CHECK(t, !res.eaten);
|
CT_CHECK(t, !res.eaten);
|
||||||
notrace(t, &f);
|
notrace(t, &f);
|
||||||
checkenginepreedit(t, "k");
|
checkenginepreedit(t, "k");
|
||||||
@@ -347,6 +272,7 @@ ibus_context_lifecycle(struct ct *t)
|
|||||||
Keyreq req;
|
Keyreq req;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
Caret at;
|
Caret at;
|
||||||
|
char text[Maxutf];
|
||||||
|
|
||||||
if(!ibusbegin(t, &f))
|
if(!ibusbegin(t, &f))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
@@ -358,25 +284,21 @@ ibus_context_lifecycle(struct ct *t)
|
|||||||
CT_EQ_PTR(t, b, findcontext(f.c2, "/context/one"));
|
CT_EQ_PTR(t, b, findcontext(f.c2, "/context/one"));
|
||||||
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/missing"));
|
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/missing"));
|
||||||
|
|
||||||
memset(&res, 0, sizeof res);
|
|
||||||
CT_CHECK(t, !processkey(a, 'x', 0, &res));
|
|
||||||
CT_CHECK(t, !res.eaten);
|
|
||||||
notrace(t, &f);
|
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
|
||||||
|
|
||||||
setcursor(a, -10, -20, 14);
|
setcursor(a, -10, -20, 14);
|
||||||
notrace(t, &f);
|
notrace(t, &f);
|
||||||
CT_CHECK(t, a->caret.valid);
|
CT_CHECK(t, a->caret.valid);
|
||||||
a->focused = 1;
|
|
||||||
memset(&res, 0, sizeof res);
|
memset(&res, 0, sizeof res);
|
||||||
CT_CHECK(t, !processkey(a, 'x', Relmask, &res));
|
CT_CHECK(t, !processkey(a, 'x', Relmask, text, sizeof text, &res));
|
||||||
CT_CHECK(t, !res.eaten);
|
CT_CHECK(t, !res.eaten);
|
||||||
notrace(t, &f);
|
notrace(t, &f);
|
||||||
|
CT_CHECK(t, !a->focused);
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
|
|
||||||
|
/* A key stands for the FocusIn a client may never send. */
|
||||||
memset(&res, 0, sizeof res);
|
memset(&res, 0, sizeof res);
|
||||||
CT_CHECK(t, processkey(a, 'x', 0, &res));
|
CT_CHECK(t, processkey(a, 'x', 0, text, sizeof text, &res));
|
||||||
req = nexttrace(t, &f, Keypress, a);
|
req = nexttrace(t, &f, Keypress, a);
|
||||||
|
CT_CHECK(t, a->focused);
|
||||||
CT_CHECK(t, !res.eaten);
|
CT_CHECK(t, !res.eaten);
|
||||||
CT_CHECK(t, req.caret.valid);
|
CT_CHECK(t, req.caret.valid);
|
||||||
CT_EQ_INT(t, -10, req.caret.x);
|
CT_EQ_INT(t, -10, req.caret.x);
|
||||||
@@ -450,7 +372,7 @@ ibus_context_lifecycle(struct ct *t)
|
|||||||
notrace(t, &f);
|
notrace(t, &f);
|
||||||
|
|
||||||
memset(&res, 0, sizeof res);
|
memset(&res, 0, sizeof res);
|
||||||
CT_CHECK(t, !processkey(b, 'x', Relmask, &res));
|
CT_CHECK(t, !processkey(b, 'x', Relmask, text, sizeof text, &res));
|
||||||
CT_CHECK(t, !res.eaten);
|
CT_CHECK(t, !res.eaten);
|
||||||
notrace(t, &f);
|
notrace(t, &f);
|
||||||
CT_EQ_PTR(t, b, testengineowner());
|
CT_EQ_PTR(t, b, testengineowner());
|
||||||
@@ -507,12 +429,8 @@ ibus_active_release_lifecycle(struct ct *t)
|
|||||||
reused = newcontext(f.c1, "/context/prune");
|
reused = newcontext(f.c1, "/context/prune");
|
||||||
CT_EQ_PTR(t, ctx, reused);
|
CT_EQ_PTR(t, ctx, reused);
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
memset(&res, 0, sizeof res);
|
|
||||||
CT_CHECK(t, !processkey(reused, 'x', 0, &res));
|
|
||||||
CT_CHECK(t, !res.eaten);
|
|
||||||
notrace(t, &f);
|
|
||||||
reused->focused = 1;
|
|
||||||
contextkey(t, &f, reused, 'n', 0);
|
contextkey(t, &f, reused, 'n', 0);
|
||||||
|
CT_CHECK(t, reused->focused);
|
||||||
CT_EQ_PTR(t, reused, testengineowner());
|
CT_EQ_PTR(t, reused, testengineowner());
|
||||||
dropconncontexts(f.c1);
|
dropconncontexts(f.c1);
|
||||||
nexttrace(t, &f, Keyrelease, reused);
|
nexttrace(t, &f, Keyrelease, reused);
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ tryclient(char *path, int64_t deadline, int *client)
|
|||||||
fd = connectsocket(path, deadline);
|
fd = connectsocket(path, deadline);
|
||||||
if(fd < 0)
|
if(fd < 0)
|
||||||
return errno == ETIMEDOUT ? -1 : 0;
|
return errno == ETIMEDOUT ? -1 : 0;
|
||||||
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
if(!sendkey(fd, 1, 0, 0)){
|
||||||
close(fd);
|
close(fd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -208,7 +208,7 @@ overflowrejected(char *path)
|
|||||||
fd = connectsocket(path, deadline);
|
fd = connectsocket(path, deadline);
|
||||||
if(fd < 0)
|
if(fd < 0)
|
||||||
return fail("overflow connect: %s", strerror(errno));
|
return fail("overflow connect: %s", strerror(errno));
|
||||||
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
if(!sendkey(fd, 1, 0, 0)){
|
||||||
close(fd);
|
close(fd);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -286,7 +286,7 @@ runcapacity(char *path)
|
|||||||
fail("connect capacity client %d: %s", i, strerror(errno));
|
fail("connect capacity client %d: %s", i, strerror(errno));
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
if(!requestkey(client[i], 1, 0, Kmodfirst, 0, "", "",
|
if(!requestkey(client[i], 1, 0, 0, 0, "", "",
|
||||||
"capacity modifier"))
|
"capacity modifier"))
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,10 +55,12 @@ ipc_masks_modifiers(struct ct *t)
|
|||||||
CT_EQ_UINT(t, Kret, ipckeysym(0xff0d, '\r'));
|
CT_EQ_UINT(t, Kret, ipckeysym(0xff0d, '\r'));
|
||||||
CT_EQ_UINT(t, Kret, ipckeysym(0xff8d, '\r')); /* KP_Enter */
|
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, Ktab, ipckeysym(0xfe20, 0)); /* ISO_Left_Tab */
|
||||||
|
CT_EQ_UINT(t, Kdown, ipckeysym(0xff99, 0)); /* KP_Down */
|
||||||
CT_EQ_UINT(t, Kback, ipckeysym(0xff08, 8));
|
CT_EQ_UINT(t, Kback, ipckeysym(0xff08, 8));
|
||||||
CT_EQ_UINT(t, Kspec + 0xff, ipckeysym(0xffff, 0x7f)); /* Delete */
|
CT_EQ_UINT(t, Kspec + 0xff, ipckeysym(0xffff, 0x7f)); /* Delete */
|
||||||
CT_EQ_UINT(t, 0x1f642, ipckeysym(0x101f642, 0x1f642));
|
CT_EQ_UINT(t, 0x1f642, ipckeysym(0x101f642, 0x1f642));
|
||||||
CT_EQ_UINT(t, 0, ipckeysym(0xfe03, 0)); /* ISO_Level3_Shift */
|
CT_EQ_UINT(t, 0, ipckeysym(0xfe03, 0)); /* ISO_Level3_Shift */
|
||||||
|
CT_EQ_UINT(t, 0, ipckeysym(0xffe1, 0)); /* Shift_L */
|
||||||
CT_EQ_UINT(t, Mshift|Mctrl, ipcmod(Mshift|Mctrl|(1<<1)|(1<<4)));
|
CT_EQ_UINT(t, Mshift|Mctrl, ipcmod(Mshift|Mctrl|(1<<1)|(1<<4)));
|
||||||
CT_EQ_UINT(t, Msuper, ipcmod(1<<6));
|
CT_EQ_UINT(t, Msuper, ipcmod(1<<6));
|
||||||
CT_EQ_UINT(t, Msuper, ipcmod(1<<26));
|
CT_EQ_UINT(t, Msuper, ipcmod(1<<26));
|
||||||
@@ -68,7 +70,7 @@ void
|
|||||||
ipc_control_and_caret_frames(struct ct *t)
|
ipc_control_and_caret_frames(struct ct *t)
|
||||||
{
|
{
|
||||||
static const uchar capon[Ipcreqsz] = {
|
static const uchar capon[Ipcreqsz] = {
|
||||||
Ipcext|Cclientpreedit, Ipcversion, Ipcopcap, 0, 0, 0,
|
Ipcext|Ipcreqwant, Ipcversion, Ipcopcap, 0, 0, 0,
|
||||||
};
|
};
|
||||||
static const uchar capoff[Ipcreqsz] = {
|
static const uchar capoff[Ipcreqsz] = {
|
||||||
Ipcext, Ipcversion, Ipcopcap, 0, 0, 0,
|
Ipcext, Ipcversion, Ipcopcap, 0, 0, 0,
|
||||||
@@ -87,7 +89,7 @@ ipc_control_and_caret_frames(struct ct *t)
|
|||||||
int valid, want;
|
int valid, want;
|
||||||
int32_t x, y, h;
|
int32_t x, y, h;
|
||||||
|
|
||||||
ipcpackcap(buf, Cclientpreedit);
|
ipcpackcap(buf, 1);
|
||||||
CT_EQ_MEM(t, capon, buf, sizeof capon);
|
CT_EQ_MEM(t, capon, buf, sizeof capon);
|
||||||
CT_EQ_INT(t, Ipccap, ipcreqtype(buf));
|
CT_EQ_INT(t, Ipccap, ipcreqtype(buf));
|
||||||
/* The probe remains a harmless key-zero request to an old daemon. */
|
/* The probe remains a harmless key-zero request to an old daemon. */
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@ kotrans(char *keys)
|
|||||||
Str out, raw;
|
Str out, raw;
|
||||||
|
|
||||||
raw = mkstr(keys);
|
raw = mkstr(keys);
|
||||||
transstr(getlang(LangKO), &raw, &out);
|
transstr(getlang(LangKO), nil, &raw, &out);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +25,12 @@ korean_sequences(struct ct *t)
|
|||||||
{ "rr", "ㄱㄱ" },
|
{ "rr", "ㄱㄱ" },
|
||||||
{ "Rk", "까" },
|
{ "Rk", "까" },
|
||||||
{ "rk1", "가1" },
|
{ "rk1", "가1" },
|
||||||
|
{ "rt", "ㄳ" },
|
||||||
|
{ "rtk", "ㄱ사" },
|
||||||
|
{ "rtt", "ㄳㅅ" },
|
||||||
|
{ "kr", "가" },
|
||||||
|
{ "hkr", "과" },
|
||||||
|
{ "kk", "ㅏㅏ" },
|
||||||
};
|
};
|
||||||
Str out;
|
Str out;
|
||||||
int i;
|
int i;
|
||||||
@@ -81,6 +87,7 @@ korean_backspace(struct ct *t)
|
|||||||
{
|
{
|
||||||
static const struct { char *before, *after; } cases[] = {
|
static const struct { char *before, *after; } cases[] = {
|
||||||
{ "ㄱ", "" },
|
{ "ㄱ", "" },
|
||||||
|
{ "ㄳ", "ㄱ" },
|
||||||
{ "가", "ㄱ" },
|
{ "가", "ㄱ" },
|
||||||
{ "과", "고" }, { "괘", "고" }, { "괴", "고" },
|
{ "과", "고" }, { "괘", "고" }, { "괴", "고" },
|
||||||
{ "궈", "구" }, { "궤", "구" }, { "귀", "구" },
|
{ "궈", "구" }, { "궤", "구" }, { "귀", "구" },
|
||||||
|
|||||||
84
tests/live.c
84
tests/live.c
@@ -240,6 +240,81 @@ childfds(int fd)
|
|||||||
return dup2(fd, STDOUT_FILENO) >= 0 && dup2(fd, STDERR_FILENO) >= 0;
|
return dup2(fd, STDOUT_FILENO) >= 0 && dup2(fd, STDERR_FILENO) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A private X server for the tests that need one; its display goes in
|
||||||
|
* Live, so the daemon and the clients started after it share it.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
startxvfb(Live *l, Daemon *d, char *program)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
char fdarg[16], line[32];
|
||||||
|
int errpipe[2], n, pipefd[2];
|
||||||
|
int64_t deadline;
|
||||||
|
pid_t pid;
|
||||||
|
ssize_t nr;
|
||||||
|
|
||||||
|
if(pipe2(pipefd, O_CLOEXEC) < 0)
|
||||||
|
return fail("Xvfb display pipe: %s", strerror(errno));
|
||||||
|
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0){
|
||||||
|
close(pipefd[0]);
|
||||||
|
close(pipefd[1]);
|
||||||
|
return fail("pipe2: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
pid = fork();
|
||||||
|
if(pid < 0){
|
||||||
|
close(pipefd[0]);
|
||||||
|
close(pipefd[1]);
|
||||||
|
close(errpipe[0]);
|
||||||
|
close(errpipe[1]);
|
||||||
|
return fail("fork Xvfb: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
if(pid == 0){
|
||||||
|
close(pipefd[0]);
|
||||||
|
close(errpipe[0]);
|
||||||
|
n = fcntl(pipefd[1], F_GETFD);
|
||||||
|
if(n < 0 || fcntl(pipefd[1], F_SETFD, n & ~FD_CLOEXEC) < 0 ||
|
||||||
|
!childfds(errpipe[1]))
|
||||||
|
_exit(126);
|
||||||
|
snprintf(fdarg, sizeof fdarg, "%d", pipefd[1]);
|
||||||
|
execlp(program, program, "-displayfd", fdarg, "-screen", "0",
|
||||||
|
"1024x768x24", "-nolisten", "tcp", "-noreset", (char*)0);
|
||||||
|
_exit(127);
|
||||||
|
}
|
||||||
|
close(pipefd[1]);
|
||||||
|
close(errpipe[1]);
|
||||||
|
d->pid = pid;
|
||||||
|
d->errfd = errpipe[0];
|
||||||
|
pfd.fd = pipefd[0];
|
||||||
|
pfd.events = POLLIN|POLLHUP;
|
||||||
|
deadline = nowms() + Starttimeout;
|
||||||
|
n = 0;
|
||||||
|
while(n + 1 < (int)sizeof line){
|
||||||
|
pfd.revents = 0;
|
||||||
|
if(poll(&pfd, 1, leftms(deadline)) <= 0)
|
||||||
|
break;
|
||||||
|
nr = read(pipefd[0], line + n, sizeof line - n - 1);
|
||||||
|
if(nr < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if(nr <= 0)
|
||||||
|
break;
|
||||||
|
n += nr;
|
||||||
|
line[n] = '\0';
|
||||||
|
if(strchr(line, '\n') != NULL)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
close(pipefd[0]);
|
||||||
|
if(n == 0 || strchr(line, '\n') == NULL)
|
||||||
|
return fail("Xvfb did not report a private display");
|
||||||
|
line[strcspn(line, "\r\n")] = '\0';
|
||||||
|
if(strspn(line, "0123456789") != strlen(line))
|
||||||
|
return fail("invalid Xvfb display number %s", line);
|
||||||
|
if(snprintf(l->display, sizeof l->display, ":%s", line)
|
||||||
|
>= (int)sizeof l->display)
|
||||||
|
return fail("Xvfb display number is too long");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
startdaemon(Live *l, Daemon *d, char *program, char *arg)
|
startdaemon(Live *l, Daemon *d, char *program, char *arg)
|
||||||
{
|
{
|
||||||
@@ -268,7 +343,10 @@ startdaemon(Live *l, Daemon *d, char *program, char *arg)
|
|||||||
}
|
}
|
||||||
if(setenv("XDG_RUNTIME_DIR", l->runtime, 1) < 0 ||
|
if(setenv("XDG_RUNTIME_DIR", l->runtime, 1) < 0 ||
|
||||||
setenv("XDG_CONFIG_HOME", l->config, 1) < 0 ||
|
setenv("XDG_CONFIG_HOME", l->config, 1) < 0 ||
|
||||||
setenv("HOME", l->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
setenv("HOME", l->home, 1) < 0 ||
|
||||||
|
(l->display[0] != '\0' ?
|
||||||
|
setenv("DISPLAY", l->display, 1) :
|
||||||
|
unsetenv("DISPLAY")) < 0 ||
|
||||||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
||||||
unsetenv("IBUS_ADDRESS") < 0 ||
|
unsetenv("IBUS_ADDRESS") < 0 ||
|
||||||
unsetenv("IBUS_ADDRESS_FILE") < 0){
|
unsetenv("IBUS_ADDRESS_FILE") < 0){
|
||||||
@@ -393,7 +471,7 @@ stopdaemon(Daemon *d)
|
|||||||
}
|
}
|
||||||
/* plan9port turns a caught termination note into exit status 1. */
|
/* plan9port turns a caught termination note into exit status 1. */
|
||||||
if(reaped && !((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) ||
|
if(reaped && !((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) ||
|
||||||
(WIFEXITED(status) && WEXITSTATUS(status) == 1)))
|
(WIFEXITED(status) && WEXITSTATUS(status) <= 1)))
|
||||||
ok = fail("%s exited with unexpected wait status %#x", d->name,
|
ok = fail("%s exited with unexpected wait status %#x", d->name,
|
||||||
status);
|
status);
|
||||||
readerrors(d);
|
readerrors(d);
|
||||||
@@ -691,5 +769,5 @@ ipcprobe(int fd, char *where)
|
|||||||
{
|
{
|
||||||
unsigned char empty[] = {0, 0, 0, 0, 0};
|
unsigned char empty[] = {0, 0, 0, 0, 0};
|
||||||
|
|
||||||
return ipcrequest(fd, 0, Kmodfirst, empty, sizeof empty, where);
|
return ipcrequest(fd, 0, 0, empty, sizeof empty, where);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ struct Live
|
|||||||
char home[320];
|
char home[320];
|
||||||
char socket[384];
|
char socket[384];
|
||||||
char addrfile[512];
|
char addrfile[512];
|
||||||
|
char display[32];
|
||||||
};
|
};
|
||||||
|
|
||||||
extern char *testname;
|
extern char *testname;
|
||||||
@@ -54,6 +55,7 @@ int livesetup(Live*, char*);
|
|||||||
int liveclean(Live*);
|
int liveclean(Live*);
|
||||||
|
|
||||||
void daemoninit(Daemon*, char*);
|
void daemoninit(Daemon*, char*);
|
||||||
|
int startxvfb(Live*, Daemon*, char*);
|
||||||
int startdaemon(Live*, Daemon*, char*, char*);
|
int startdaemon(Live*, Daemon*, char*, char*);
|
||||||
int waitready(Live*, Daemon*, char*, size_t);
|
int waitready(Live*, Daemon*, char*, size_t);
|
||||||
int daemonalive(Daemon*);
|
int daemonalive(Daemon*);
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ MKEMOJI = ROOT / "map" / "mkemoji"
|
|||||||
TIMEOUT = 10
|
TIMEOUT = 10
|
||||||
|
|
||||||
|
|
||||||
def generate(source=None):
|
def generate(*sources):
|
||||||
args = [sys.executable, "-B", str(MKEMOJI)]
|
args = [sys.executable, "-B", str(MKEMOJI)] + [str(s) for s in sources]
|
||||||
if source is not None:
|
|
||||||
args.append(str(source))
|
|
||||||
return subprocess.run(
|
return subprocess.run(
|
||||||
args, capture_output=True, text=True, check=False, timeout=TIMEOUT
|
args, capture_output=True, text=True, check=False, timeout=TIMEOUT
|
||||||
)
|
)
|
||||||
@@ -37,27 +35,32 @@ class MkemojiTest(unittest.TestCase):
|
|||||||
result = generate()
|
result = generate()
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
data = table(result.stdout)
|
data = table(result.stdout)
|
||||||
self.assertEqual(data["^"].split()[:9], list("¹²³⁴⁵⁶⁷⁸⁹"))
|
keys = list(data)
|
||||||
self.assertEqual(data["_"].split()[:9], list("₁₂₃₄₅₆₇₈₉"))
|
self.assertEqual([k for k in keys if k[:1] == "^"][:9],
|
||||||
self.assertEqual(data["<"].split()[2], "♥")
|
[f"^{d}" for d in range(1, 10)])
|
||||||
|
self.assertEqual([k for k in keys if k[:1] == "_"][:9],
|
||||||
|
[f"_{d}" for d in range(1, 10)])
|
||||||
|
self.assertEqual([k for k in keys if k[:1] == "<"][2], "<3")
|
||||||
self.assertEqual(data["^1"], "¹")
|
self.assertEqual(data["^1"], "¹")
|
||||||
self.assertEqual(data["_2"], "₂")
|
self.assertEqual(data["_2"], "₂")
|
||||||
self.assertEqual(data["<3"], "♥")
|
self.assertEqual(data["<3"].split()[0], "♥")
|
||||||
|
self.assertIn("😀", data["smile"].split())
|
||||||
|
self.assertIn("😀", data["웃음"].split())
|
||||||
|
|
||||||
def test_fold_normalize_and_exact_first(self):
|
def test_fold_normalize_and_source_order(self):
|
||||||
source = self.source(
|
source = self.source(
|
||||||
"β\tALPHABET\n"
|
"β\tALPHABET\talpha\n"
|
||||||
"α\talpha\n"
|
"α\talpha\n"
|
||||||
"e\u0301\tE\u0301\n"
|
"é\tÉ\n"
|
||||||
"#\thash\n"
|
"#\thash\n"
|
||||||
)
|
)
|
||||||
first = generate(source)
|
first = generate(source)
|
||||||
second = generate(source)
|
second = generate(source, source)
|
||||||
self.assertEqual(first.returncode, 0, first.stderr)
|
self.assertEqual(first.returncode, 0, first.stderr)
|
||||||
self.assertEqual(first.stdout, second.stdout)
|
self.assertEqual(first.stdout, second.stdout)
|
||||||
data = table(first.stdout)
|
data = table(first.stdout)
|
||||||
self.assertEqual(data["alpha"].split(), ["α", "β"])
|
self.assertEqual(list(data), ["alphabet", "alpha", "é", "hash"])
|
||||||
self.assertEqual(data["al"].split(), ["β", "α"])
|
self.assertEqual(data["alpha"].split(), ["β", "α"])
|
||||||
self.assertEqual(data["é"], "é")
|
self.assertEqual(data["é"], "é")
|
||||||
self.assertEqual(data["hash"], "#")
|
self.assertEqual(data["hash"], "#")
|
||||||
|
|
||||||
|
|||||||
@@ -42,25 +42,27 @@ class MkhanjaTest(unittest.TestCase):
|
|||||||
self.addCleanup(tmp.cleanup)
|
self.addCleanup(tmp.cleanup)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def test_imports_single_bmp_hanja_rows(self):
|
def test_imports_bmp_hanja_rows(self):
|
||||||
result = run(
|
result = run(
|
||||||
IMPORT,
|
IMPORT,
|
||||||
self.source(
|
self.source(
|
||||||
"# Copyright holder\n"
|
"# Copyright holder\n"
|
||||||
"# BSD license\n"
|
"# BSD license\n"
|
||||||
"한:漢:first\n"
|
"\ud55c:\u6f22:first\n"
|
||||||
"가:㐀:extension A\n"
|
"\uac00:\u3400:extension A\n"
|
||||||
"김:金:compatibility\n"
|
"\uae40:\u91d1:compatibility\n"
|
||||||
"방학:放:word reading\n"
|
"\ud55c\uc790:\u6f22\u5b57:word\n"
|
||||||
"학:學校:word value\n"
|
"\ud55c:\U00020000:astral\n"
|
||||||
"한:𠀀:astral\n"
|
"\u3131:\u52a0:jamo\n"
|
||||||
"ㄱ:加:jamo\n"
|
"\ud55c\uae00:\u97d3glyph:mixed\n"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
lines = result.stdout.splitlines()
|
lines = result.stdout.splitlines()
|
||||||
self.assertEqual(lines[:2], ["# Copyright holder", "# BSD license"])
|
self.assertEqual(lines[:2], ["# Copyright holder", "# BSD license"])
|
||||||
self.assertEqual(lines[2:], ["", "漢\t한", "㐀\t가", "金\t김"])
|
self.assertEqual(lines[2:], ["", "\u6f22\t\ud55c", "\u3400\t\uac00",
|
||||||
|
"\u91d1\t\uae40",
|
||||||
|
"\u6f22\u5b57\t\ud55c\uc790"])
|
||||||
|
|
||||||
def test_import_rejects_malformed_and_duplicate_rows(self):
|
def test_import_rejects_malformed_and_duplicate_rows(self):
|
||||||
bad = [
|
bad = [
|
||||||
@@ -91,6 +93,7 @@ class MkhanjaTest(unittest.TestCase):
|
|||||||
"㐀\t가\n"
|
"㐀\t가\n"
|
||||||
"家\t가\n"
|
"家\t가\n"
|
||||||
"金\t금\n"
|
"金\t금\n"
|
||||||
|
"漢字\t한자\n"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
@@ -103,13 +106,14 @@ class MkhanjaTest(unittest.TestCase):
|
|||||||
"한\t漢 韓",
|
"한\t漢 韓",
|
||||||
"가\t㐀 家",
|
"가\t㐀 家",
|
||||||
"금\t金",
|
"금\t金",
|
||||||
|
"한자\t漢字",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_generator_rejects_bad_rows(self):
|
def test_generator_rejects_bad_rows(self):
|
||||||
bad = [
|
bad = [
|
||||||
"漢韓\t한\n",
|
"漢a\t한\n",
|
||||||
"漢\t한자\n",
|
"漢\t한a\n",
|
||||||
"𠀀\t한\n",
|
"𠀀\t한\n",
|
||||||
"漢\t한\n漢\t한\n",
|
"漢\t한\n漢\t한\n",
|
||||||
"漢 한\n",
|
"漢 한\n",
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
|
/* A popup with every section, tall enough for the tests. */
|
||||||
|
#define Imgh (2*PopupPad + (Maxdisp + 2)*Fontsz + PopupSep)
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
Rgbmask = 0xffffff,
|
Rgbmask = 0xffffff,
|
||||||
};
|
};
|
||||||
@@ -40,9 +43,6 @@ checkpadding(struct ct *t, u32int *img, Popup *p)
|
|||||||
void
|
void
|
||||||
popup_layout(struct ct *t)
|
popup_layout(struct ct *t)
|
||||||
{
|
{
|
||||||
int tinyheight[] = {
|
|
||||||
1, PopupPad, 2*PopupPad + Fontsz - 1,
|
|
||||||
};
|
|
||||||
static const struct {
|
static const struct {
|
||||||
int first;
|
int first;
|
||||||
int shown;
|
int shown;
|
||||||
@@ -132,8 +132,7 @@ popup_layout(struct ct *t)
|
|||||||
CT_EQ_INT(t, 100, x);
|
CT_EQ_INT(t, 100, x);
|
||||||
CT_EQ_INT(t, 200, y);
|
CT_EQ_INT(t, 200, y);
|
||||||
|
|
||||||
if(!CT_CHECK(t, textinit()))
|
textinit();
|
||||||
return;
|
|
||||||
|
|
||||||
/* Empty sections reserve nothing. */
|
/* Empty sections reserve nothing. */
|
||||||
memset(&dc, 0, sizeof dc);
|
memset(&dc, 0, sizeof dc);
|
||||||
@@ -145,10 +144,10 @@ popup_layout(struct ct *t)
|
|||||||
CT_EQ_INT(t, -1, p.rowsy);
|
CT_EQ_INT(t, -1, p.rowsy);
|
||||||
CT_EQ_INT(t, -1, p.marky);
|
CT_EQ_INT(t, -1, p.marky);
|
||||||
|
|
||||||
/* Preedit only. */
|
/* Preedit only: as wide as its text. */
|
||||||
dc.pre = mkstr("preedit");
|
dc.pre = mkstr("preedit");
|
||||||
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
|
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
|
||||||
CT_EQ_INT(t, PopupBasew, p.w);
|
CT_EQ_INT(t, textwidth(&dc.pre) + 2*PopupPad, p.w);
|
||||||
CT_EQ_INT(t, PopupPad, p.prey);
|
CT_EQ_INT(t, PopupPad, p.prey);
|
||||||
CT_EQ_INT(t, -1, p.sepy);
|
CT_EQ_INT(t, -1, p.sepy);
|
||||||
CT_EQ_INT(t, -1, p.rowsy);
|
CT_EQ_INT(t, -1, p.rowsy);
|
||||||
@@ -320,24 +319,5 @@ popup_layout(struct ct *t)
|
|||||||
CT_CHECK(t, numink > 0); /* full-width 9 */
|
CT_CHECK(t, numink > 0); /* full-width 9 */
|
||||||
CT_CHECK(t, markink > 0); /* 9-9/9 */
|
CT_CHECK(t, markink > 0); /* 9-9/9 */
|
||||||
|
|
||||||
/* The selected row wins over preedit and footer in one-row height. */
|
|
||||||
dc.pre = mkstr("preedit");
|
|
||||||
popuplayout(&dc, PopupBasew, 2*PopupPad + Fontsz, &p);
|
|
||||||
CT_EQ_INT(t, -1, p.prey);
|
|
||||||
CT_EQ_INT(t, 1, p.n);
|
|
||||||
CT_EQ_INT(t, Maxdisp - 1, p.row0);
|
|
||||||
CT_EQ_INT(t, -1, p.marky);
|
|
||||||
|
|
||||||
/* With less than one padded row, selected content starts at the top. */
|
|
||||||
for(i = 0; i < nelem(tinyheight); i++){
|
|
||||||
popuplayout(&dc, PopupBasew, tinyheight[i], &p);
|
|
||||||
CT_EQ_INT(t, min(tinyheight[i], Fontsz), p.h);
|
|
||||||
CT_EQ_INT(t, 0, p.rowsy);
|
|
||||||
CT_EQ_INT(t, 0, p.sely);
|
|
||||||
CT_EQ_INT(t, 1, p.n);
|
|
||||||
CT_EQ_INT(t, Maxdisp - 1, p.row0);
|
|
||||||
popupdraw(img, &dc, &p);
|
|
||||||
CT_EQ_UINT(t, Colsel, pixel(img, &p, p.w - PopupPad - 1, 0));
|
|
||||||
}
|
|
||||||
free(img);
|
free(img);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,14 +143,14 @@ nextrequestcap(struct ct *t, Pump *p, int op, int cap)
|
|||||||
memset(&req, 0, sizeof req);
|
memset(&req, 0, sizeof req);
|
||||||
chanrecv(p->trace, &req);
|
chanrecv(p->trace, &req);
|
||||||
CT_EQ_INT(t, op, req.op);
|
CT_EQ_INT(t, op, req.op);
|
||||||
CT_EQ_INT(t, cap, req.cap);
|
CT_EQ_INT(t, cap, req.clientpre);
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Keyreq
|
static Keyreq
|
||||||
nextrequest(struct ct *t, Pump *p, int op)
|
nextrequest(struct ct *t, Pump *p, int op)
|
||||||
{
|
{
|
||||||
return nextrequestcap(t, p, op, Cclientpreedit);
|
return nextrequestcap(t, p, op, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -284,10 +284,11 @@ server_connection_ownership(struct ct *t)
|
|||||||
req = nextrequest(t, &f.pump, Keyreset);
|
req = nextrequest(t, &f.pump, Keyreset);
|
||||||
CT_EQ_PTR(t, aowner, req.owner);
|
CT_EQ_PTR(t, aowner, req.owner);
|
||||||
allowrequest(&f.pump);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
|
/* The reset hands back what the takeover took from this client. */
|
||||||
|
CT_CHECK(t, readreply(t, &a, 1, "か", preedit));
|
||||||
CT_EQ_STR(t, "", preedit);
|
CT_EQ_STR(t, "", preedit);
|
||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
if(!sendkey(t, &b, 1, 0, Kmodfirst))
|
if(!sendkey(t, &b, 1, 0, 0))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &f.pump, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
allowrequest(&f.pump);
|
allowrequest(&f.pump);
|
||||||
@@ -387,7 +388,7 @@ server_extension_stream(struct ct *t)
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
/* Capability and caret frames may be split at any byte boundary. */
|
/* Capability and caret frames may be split at any byte boundary. */
|
||||||
ipcpackcap(frame, Cclientpreedit);
|
ipcpackcap(frame, 1);
|
||||||
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
|
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &f.pump, Keycap);
|
req = nextrequest(t, &f.pump, Keycap);
|
||||||
@@ -446,7 +447,7 @@ server_extension_stream(struct ct *t)
|
|||||||
allowrequest(&f.pump);
|
allowrequest(&f.pump);
|
||||||
CT_EQ_INT(t, 1, readreply(t, &a, 0, "k", preedit));
|
CT_EQ_INT(t, 1, readreply(t, &a, 0, "k", preedit));
|
||||||
|
|
||||||
ipcpackcap(frame, Cclientpreedit);
|
ipcpackcap(frame, 1);
|
||||||
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &f.pump, Keycap);
|
req = nextrequest(t, &f.pump, Keycap);
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
/* Included after dat.h and fn.h (or after the source under test). */
|
/* Included after dat.h and fn.h (or after the source under test). */
|
||||||
#include "cutest/cutest.h"
|
#include "cutest/cutest.h"
|
||||||
|
|
||||||
extern Lang testvi;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A test's stand-in for imthread: handles engine requests from keyc,
|
* A test's stand-in for imthread: handles engine requests from keyc,
|
||||||
* tracing each one first. Requests whose op is holdop (every request
|
* tracing each one first. Requests whose op is holdop (every request
|
||||||
@@ -95,7 +93,7 @@ void engine_randomized_stress(struct ct*);
|
|||||||
void engine_full_boundary_passthrough(struct ct*);
|
void engine_full_boundary_passthrough(struct ct*);
|
||||||
void dictionary_candidates(struct ct*);
|
void dictionary_candidates(struct ct*);
|
||||||
void dictionary_misses(struct ct*);
|
void dictionary_misses(struct ct*);
|
||||||
void dictionary_emoji_identity(struct ct*);
|
void dictionary_prefix(struct ct*);
|
||||||
void ipc_masks_modifiers(struct ct*);
|
void ipc_masks_modifiers(struct ct*);
|
||||||
void ipc_control_and_caret_frames(struct ct*);
|
void ipc_control_and_caret_frames(struct ct*);
|
||||||
void ipc_runtime_path(struct ct*);
|
void ipc_runtime_path(struct ct*);
|
||||||
@@ -108,11 +106,11 @@ void server_connection_ownership(struct ct*);
|
|||||||
void server_extension_stream(struct ct*);
|
void server_extension_stream(struct ct*);
|
||||||
void server_rejects_unknown_extension(struct ct*);
|
void server_rejects_unknown_extension(struct ct*);
|
||||||
void ibus_machine_id_fallback(struct ct*);
|
void ibus_machine_id_fallback(struct ct*);
|
||||||
void ibus_startup_requires_ownership(struct ct*);
|
|
||||||
void ibus_capability_policy(struct ct*);
|
void ibus_capability_policy(struct ct*);
|
||||||
void ibus_private_input_policy(struct ct*);
|
void ibus_private_input_policy(struct ct*);
|
||||||
void ibus_context_lifecycle(struct ct*);
|
void ibus_context_lifecycle(struct ct*);
|
||||||
void ibus_active_release_lifecycle(struct ct*);
|
void ibus_active_release_lifecycle(struct ct*);
|
||||||
|
void compose_sequences(struct ct*);
|
||||||
void xim_keymap_lookup(struct ct*);
|
void xim_keymap_lookup(struct ct*);
|
||||||
void xim_compound_text(struct ct*);
|
void xim_compound_text(struct ct*);
|
||||||
void xim_adapter_key_contract(struct ct*);
|
void xim_adapter_key_contract(struct ct*);
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
Channel *drawc;
|
Channel *drawc;
|
||||||
Channel *keyc;
|
Channel *keyc;
|
||||||
Lang testvi;
|
|
||||||
|
|
||||||
void
|
void
|
||||||
die(char *fmt, ...)
|
die(char *fmt, ...)
|
||||||
@@ -45,20 +44,16 @@ erealloc(void *p, ulong n)
|
|||||||
static void
|
static void
|
||||||
testmapinit(void)
|
testmapinit(void)
|
||||||
{
|
{
|
||||||
Lang *jp, *kata;
|
Lang *jp, *kata, *vi;
|
||||||
|
|
||||||
jp = getlang(LangJP);
|
jp = getlang(LangJP);
|
||||||
kata = getlang(LangJPK);
|
kata = getlang(LangJPK);
|
||||||
if(jp == nil || kata == nil)
|
vi = getlang(LangVI);
|
||||||
|
if(jp == nil || kata == nil || vi == nil)
|
||||||
die("test language is not registered");
|
die("test language is not registered");
|
||||||
jp->map = trieopen("../map/hira.map");
|
jp->map = trieopen("../map/hira.map");
|
||||||
kata->map = trieopen("../map/kata.map");
|
kata->map = trieopen("../map/kata.map");
|
||||||
memset(&testvi, 0, sizeof testvi);
|
vi->map = trieopen("../map/telex.map");
|
||||||
testvi.lang = LangVI;
|
|
||||||
testvi.mapname = "telex";
|
|
||||||
testvi.trans = transvi;
|
|
||||||
testvi.back = backvi;
|
|
||||||
testvi.map = trieopen("../map/telex.map");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef STRESS
|
#ifndef STRESS
|
||||||
@@ -118,7 +113,7 @@ static const struct ct_test tests[] = {
|
|||||||
{ "engine/full-boundary-passthrough", engine_full_boundary_passthrough },
|
{ "engine/full-boundary-passthrough", engine_full_boundary_passthrough },
|
||||||
{ "dict/candidates", dictionary_candidates },
|
{ "dict/candidates", dictionary_candidates },
|
||||||
{ "dict/misses", dictionary_misses },
|
{ "dict/misses", dictionary_misses },
|
||||||
{ "dict/emoji-identity", dictionary_emoji_identity },
|
{ "dict/prefix", dictionary_prefix },
|
||||||
{ "ipc/masks-modifiers", ipc_masks_modifiers },
|
{ "ipc/masks-modifiers", ipc_masks_modifiers },
|
||||||
{ "ipc/control-caret-frames", ipc_control_and_caret_frames },
|
{ "ipc/control-caret-frames", ipc_control_and_caret_frames },
|
||||||
{ "ipc/runtime-path", ipc_runtime_path },
|
{ "ipc/runtime-path", ipc_runtime_path },
|
||||||
@@ -131,11 +126,11 @@ static const struct ct_test tests[] = {
|
|||||||
{ "server/extension-stream", server_extension_stream },
|
{ "server/extension-stream", server_extension_stream },
|
||||||
{ "server/rejects-unknown-extension", server_rejects_unknown_extension },
|
{ "server/rejects-unknown-extension", server_rejects_unknown_extension },
|
||||||
{ "ibus/machine-id-fallback", ibus_machine_id_fallback },
|
{ "ibus/machine-id-fallback", ibus_machine_id_fallback },
|
||||||
{ "ibus/startup-requires-ownership", ibus_startup_requires_ownership },
|
|
||||||
{ "ibus/capability-policy", ibus_capability_policy },
|
{ "ibus/capability-policy", ibus_capability_policy },
|
||||||
{ "ibus/private-input-policy", ibus_private_input_policy },
|
{ "ibus/private-input-policy", ibus_private_input_policy },
|
||||||
{ "ibus/context-lifecycle", ibus_context_lifecycle },
|
{ "ibus/context-lifecycle", ibus_context_lifecycle },
|
||||||
{ "ibus/active-release-lifecycle", ibus_active_release_lifecycle },
|
{ "ibus/active-release-lifecycle", ibus_active_release_lifecycle },
|
||||||
|
{ "compose/sequences", compose_sequences },
|
||||||
{ "xim/keymap-lookup", xim_keymap_lookup },
|
{ "xim/keymap-lookup", xim_keymap_lookup },
|
||||||
{ "xim/compound-text", xim_compound_text },
|
{ "xim/compound-text", xim_compound_text },
|
||||||
{ "xim/adapter-key-contract", xim_adapter_key_contract },
|
{ "xim/adapter-key-contract", xim_adapter_key_contract },
|
||||||
@@ -170,8 +165,6 @@ threadmain(int argc, char **argv)
|
|||||||
trieclose(langs[i].map);
|
trieclose(langs[i].map);
|
||||||
langs[i].map = nil;
|
langs[i].map = nil;
|
||||||
}
|
}
|
||||||
trieclose(testvi.map);
|
|
||||||
testvi.map = nil;
|
|
||||||
chanfree(drawc);
|
chanfree(drawc);
|
||||||
chanfree(keyc);
|
chanfree(keyc);
|
||||||
threadexitsall(status == 0 ? nil : "tests failed");
|
threadexitsall(status == 0 ? nil : "tests failed");
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ typevi(struct ct *t, char *keys, char *want)
|
|||||||
Str out, raw;
|
Str out, raw;
|
||||||
|
|
||||||
raw = mkstr(keys);
|
raw = mkstr(keys);
|
||||||
transstr(&testvi, &raw, &out);
|
transstr(getlang(LangVI), nil, &raw, &out);
|
||||||
checkstr(t, keys, want, &out);
|
checkstr(t, keys, want, &out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ vietnamese_backspace(struct ct *t)
|
|||||||
|
|
||||||
for(i = 0; i < nelem(cases); i++){
|
for(i = 0; i < nelem(cases); i++){
|
||||||
memset(&state, 0, sizeof state);
|
memset(&state, 0, sizeof state);
|
||||||
state.l = &testvi;
|
state.l = getlang(LangVI);
|
||||||
state.pre = mkstr(cases[i].pre);
|
state.pre = mkstr(cases[i].pre);
|
||||||
state.raw = mkstr(cases[i].raw);
|
state.raw = mkstr(cases[i].raw);
|
||||||
backvi(&state);
|
backvi(&state);
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ static int wireflush(xcb_connection_t*);
|
|||||||
#define xcb_translate_coordinates wiretranslate
|
#define xcb_translate_coordinates wiretranslate
|
||||||
#define xcb_translate_coordinates_reply wiretranslatereply
|
#define xcb_translate_coordinates_reply wiretranslatereply
|
||||||
#define xcb_flush wireflush
|
#define xcb_flush wireflush
|
||||||
#include "xim/xim.c"
|
#include "xim.c"
|
||||||
#undef xcb_im_commit_string
|
#undef xcb_im_commit_string
|
||||||
#undef xcb_im_preedit_start_callback
|
#undef xcb_im_preedit_start_callback
|
||||||
#undef xcb_im_preedit_draw_callback
|
#undef xcb_im_preedit_draw_callback
|
||||||
@@ -437,7 +437,7 @@ nexttracecap(struct ct *t, Ximfix *f, int op, Ic *owner, int cap)
|
|||||||
return req;
|
return req;
|
||||||
CT_EQ_INT(t, op, req.op);
|
CT_EQ_INT(t, op, req.op);
|
||||||
CT_EQ_PTR(t, owner, req.owner);
|
CT_EQ_PTR(t, owner, req.owner);
|
||||||
CT_EQ_INT(t, cap, req.cap);
|
CT_EQ_INT(t, cap, req.clientpre);
|
||||||
CT_EQ_PTR(t, replyc, req.reply);
|
CT_EQ_PTR(t, replyc, req.reply);
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
@@ -445,7 +445,7 @@ nexttracecap(struct ct *t, Ximfix *f, int op, Ic *owner, int cap)
|
|||||||
static Keyreq
|
static Keyreq
|
||||||
nexttrace(struct ct *t, Ximfix *f, int op, Ic *state)
|
nexttrace(struct ct *t, Ximfix *f, int op, Ic *state)
|
||||||
{
|
{
|
||||||
return nexttracecap(t, f, op, state, state->cap);
|
return nexttracecap(t, f, op, state, state->clientpre);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -539,7 +539,7 @@ xim_adapter_release_lifecycle(struct ct *t)
|
|||||||
keypress(dead, 'n', 0, &res);
|
keypress(dead, 'n', 0, &res);
|
||||||
nexttrace(t, &f, Keypress, dead);
|
nexttrace(t, &f, Keypress, dead);
|
||||||
CT_EQ_PTR(t, dead, testengineowner());
|
CT_EQ_PTR(t, dead, testengineowner());
|
||||||
deadcap = dead->cap;
|
deadcap = dead->clientpre;
|
||||||
icfree(dead);
|
icfree(dead);
|
||||||
nexttracecap(t, &f, Keyrelease, dead, deadcap);
|
nexttracecap(t, &f, Keyrelease, dead, deadcap);
|
||||||
dead = nil;
|
dead = nil;
|
||||||
@@ -632,6 +632,9 @@ xim_adapter_styles(struct ct *t)
|
|||||||
XCB_IM_PreeditPosition | XCB_IM_StatusNothing,
|
XCB_IM_PreeditPosition | XCB_IM_StatusNothing,
|
||||||
XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing,
|
XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing,
|
||||||
XCB_IM_PreeditNothing | XCB_IM_StatusNothing,
|
XCB_IM_PreeditNothing | XCB_IM_StatusNothing,
|
||||||
|
XCB_IM_PreeditPosition | XCB_IM_StatusNone,
|
||||||
|
XCB_IM_PreeditCallbacks | XCB_IM_StatusNone,
|
||||||
|
XCB_IM_PreeditNothing | XCB_IM_StatusNone,
|
||||||
};
|
};
|
||||||
xcb_im_input_context_t *ic;
|
xcb_im_input_context_t *ic;
|
||||||
Ic *state;
|
Ic *state;
|
||||||
@@ -651,7 +654,8 @@ xim_adapter_styles(struct ct *t)
|
|||||||
state = icbindings[0].data;
|
state = icbindings[0].data;
|
||||||
if(!CT_CHECK(t, state != nil && state == ics))
|
if(!CT_CHECK(t, state != nil && state == ics))
|
||||||
continue;
|
continue;
|
||||||
CT_EQ_INT(t, i == 1 ? Cclientpreedit : 0, state->cap);
|
CT_EQ_INT(t, want[i] & XCB_IM_PreeditCallbacks ? 1 : 0,
|
||||||
|
state->clientpre);
|
||||||
CT_EQ_PTR(t, ic, state->xic);
|
CT_EQ_PTR(t, ic, state->xic);
|
||||||
icunlink(state);
|
icunlink(state);
|
||||||
free(state);
|
free(state);
|
||||||
@@ -663,7 +667,7 @@ xim_adapter_placement(struct ct *t)
|
|||||||
{
|
{
|
||||||
/* Spot location first, then the bottom of the focus window, then of
|
/* Spot location first, then the bottom of the focus window, then of
|
||||||
* the client window; an unset focus window is the client window. */
|
* the client window; an unset focus window is the client window. */
|
||||||
static const struct {
|
const struct {
|
||||||
u32int mask;
|
u32int mask;
|
||||||
int focuswin;
|
int focuswin;
|
||||||
int fgeo;
|
int fgeo;
|
||||||
@@ -673,14 +677,15 @@ xim_adapter_placement(struct ct *t)
|
|||||||
int valid;
|
int valid;
|
||||||
int x;
|
int x;
|
||||||
int y;
|
int y;
|
||||||
|
int h;
|
||||||
} cases[] = {
|
} cases[] = {
|
||||||
{XCB_XIM_XNSpotLocation_MASK, 10, 1, 1, 1, 1, 1, 103, 204},
|
{XCB_XIM_XNSpotLocation_MASK, 10, 1, 1, 1, 1, 1, 103, 204-Fontsz, Fontsz},
|
||||||
{XCB_XIM_XNSpotLocation_MASK, 10, 1, 0, 1, 1, 1, 300, 440},
|
{XCB_XIM_XNSpotLocation_MASK, 10, 1, 0, 1, 1, 1, 300, 440, 0},
|
||||||
{XCB_XIM_XNSpotLocation_MASK, 0, 1, 1, 1, 1, 1, 303, 404},
|
{XCB_XIM_XNSpotLocation_MASK, 0, 1, 1, 1, 1, 1, 303, 404-Fontsz, Fontsz},
|
||||||
{0, 10, 1, 1, 1, 1, 1, 100, 230},
|
{0, 10, 1, 1, 1, 1, 1, 100, 230, 0},
|
||||||
{0, 0, 1, 1, 1, 1, 1, 300, 440},
|
{0, 0, 1, 1, 1, 1, 1, 300, 440, 0},
|
||||||
{0, 10, 0, 1, 1, 1, 1, 300, 440},
|
{0, 10, 0, 1, 1, 1, 1, 300, 440, 0},
|
||||||
{0, 10, 0, 1, 0, 1, 0, 0, 0},
|
{0, 10, 0, 1, 0, 1, 0, 0, 0, 0},
|
||||||
};
|
};
|
||||||
xcb_im_input_context_t *ic;
|
xcb_im_input_context_t *ic;
|
||||||
Icbinding *b;
|
Icbinding *b;
|
||||||
@@ -712,7 +717,7 @@ xim_adapter_placement(struct ct *t)
|
|||||||
CT_EQ_INT(t, cases[i].valid, state.caret.valid);
|
CT_EQ_INT(t, cases[i].valid, state.caret.valid);
|
||||||
CT_EQ_INT(t, cases[i].x, state.caret.x);
|
CT_EQ_INT(t, cases[i].x, state.caret.x);
|
||||||
CT_EQ_INT(t, cases[i].y, state.caret.y);
|
CT_EQ_INT(t, cases[i].y, state.caret.y);
|
||||||
CT_EQ_INT(t, 0, state.caret.h);
|
CT_EQ_INT(t, cases[i].h, state.caret.h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -752,13 +757,13 @@ xim_adapter_placement_updates(struct ct *t)
|
|||||||
req = nexttrace(t, &f, Keypress, &state);
|
req = nexttrace(t, &f, Keypress, &state);
|
||||||
CT_CHECK(t, req.caret.valid);
|
CT_CHECK(t, req.caret.valid);
|
||||||
CT_EQ_INT(t, 105, req.caret.x);
|
CT_EQ_INT(t, 105, req.caret.x);
|
||||||
CT_EQ_INT(t, 206, req.caret.y);
|
CT_EQ_INT(t, 206-Fontsz, req.caret.y);
|
||||||
wirewins[0].rootx = 150;
|
wirewins[0].rootx = 150;
|
||||||
wirewins[0].rooty = 250;
|
wirewins[0].rooty = 250;
|
||||||
kpress(&state, &ev);
|
kpress(&state, &ev);
|
||||||
req = nexttrace(t, &f, Keypress, &state);
|
req = nexttrace(t, &f, Keypress, &state);
|
||||||
CT_EQ_INT(t, 155, req.caret.x);
|
CT_EQ_INT(t, 155, req.caret.x);
|
||||||
CT_EQ_INT(t, 256, req.caret.y);
|
CT_EQ_INT(t, 256-Fontsz, req.caret.y);
|
||||||
|
|
||||||
b->preattr.spot_location.x = 15;
|
b->preattr.spot_location.x = 15;
|
||||||
b->preattr.spot_location.y = 16;
|
b->preattr.spot_location.y = 16;
|
||||||
@@ -768,7 +773,7 @@ xim_adapter_placement_updates(struct ct *t)
|
|||||||
req = nexttrace(t, &f, Keycaret, &state);
|
req = nexttrace(t, &f, Keycaret, &state);
|
||||||
CT_CHECK(t, req.caret.valid);
|
CT_CHECK(t, req.caret.valid);
|
||||||
CT_EQ_INT(t, 165, req.caret.x);
|
CT_EQ_INT(t, 165, req.caret.x);
|
||||||
CT_EQ_INT(t, 266, req.caret.y);
|
CT_EQ_INT(t, 266-Fontsz, req.caret.y);
|
||||||
b->preattrmask = 0;
|
b->preattrmask = 0;
|
||||||
wirewins[0].geometryok = 0;
|
wirewins[0].geometryok = 0;
|
||||||
hdr.major_opcode = XCB_XIM_SET_IC_FOCUS;
|
hdr.major_opcode = XCB_XIM_SET_IC_FOCUS;
|
||||||
@@ -794,7 +799,7 @@ xim_adapter_callback_replacement(struct ct *t)
|
|||||||
ic = (xcb_im_input_context_t*)(uintptr)3;
|
ic = (xcb_im_input_context_t*)(uintptr)3;
|
||||||
memset(&state, 0, sizeof state);
|
memset(&state, 0, sizeof state);
|
||||||
state.xic = ic;
|
state.xic = ic;
|
||||||
state.cap = Cclientpreedit;
|
state.clientpre = 1;
|
||||||
wireclear();
|
wireclear();
|
||||||
sinit(&pre, "k", strlen("k"));
|
sinit(&pre, "k", strlen("k"));
|
||||||
updatepreedit(&state, &pre);
|
updatepreedit(&state, &pre);
|
||||||
@@ -843,7 +848,7 @@ xim_adapter_callback_unicode(struct ct *t)
|
|||||||
for(i = 0; i < nelem(text); i++){
|
for(i = 0; i < nelem(text); i++){
|
||||||
memset(&state, 0, sizeof state);
|
memset(&state, 0, sizeof state);
|
||||||
state.xic = (xcb_im_input_context_t*)(uintptr)(10+i);
|
state.xic = (xcb_im_input_context_t*)(uintptr)(10+i);
|
||||||
state.cap = Cclientpreedit;
|
state.clientpre = 1;
|
||||||
sinit(&pre, text[i], strlen(text[i]));
|
sinit(&pre, text[i], strlen(text[i]));
|
||||||
wireclear();
|
wireclear();
|
||||||
updatepreedit(&state, &pre);
|
updatepreedit(&state, &pre);
|
||||||
@@ -863,7 +868,7 @@ startstate(Ic *state, xcb_im_input_context_t *ic)
|
|||||||
|
|
||||||
memset(state, 0, sizeof *state);
|
memset(state, 0, sizeof *state);
|
||||||
state->xic = ic;
|
state->xic = ic;
|
||||||
state->cap = Cclientpreedit;
|
state->clientpre = 1;
|
||||||
sinit(&pre, "あ", strlen("あ"));
|
sinit(&pre, "あ", strlen("あ"));
|
||||||
updatepreedit(state, &pre);
|
updatepreedit(state, &pre);
|
||||||
}
|
}
|
||||||
@@ -891,7 +896,7 @@ xim_adapter_callback_cleanup(struct ct *t)
|
|||||||
wireclear();
|
wireclear();
|
||||||
wirebind(0, ic, &state);
|
wirebind(0, ic, &state);
|
||||||
state.xic = ic;
|
state.xic = ic;
|
||||||
state.cap = Cclientpreedit;
|
state.clientpre = 1;
|
||||||
keypress(&state, 'k', 0, &res);
|
keypress(&state, 'k', 0, &res);
|
||||||
nexttrace(t, &f, Keypress, &state);
|
nexttrace(t, &f, Keypress, &state);
|
||||||
keypress(&state, 'a', 0, &res);
|
keypress(&state, 'a', 0, &res);
|
||||||
@@ -979,7 +984,7 @@ xim_adapter_callback_transfer(struct ct *t)
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
wireclear();
|
wireclear();
|
||||||
a.xic = aic;
|
a.xic = aic;
|
||||||
a.cap = Cclientpreedit;
|
a.clientpre = 1;
|
||||||
sinit(&pre, "あ", strlen("あ"));
|
sinit(&pre, "あ", strlen("あ"));
|
||||||
updatepreedit(&a, &pre);
|
updatepreedit(&a, &pre);
|
||||||
keypress(&b, 'x', 0, &res);
|
keypress(&b, 'x', 0, &res);
|
||||||
@@ -1016,7 +1021,7 @@ xim_adapter_callback_owner_loss(struct ct *t)
|
|||||||
wireclear();
|
wireclear();
|
||||||
wirebind(0, ic, &state);
|
wirebind(0, ic, &state);
|
||||||
state.xic = ic;
|
state.xic = ic;
|
||||||
state.cap = Cclientpreedit;
|
state.clientpre = 1;
|
||||||
keypress(&state, 'k', 0, &res);
|
keypress(&state, 'k', 0, &res);
|
||||||
nexttrace(t, &f, Keypress, &state);
|
nexttrace(t, &f, Keypress, &state);
|
||||||
updatepreedit(&state, &res.preedit);
|
updatepreedit(&state, &res.preedit);
|
||||||
@@ -1054,9 +1059,12 @@ xim_adapter_callback_owner_loss(struct ct *t)
|
|||||||
checkpreowner();
|
checkpreowner();
|
||||||
notrace(t, &f);
|
notrace(t, &f);
|
||||||
|
|
||||||
/* Late XIM lifecycle messages remain idempotent after owner cleanup. */
|
/* The release hands back the reading the takeover took. */
|
||||||
release(&state);
|
release(&state);
|
||||||
nexttrace(t, &f, Keyrelease, &state);
|
nexttrace(t, &f, Keyrelease, &state);
|
||||||
|
CT_EQ_INT(t, 5, nwirecalls);
|
||||||
|
CT_EQ_INT(t, Wcommit, wirecalls[4].op);
|
||||||
|
/* Late XIM lifecycle messages remain idempotent after owner cleanup. */
|
||||||
release(&state);
|
release(&state);
|
||||||
notrace(t, &f);
|
notrace(t, &f);
|
||||||
memset(&hdr, 0, sizeof hdr);
|
memset(&hdr, 0, sizeof hdr);
|
||||||
@@ -1066,7 +1074,7 @@ xim_adapter_callback_owner_loss(struct ct *t)
|
|||||||
hdr.major_opcode = XCB_XIM_DESTROY_IC;
|
hdr.major_opcode = XCB_XIM_DESTROY_IC;
|
||||||
callback(nil, nil, ic, &hdr, nil, nil, nil);
|
callback(nil, nil, ic, &hdr, nil, nil, nil);
|
||||||
notrace(t, &f);
|
notrace(t, &f);
|
||||||
CT_EQ_INT(t, 4, nwirecalls);
|
CT_EQ_INT(t, 5, nwirecalls);
|
||||||
|
|
||||||
memset(&req, 0, sizeof req);
|
memset(&req, 0, sizeof req);
|
||||||
req.owner = &foreign;
|
req.owner = &foreign;
|
||||||
@@ -1104,3 +1112,61 @@ xim_adapter_commit_encoding(struct ct *t)
|
|||||||
commit(&state, text, 0);
|
commit(&state, text, 0);
|
||||||
CT_EQ_INT(t, 0, nwirecalls);
|
CT_EQ_INT(t, 0, nwirecalls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
xim_keymap_lookup(struct ct *t)
|
||||||
|
{
|
||||||
|
enum { ShiftMask = 1<<0, LockMask = 1<<1, Mod2Mask = 1<<4,
|
||||||
|
Mod5Mask = 1<<7, Group1 = 1<<13 };
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,23 +24,14 @@ enum
|
|||||||
Eventtimeout = 4000,
|
Eventtimeout = 4000,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Child Child;
|
|
||||||
typedef struct Run Run;
|
typedef struct Run Run;
|
||||||
typedef struct Prelog Prelog;
|
typedef struct Prelog Prelog;
|
||||||
|
|
||||||
struct Child
|
|
||||||
{
|
|
||||||
pid_t pid;
|
|
||||||
pid_t pgid;
|
|
||||||
char log[320];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Run
|
struct Run
|
||||||
{
|
{
|
||||||
Live l;
|
Live l;
|
||||||
Child xvfb;
|
Daemon xvfb;
|
||||||
Child daemon;
|
Daemon daemon;
|
||||||
char display[32];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Prelog
|
struct Prelog
|
||||||
@@ -55,144 +46,10 @@ struct Prelog
|
|||||||
|
|
||||||
static int xerror;
|
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
|
static int
|
||||||
waitsocket(Run *r)
|
waitsocket(Run *r)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
int status;
|
|
||||||
int64_t deadline;
|
int64_t deadline;
|
||||||
|
|
||||||
deadline = nowms() + Starttimeout;
|
deadline = nowms() + Starttimeout;
|
||||||
@@ -200,10 +57,8 @@ waitsocket(Run *r)
|
|||||||
if(lstat(r->l.socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
if(lstat(r->l.socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
||||||
(st.st_mode & 0777) == 0600)
|
(st.st_mode & 0777) == 0600)
|
||||||
return 1;
|
return 1;
|
||||||
if(waitpid(r->daemon.pid, &status, WNOHANG) == r->daemon.pid){
|
if(!daemonalive(&r->daemon))
|
||||||
r->daemon.pid = -1;
|
|
||||||
return fail("daemon exited before creating its IPC socket");
|
return fail("daemon exited before creating its IPC socket");
|
||||||
}
|
|
||||||
pausems(20);
|
pausems(20);
|
||||||
}
|
}
|
||||||
return fail("timed out waiting for daemon IPC socket");
|
return fail("timed out waiting for daemon IPC socket");
|
||||||
@@ -213,90 +68,29 @@ static int
|
|||||||
start(Run *r, char *daemon, char *mapdir, char *xvfb)
|
start(Run *r, char *daemon, char *mapdir, char *xvfb)
|
||||||
{
|
{
|
||||||
memset(r, 0, sizeof *r);
|
memset(r, 0, sizeof *r);
|
||||||
r->xvfb.pid = r->daemon.pid = -1;
|
daemoninit(&r->xvfb, "Xvfb");
|
||||||
r->xvfb.pgid = r->daemon.pgid = -1;
|
daemoninit(&r->daemon, "daemon");
|
||||||
if(!livesetup(&r->l, "xim"))
|
if(!livesetup(&r->l, "xim") || !startxvfb(&r->l, &r->xvfb, xvfb))
|
||||||
return 0;
|
return 0;
|
||||||
if(snprintf(r->xvfb.log, sizeof r->xvfb.log, "%s/xvfb.log", r->l.root)
|
if(setenv("DISPLAY", r->l.display, 1) < 0 ||
|
||||||
>= (int)sizeof r->xvfb.log ||
|
setenv("XMODIFIERS", "@im=strans", 1) < 0)
|
||||||
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));
|
return fail("set private environment: %s", strerror(errno));
|
||||||
if(!spawndaemon(r, daemon, mapdir))
|
if(!startdaemon(&r->l, &r->daemon, daemon, mapdir))
|
||||||
return 0;
|
return 0;
|
||||||
return waitsocket(r);
|
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
|
static void
|
||||||
cleanup(Run *r, int noisy)
|
cleanup(Run *r, int noisy)
|
||||||
{
|
{
|
||||||
stopchild(&r->daemon);
|
stopdaemon(&r->daemon);
|
||||||
stopchild(&r->xvfb);
|
stopdaemon(&r->xvfb);
|
||||||
if(noisy){
|
if(noisy){
|
||||||
fprintf(stderr, "%s: daemon log:\n", testname);
|
showerrors(&r->daemon);
|
||||||
showlog(r->daemon.log);
|
showerrors(&r->xvfb);
|
||||||
fprintf(stderr, "%s: Xvfb log:\n", testname);
|
|
||||||
showlog(r->xvfb.log);
|
|
||||||
}
|
}
|
||||||
if(r->daemon.log[0] != '\0')
|
closeerrors(&r->daemon);
|
||||||
unlink(r->daemon.log);
|
closeerrors(&r->xvfb);
|
||||||
if(r->xvfb.log[0] != '\0')
|
|
||||||
unlink(r->xvfb.log);
|
|
||||||
liveclean(&r->l);
|
liveclean(&r->l);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -765,9 +559,6 @@ main(int argc, char **argv)
|
|||||||
fprintf(stderr, "usage: xim_live_test daemon mapdir [Xvfb]\n");
|
fprintf(stderr, "usage: xim_live_test daemon mapdir [Xvfb]\n");
|
||||||
return 2;
|
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";
|
xvfb = argc == 4 ? argv[3] : "Xvfb";
|
||||||
ok = start(&run, argv[1], argv[2], xvfb);
|
ok = start(&run, argv[1], argv[2], xvfb);
|
||||||
if(!ok){
|
if(!ok){
|
||||||
@@ -781,15 +572,15 @@ main(int argc, char **argv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
XSetErrorHandler(xerr);
|
XSetErrorHandler(xerr);
|
||||||
dpy = XOpenDisplay(run.display);
|
dpy = XOpenDisplay(run.l.display);
|
||||||
if(dpy == NULL){
|
if(dpy == NULL){
|
||||||
fail("open private display %s", run.display);
|
fail("open private display %s", run.l.display);
|
||||||
cleanup(&run, 1);
|
cleanup(&run, 1);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
im = openim(dpy);
|
im = openim(dpy);
|
||||||
if(im == NULL){
|
if(im == NULL){
|
||||||
fail("open strans XIM on private display %s", run.display);
|
fail("open strans XIM on private display %s", run.l.display);
|
||||||
XCloseDisplay(dpy);
|
XCloseDisplay(dpy);
|
||||||
cleanup(&run, 1);
|
cleanup(&run, 1);
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
76
trie.c
76
trie.c
@@ -3,9 +3,9 @@
|
|||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A byte trie over UTF-8 keys. Maps need prefix matches while composing;
|
* A trie over the runes of a key. Maps need prefix matches while
|
||||||
* dictionaries need exact matches; both are small enough for one structure.
|
* composing; dictionaries need exact matches; both are small enough for
|
||||||
* Node 0 is the root.
|
* one structure. Node 0 is the root.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -24,26 +24,37 @@ newnode(Trie *t)
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The children of a node are kept in rune order, so both end early. */
|
||||||
static int
|
static int
|
||||||
find(Trie *t, int ni, char c)
|
find(Trie *t, int ni, Rune c)
|
||||||
{
|
{
|
||||||
int pi;
|
int pi;
|
||||||
|
|
||||||
for(pi = t->nodes[ni].child; pi >= 0; pi = t->nodes[pi].sibling)
|
for(pi = t->nodes[ni].child; pi >= 0; pi = t->nodes[pi].sibling){
|
||||||
if(t->nodes[pi].c == c)
|
if(t->nodes[pi].c == c)
|
||||||
return pi;
|
return pi;
|
||||||
|
if(t->nodes[pi].c > c)
|
||||||
|
break;
|
||||||
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
add(Trie *t, int ni, char c)
|
add(Trie *t, int ni, Rune c)
|
||||||
{
|
{
|
||||||
int pi;
|
int next, pi, prev;
|
||||||
|
|
||||||
|
prev = -1;
|
||||||
|
for(next = t->nodes[ni].child; next >= 0 && t->nodes[next].c < c;
|
||||||
|
next = t->nodes[next].sibling)
|
||||||
|
prev = next;
|
||||||
pi = newnode(t);
|
pi = newnode(t);
|
||||||
t->nodes[pi].c = c;
|
t->nodes[pi].c = c;
|
||||||
t->nodes[pi].sibling = t->nodes[ni].child;
|
t->nodes[pi].sibling = next;
|
||||||
t->nodes[ni].child = pi;
|
if(prev < 0)
|
||||||
|
t->nodes[ni].child = pi;
|
||||||
|
else
|
||||||
|
t->nodes[prev].sibling = pi;
|
||||||
return pi;
|
return pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,15 +74,26 @@ trienew(void)
|
|||||||
void
|
void
|
||||||
trieput(Trie *t, char *key, int klen, char *val, int vlen)
|
trieput(Trie *t, char *key, int klen, char *val, int vlen)
|
||||||
{
|
{
|
||||||
int ni, ci, i;
|
Str k;
|
||||||
|
int ci, i, ni, same;
|
||||||
|
|
||||||
|
if(!sinit(&k, key, klen))
|
||||||
|
return;
|
||||||
ni = 0;
|
ni = 0;
|
||||||
for(i = 0; i < klen; i++){
|
same = 1;
|
||||||
ci = find(t, ni, key[i]);
|
for(i = 0; i < k.n; i++){
|
||||||
|
if(same && i < t->last.n && t->last.r[i] == k.r[i]){
|
||||||
|
ni = t->path[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
same = 0;
|
||||||
|
ci = find(t, ni, k.r[i]);
|
||||||
if(ci < 0)
|
if(ci < 0)
|
||||||
ci = add(t, ni, key[i]);
|
ci = add(t, ni, k.r[i]);
|
||||||
ni = ci;
|
ni = ci;
|
||||||
|
t->path[i] = ni;
|
||||||
}
|
}
|
||||||
|
t->last = k;
|
||||||
free(t->nodes[ni].val);
|
free(t->nodes[ni].val);
|
||||||
t->nodes[ni].val = emalloc(vlen + 1);
|
t->nodes[ni].val = emalloc(vlen + 1);
|
||||||
memmove(t->nodes[ni].val, val, vlen);
|
memmove(t->nodes[ni].val, val, vlen);
|
||||||
@@ -153,24 +175,30 @@ trieclose(Trie *t)
|
|||||||
free(t);
|
free(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A nil trie is an unloaded map: every key misses. */
|
/* The node key leads to, or -1. A nil trie is an unloaded map: every key misses. */
|
||||||
|
int
|
||||||
|
trienode(Trie *t, Str *key)
|
||||||
|
{
|
||||||
|
int i, ni;
|
||||||
|
|
||||||
|
if(t == nil)
|
||||||
|
return -1;
|
||||||
|
ni = 0;
|
||||||
|
for(i = 0; i < key->n && ni >= 0; i++)
|
||||||
|
ni = find(t, ni, key->r[i]);
|
||||||
|
return ni;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
trielookup(Trie *t, Str *key, char **val, int *vlen)
|
trielookup(Trie *t, Str *key, char **val, int *vlen)
|
||||||
{
|
{
|
||||||
char buf[Maxutf];
|
int ni;
|
||||||
int i, klen, ni;
|
|
||||||
|
|
||||||
*val = nil;
|
*val = nil;
|
||||||
*vlen = 0;
|
*vlen = 0;
|
||||||
if(t == nil)
|
ni = trienode(t, key);
|
||||||
|
if(ni < 0)
|
||||||
return TrieMiss;
|
return TrieMiss;
|
||||||
klen = stoutf(key, buf, sizeof buf);
|
|
||||||
ni = 0;
|
|
||||||
for(i = 0; i < klen; i++){
|
|
||||||
ni = find(t, ni, buf[i]);
|
|
||||||
if(ni < 0)
|
|
||||||
return TrieMiss;
|
|
||||||
}
|
|
||||||
if(t->nodes[ni].val == nil)
|
if(t->nodes[ni].val == nil)
|
||||||
return TriePrefix;
|
return TriePrefix;
|
||||||
*val = t->nodes[ni].val;
|
*val = t->nodes[ni].val;
|
||||||
|
|||||||
85
win.c
85
win.c
@@ -1,4 +1,5 @@
|
|||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
|
#include <xcb/xcb_aux.h>
|
||||||
#include <xcb/randr.h>
|
#include <xcb/randr.h>
|
||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
@@ -12,17 +13,7 @@ static u32int *img;
|
|||||||
static xcb_atom_t currentdesktop;
|
static xcb_atom_t currentdesktop;
|
||||||
static xcb_atom_t workarea;
|
static xcb_atom_t workarea;
|
||||||
static int hasrandr, imgh, imgw;
|
static int hasrandr, imgh, imgw;
|
||||||
|
static int shown, ptrx, ptry; /* the popup, and where the pointer was as it came up */
|
||||||
static xcb_screen_t*
|
|
||||||
getscr(xcb_connection_t *c, int n)
|
|
||||||
{
|
|
||||||
xcb_screen_iterator_t i;
|
|
||||||
|
|
||||||
for(i = xcb_setup_roots_iterator(xcb_get_setup(c)); i.rem; xcb_screen_next(&i))
|
|
||||||
if(n-- == 0)
|
|
||||||
return i.data;
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
static xcb_visualtype_t*
|
static xcb_visualtype_t*
|
||||||
getvisual(xcb_screen_t *s)
|
getvisual(xcb_screen_t *s)
|
||||||
@@ -201,7 +192,7 @@ static int
|
|||||||
wininit(void)
|
wininit(void)
|
||||||
{
|
{
|
||||||
int n;
|
int n;
|
||||||
u32int mask, vals[4];
|
u32int mask, vals[5];
|
||||||
xcb_atom_t type, tooltip;
|
xcb_atom_t type, tooltip;
|
||||||
xcb_randr_query_version_cookie_t rc;
|
xcb_randr_query_version_cookie_t rc;
|
||||||
xcb_randr_query_version_reply_t *rr;
|
xcb_randr_query_version_reply_t *rr;
|
||||||
@@ -217,7 +208,7 @@ wininit(void)
|
|||||||
wincleanup();
|
wincleanup();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
scr = getscr(conn, n);
|
scr = xcb_aux_get_screen(conn, n);
|
||||||
if(scr == nil || !validformat(conn, scr)){
|
if(scr == nil || !validformat(conn, scr)){
|
||||||
fprint(2, "strans: popup disabled: unsupported X root format\n");
|
fprint(2, "strans: popup disabled: unsupported X root format\n");
|
||||||
wincleanup();
|
wincleanup();
|
||||||
@@ -234,14 +225,17 @@ wininit(void)
|
|||||||
currentdesktop = getatom("_NET_CURRENT_DESKTOP");
|
currentdesktop = getatom("_NET_CURRENT_DESKTOP");
|
||||||
workarea = getatom("_NET_WORKAREA");
|
workarea = getatom("_NET_WORKAREA");
|
||||||
win = xcb_generate_id(conn);
|
win = xcb_generate_id(conn);
|
||||||
|
/* Clicks on the popup go nowhere, not to the root window under it. */
|
||||||
mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
|
mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
|
||||||
XCB_CW_OVERRIDE_REDIRECT | XCB_CW_SAVE_UNDER;
|
XCB_CW_OVERRIDE_REDIRECT | XCB_CW_SAVE_UNDER |
|
||||||
|
XCB_CW_DONT_PROPAGATE;
|
||||||
vals[0] = Colbg;
|
vals[0] = Colbg;
|
||||||
vals[1] = 0;
|
vals[1] = Colsep;
|
||||||
vals[2] = 1;
|
vals[2] = 1;
|
||||||
vals[3] = 1;
|
vals[3] = 1;
|
||||||
|
vals[4] = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE;
|
||||||
xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, scr->root,
|
xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, scr->root,
|
||||||
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
0, 0, 1, 1, PopupBorder, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||||
scr->root_visual, mask, vals);
|
scr->root_visual, mask, vals);
|
||||||
type = getatom("_NET_WM_WINDOW_TYPE");
|
type = getatom("_NET_WM_WINDOW_TYPE");
|
||||||
tooltip = getatom("_NET_WM_WINDOW_TYPE_TOOLTIP");
|
tooltip = getatom("_NET_WM_WINDOW_TYPE_TOOLTIP");
|
||||||
@@ -250,11 +244,7 @@ wininit(void)
|
|||||||
win, type, XCB_ATOM_ATOM, 32, 1, &tooltip);
|
win, type, XCB_ATOM_ATOM, 32, 1, &tooltip);
|
||||||
gc = xcb_generate_id(conn);
|
gc = xcb_generate_id(conn);
|
||||||
xcb_create_gc(conn, gc, win, 0, nil);
|
xcb_create_gc(conn, gc, win, 0, nil);
|
||||||
if(!textinit()){
|
textinit();
|
||||||
fprint(2, "strans: popup disabled: no usable fonts\n");
|
|
||||||
wincleanup();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,43 +296,54 @@ putimage(int w, int h)
|
|||||||
xcb_clear_area(conn, 0, win, 0, 0, w, h);
|
xcb_clear_area(conn, 0, win, 0, 0, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static void
|
||||||
winhide(void)
|
winhide(void)
|
||||||
{
|
{
|
||||||
xcb_unmap_window(conn, win);
|
xcb_unmap_window(conn, win);
|
||||||
return xcb_flush(conn) > 0;
|
xcb_flush(conn);
|
||||||
|
shown = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Draws dc at the caret, or by the pointer when the caret is unknown. */
|
/*
|
||||||
static int
|
* Draws dc at the caret, or by the pointer when the caret is unknown:
|
||||||
|
* where the pointer was as the popup came up, so that it does not
|
||||||
|
* follow the mouse from key to key.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
winshow(Drawcmd *dc)
|
winshow(Drawcmd *dc)
|
||||||
{
|
{
|
||||||
Area area;
|
Area area;
|
||||||
Popup p;
|
Popup p;
|
||||||
int ax, ay, px, py, x, y;
|
int ax, ay, x, y;
|
||||||
u32int vals[5];
|
u32int vals[5];
|
||||||
xcb_query_pointer_reply_t *ptr;
|
xcb_query_pointer_reply_t *ptr;
|
||||||
xcb_query_pointer_cookie_t cookie;
|
xcb_query_pointer_cookie_t cookie;
|
||||||
|
|
||||||
if(dc->nkouho == 0 && dc->pre.n == 0)
|
if(dc->nkouho == 0 && dc->pre.n == 0){
|
||||||
return winhide();
|
winhide();
|
||||||
px = py = 0;
|
return;
|
||||||
if(!dc->caret.valid){
|
}
|
||||||
|
if(!dc->caret.valid && !shown){
|
||||||
cookie = xcb_query_pointer(conn, scr->root);
|
cookie = xcb_query_pointer(conn, scr->root);
|
||||||
ptr = xcb_query_pointer_reply(conn, cookie, nil);
|
ptr = xcb_query_pointer_reply(conn, cookie, nil);
|
||||||
if(ptr == nil)
|
if(ptr == nil)
|
||||||
return 0;
|
return;
|
||||||
px = ptr->root_x;
|
ptrx = ptr->root_x;
|
||||||
py = ptr->root_y;
|
ptry = ptr->root_y;
|
||||||
free(ptr);
|
free(ptr);
|
||||||
}
|
}
|
||||||
ax = dc->caret.valid ? dc->caret.x : px;
|
ax = dc->caret.valid ? dc->caret.x : ptrx;
|
||||||
ay = dc->caret.valid ? dc->caret.y : py;
|
ay = dc->caret.valid ? dc->caret.y : ptry;
|
||||||
popupwork(ax, ay, &area);
|
popupwork(ax, ay, &area);
|
||||||
popuplayout(dc, area.w, area.h, &p);
|
popuplayout(dc, area.w, area.h, &p);
|
||||||
if(p.w <= 0 || p.h <= 0 || !resizebacking(p.w, p.h))
|
if(p.w <= 0 || p.h <= 0){
|
||||||
return 0;
|
winhide();
|
||||||
popupposition(&dc->caret, px, py, &area, p.w, p.h, &x, &y);
|
return;
|
||||||
|
}
|
||||||
|
if(!resizebacking(p.w, p.h))
|
||||||
|
return;
|
||||||
|
popupposition(&dc->caret, ptrx, ptry, &area, p.w + 2*PopupBorder,
|
||||||
|
p.h + 2*PopupBorder, &x, &y);
|
||||||
/* Mapping does not restack: raise above windows opened since. */
|
/* Mapping does not restack: raise above windows opened since. */
|
||||||
vals[0] = x;
|
vals[0] = x;
|
||||||
vals[1] = y;
|
vals[1] = y;
|
||||||
@@ -356,7 +357,8 @@ winshow(Drawcmd *dc)
|
|||||||
popupdraw(img, dc, &p);
|
popupdraw(img, dc, &p);
|
||||||
putimage(p.w, p.h);
|
putimage(p.w, p.h);
|
||||||
xcb_map_window(conn, win);
|
xcb_map_window(conn, win);
|
||||||
return xcb_flush(conn) > 0;
|
xcb_flush(conn);
|
||||||
|
shown = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -367,8 +369,7 @@ drawthread(void*)
|
|||||||
threadsetname("draw");
|
threadsetname("draw");
|
||||||
if(!wininit())
|
if(!wininit())
|
||||||
return;
|
return;
|
||||||
while(chanrecv(drawc, &dc) > 0)
|
while(chanrecv(drawc, &dc) > 0 && !xcb_connection_has_error(conn))
|
||||||
if(!winshow(&dc))
|
winshow(&dc);
|
||||||
break;
|
|
||||||
wincleanup();
|
wincleanup();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,17 @@
|
|||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
|
#include <xcb/xcb_aux.h>
|
||||||
#include <xcb/xkb.h>
|
#include <xcb/xkb.h>
|
||||||
#include <xkbcommon/xkbcommon.h>
|
#include <xkbcommon/xkbcommon.h>
|
||||||
#include <xkbcommon/xkbcommon-compose.h>
|
#include <xkbcommon/xkbcommon-names.h>
|
||||||
#include <xkbcommon/xkbcommon-x11.h>
|
#include <xkbcommon/xkbcommon-x11.h>
|
||||||
#include <xcb-imdkit/imdkit.h>
|
#include <xcb-imdkit/imdkit.h>
|
||||||
#include <xcb-imdkit/encoding.h>
|
#include <xcb-imdkit/encoding.h>
|
||||||
|
|
||||||
u32int keymaplookup(struct xkb_state*, uint8_t, u16int);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* One XIM input context. Only PreeditCallbacks clients draw the preedit
|
* One XIM input context. Only PreeditCallbacks clients draw the preedit
|
||||||
* themselves (cap); the popup shows it for the others. engaged means the
|
* themselves (clientpre); the popup shows it for the others. engaged means the
|
||||||
* engine has seen a real key from this context and owes it a release.
|
* engine has seen a real key from this context and owes it a release.
|
||||||
*/
|
*/
|
||||||
typedef struct Ic Ic;
|
typedef struct Ic Ic;
|
||||||
@@ -26,21 +25,15 @@ struct Ic
|
|||||||
xcb_im_input_context_t *xic;
|
xcb_im_input_context_t *xic;
|
||||||
xcb_im_client_t *client;
|
xcb_im_client_t *client;
|
||||||
int engaged;
|
int engaged;
|
||||||
int cap;
|
int clientpre;
|
||||||
int prestarted;
|
int prestarted;
|
||||||
int nprerune;
|
int nprerune;
|
||||||
Caret caret;
|
Caret caret;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Ownerpoll = 200, /* ms between owner checks while a preedit shows */
|
|
||||||
};
|
|
||||||
|
|
||||||
static xcb_connection_t *conn;
|
static xcb_connection_t *conn;
|
||||||
static xcb_im_t *xim;
|
static xcb_im_t *xim;
|
||||||
static struct xkb_state *kstate;
|
static struct xkb_state *kstate;
|
||||||
static struct xkb_compose_state *compose;
|
|
||||||
static uint8_t xkbevent;
|
static uint8_t xkbevent;
|
||||||
static xcb_window_t rootwin;
|
static xcb_window_t rootwin;
|
||||||
static Ic *ics;
|
static Ic *ics;
|
||||||
@@ -52,6 +45,9 @@ static u32int styles[] = {
|
|||||||
XCB_IM_PreeditPosition | XCB_IM_StatusNothing,
|
XCB_IM_PreeditPosition | XCB_IM_StatusNothing,
|
||||||
XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing,
|
XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing,
|
||||||
XCB_IM_PreeditNothing | XCB_IM_StatusNothing,
|
XCB_IM_PreeditNothing | XCB_IM_StatusNothing,
|
||||||
|
XCB_IM_PreeditPosition | XCB_IM_StatusNone,
|
||||||
|
XCB_IM_PreeditCallbacks | XCB_IM_StatusNone,
|
||||||
|
XCB_IM_PreeditNothing | XCB_IM_StatusNone,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -99,26 +95,6 @@ out:
|
|||||||
return ok;
|
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
|
static void
|
||||||
kwatch(void)
|
kwatch(void)
|
||||||
{
|
{
|
||||||
@@ -135,18 +111,6 @@ kwatch(void)
|
|||||||
events, parts, parts, nil);
|
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
|
static int
|
||||||
translate(xcb_window_t win, int x, int y, Caret *caret)
|
translate(xcb_window_t win, int x, int y, Caret *caret)
|
||||||
{
|
{
|
||||||
@@ -210,8 +174,13 @@ place(Ic *state)
|
|||||||
XCB_XIM_XNSpotLocation_MASK){
|
XCB_XIM_XNSpotLocation_MASK){
|
||||||
attr = xcb_im_input_context_get_preedit_attr(state->xic);
|
attr = xcb_im_input_context_get_preedit_attr(state->xic);
|
||||||
if(translate(focuswin, attr->spot_location.x,
|
if(translate(focuswin, attr->spot_location.x,
|
||||||
attr->spot_location.y, &state->caret))
|
attr->spot_location.y, &state->caret)){
|
||||||
|
/* A spot is a baseline: call the line one row above it,
|
||||||
|
* so that a popup flipped above it clears the text. */
|
||||||
|
state->caret.y -= Fontsz;
|
||||||
|
state->caret.h = Fontsz;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(placebottom(state, focuswin))
|
if(placebottom(state, focuswin))
|
||||||
return;
|
return;
|
||||||
@@ -264,7 +233,7 @@ updatepreedit(Ic *state, Str *pre)
|
|||||||
size_t nwire;
|
size_t nwire;
|
||||||
int i, nbyte;
|
int i, nbyte;
|
||||||
|
|
||||||
if(!(state->cap & Cclientpreedit))
|
if(!state->clientpre)
|
||||||
return;
|
return;
|
||||||
if(pre->n == 0){
|
if(pre->n == 0){
|
||||||
clearpreedit(state);
|
clearpreedit(state);
|
||||||
@@ -300,7 +269,7 @@ sendrequest(Ic *state, int op, u32int key, u32int mod, Keyres *res)
|
|||||||
|
|
||||||
memset(&kr, 0, sizeof kr);
|
memset(&kr, 0, sizeof kr);
|
||||||
kr.owner = state;
|
kr.owner = state;
|
||||||
kr.cap = state->cap;
|
kr.clientpre = state->clientpre;
|
||||||
kr.op = op;
|
kr.op = op;
|
||||||
kr.ks = key;
|
kr.ks = key;
|
||||||
kr.mod = ipcmod(mod);
|
kr.mod = ipcmod(mod);
|
||||||
@@ -379,47 +348,58 @@ resetic(Ic *state, xcb_im_reset_ic_reply_fr_t *reply)
|
|||||||
reply->byte_length_of_committed_string = nwire;
|
reply->byte_length_of_committed_string = nwire;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The keysym for an X core key event, with its modifier and group state. */
|
||||||
|
static u32int
|
||||||
|
keymaplookup(struct xkb_state *state, uchar keycode, u16int corestate)
|
||||||
|
{
|
||||||
|
static 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;
|
||||||
|
|
||||||
|
keymap = xkb_state_get_keymap(state);
|
||||||
|
mods = 0;
|
||||||
|
for(i = 0; i < nelem(modname); i++){
|
||||||
|
if(!(corestate & (1 << 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);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
kpress(Ic *state, xcb_key_press_event_t *ev)
|
kpress(Ic *state, xcb_key_press_event_t *ev)
|
||||||
{
|
{
|
||||||
Keyres res;
|
Keyres res;
|
||||||
char buf[Maxutf];
|
char buf[Maxutf], text[Maxutf];
|
||||||
u32int key, sym;
|
u32int key, sym;
|
||||||
int composed, n, wasvalid;
|
int n;
|
||||||
|
|
||||||
sym = keymaplookup(kstate, ev->detail, ev->state);
|
sym = keymaplookup(kstate, ev->detail, ev->state);
|
||||||
composed = 0;
|
if(composekey(sym, text, sizeof text))
|
||||||
if(compose != nil &&
|
return;
|
||||||
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));
|
key = ipckeysym(sym, xkb_keysym_to_utf32(sym));
|
||||||
if(keymeaningful(key)){
|
if(keymeaningful(key))
|
||||||
wasvalid = state->caret.valid;
|
|
||||||
place(state);
|
place(state);
|
||||||
if(state->engaged && wasvalid && !state->caret.valid)
|
if(text[0] != '\0')
|
||||||
sendrequest(state, Keycaret, 0, 0, &res);
|
/* Composed text follows whatever was pending. */
|
||||||
}
|
sendrequest(state, Keyreset, 0, 0, &res);
|
||||||
keypress(state, key, ev->state, &res);
|
else
|
||||||
|
keypress(state, key, ev->state, &res);
|
||||||
n = stoutf(&res.commit, buf, sizeof buf);
|
n = stoutf(&res.commit, buf, sizeof buf);
|
||||||
commit(state, buf, n);
|
commit(state, buf, n);
|
||||||
updatepreedit(state, &res.preedit);
|
updatepreedit(state, &res.preedit);
|
||||||
if(res.eaten)
|
if(text[0] != '\0')
|
||||||
;
|
commit(state, text, strlen(text));
|
||||||
else if(composed){
|
else if(!res.eaten)
|
||||||
/* 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_im_forward_event(xim, state->xic, ev);
|
||||||
xcb_flush(conn);
|
xcb_flush(conn);
|
||||||
}
|
}
|
||||||
@@ -456,9 +436,8 @@ iccreate(xcb_im_client_t *client, xcb_im_input_context_t *ic)
|
|||||||
state = emalloc(sizeof(Ic));
|
state = emalloc(sizeof(Ic));
|
||||||
state->xic = ic;
|
state->xic = ic;
|
||||||
state->client = client;
|
state->client = client;
|
||||||
if(xcb_im_input_context_get_input_style(ic) ==
|
if(xcb_im_input_context_get_input_style(ic) & XCB_IM_PreeditCallbacks)
|
||||||
(XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing))
|
state->clientpre = 1;
|
||||||
state->cap = Cclientpreedit;
|
|
||||||
state->next = ics;
|
state->next = ics;
|
||||||
ics = state;
|
ics = state;
|
||||||
xcb_im_input_context_set_data(ic, state, icfree);
|
xcb_im_input_context_set_data(ic, state, icfree);
|
||||||
@@ -531,7 +510,7 @@ ximinit(void)
|
|||||||
ximlog("cannot connect to X server");
|
ximlog("cannot connect to X server");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
screen = getscreen(scr);
|
screen = xcb_aux_get_screen(conn, scr);
|
||||||
if(screen == nil){
|
if(screen == nil){
|
||||||
ximlog("cannot find X screen");
|
ximlog("cannot find X screen");
|
||||||
return -1;
|
return -1;
|
||||||
@@ -541,7 +520,6 @@ ximinit(void)
|
|||||||
ximlog("cannot read keyboard mapping");
|
ximlog("cannot read keyboard mapping");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
cinit();
|
|
||||||
kwatch();
|
kwatch();
|
||||||
win = xcb_generate_id(conn);
|
win = xcb_generate_id(conn);
|
||||||
xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, screen->root,
|
xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, screen->root,
|
||||||
37
xim/keymap.c
37
xim/keymap.c
@@ -1,37 +0,0 @@
|
|||||||
#include <stdint.h>
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xkbcommon/xkbcommon.h>
|
|
||||||
#include <xkbcommon/xkbcommon-names.h>
|
|
||||||
|
|
||||||
/* The keysym for an X core key event, with its modifier and group state. */
|
|
||||||
uint32_t
|
|
||||||
keymaplookup(struct xkb_state *state, uint8_t keycode, uint16_t corestate)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user