Compare commits
204 Commits
24e380651c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f77e0e9ac | |||
| 761428dbc4 | |||
| 1bc0ba0491 | |||
| 74607afef8 | |||
| 591029237e | |||
| bdf05e3c4a | |||
| 4802b6593e | |||
| ba0a08abe2 | |||
| 08d0e0fec8 | |||
| d3d46c8cb1 | |||
| b08f3bb7a2 | |||
| cf0eba6642 | |||
| 5d9a2a614e | |||
| 921e632eae | |||
| 910bf51347 | |||
| f7277f54a3 | |||
| 997e4c8d93 | |||
| dab80b3a77 | |||
| d9e27e8b01 | |||
| 5abd885e11 | |||
| a351f50308 | |||
| c9c01fdeb9 | |||
| bfcd9f6786 | |||
| 3f8f51adf4 | |||
| 8ebe6b32df | |||
| b61c04b280 | |||
| dadda19b3a | |||
| 62ddc2177b | |||
| 82078a60e7 | |||
| 46ab39ab95 | |||
| 65066eef53 | |||
| 0f128a46b3 | |||
| 650f00812d | |||
| e64e4ea6af | |||
| 867c6ebe40 | |||
| 8a08efe2bb | |||
| 5ff54237ff | |||
| bbc0c97656 | |||
| aef577026d | |||
| 8472474332 | |||
| 33710178d4 | |||
| 5cb9f69590 | |||
| 6d511919e8 | |||
| 40fd4ab551 | |||
| 841d93ac35 | |||
| f6dd056ffb | |||
| ad2f2fc5eb | |||
| 2e1e0b9645 | |||
| 5664a008de | |||
| 4ac0625c6b | |||
| 233884e51f | |||
| 27180bc0fe | |||
| f6477cb97d | |||
| 059e8bbc75 | |||
| e93ec43706 | |||
| 544ed835b7 | |||
| ae7dbae993 | |||
| 2e53627b7d | |||
| 3527bcd489 | |||
| 6a6749e824 | |||
| c2aec44aa9 | |||
| cf8eca2ff1 | |||
| d129f406d5 | |||
| e54783dc92 | |||
| 3a90826238 | |||
| 0ba7dc7f50 | |||
| ef7fb627c6 | |||
| a0e83f98c6 | |||
| 34c0c501aa | |||
| 0f684ae6d3 | |||
| c102c87d55 | |||
| bfa919f623 | |||
| 5a6f352d72 | |||
| 88ad5f7630 | |||
| 33023d7f51 | |||
| 46bb8b1d46 | |||
| 82cb07eba2 | |||
| b979afc3f7 | |||
| dd389edc4f | |||
| 268b687ee6 | |||
| e32cea3296 | |||
| a03fb05324 | |||
| 42568ab020 | |||
| 7c9e736996 | |||
| 0cb395579f | |||
| 33848709a5 | |||
| 47f9801ad8 | |||
| fcdce7dc16 | |||
| 0c4271b8b5 | |||
| bab40c95a7 | |||
| 87cb03fdf8 | |||
| cd637be4ca | |||
| 5faefd9a87 | |||
| 3d862bdc0d | |||
| 97a838eda1 | |||
| 1c8d9fac07 | |||
| 97e6f2cf1a | |||
| 852e129f62 | |||
| 1285476b41 | |||
| f0498868eb | |||
| ab0366b7f7 | |||
| f61f7ab5f8 | |||
| 95dff0b82b | |||
| abff5ed122 | |||
| 221117f93d | |||
| b77400fa86 | |||
| 63fc60dc06 | |||
| e8519b4e06 | |||
| ea363ca792 | |||
| bab6077c75 | |||
| 48d3e165c3 | |||
| 6b1de2df4f | |||
| 45c6be7e1b | |||
| 4ff696d06c | |||
| 30abd05e68 | |||
| 693a1210a6 | |||
| 91b3e538f1 | |||
| 5e72a48c2c | |||
| 11bf379ad3 | |||
| 4c8954b8ae | |||
| ae1bc51256 | |||
| e860008d84 | |||
| 32a9723043 | |||
| 6402c26830 | |||
| 9853d65d4c | |||
| cdd3e38195 | |||
| f6adb117c9 | |||
| 333300b297 | |||
| 4b6cc6fb2f | |||
| 0aef3506b6 | |||
| 723a271a9c | |||
| 3f1ff03b94 | |||
| 68678283cd | |||
| 03c196a7f6 | |||
| 8831b84ffc | |||
| 45c0290fd4 | |||
| dde9fb431b | |||
| 7220286ab5 | |||
| e1c36af9ba | |||
| 7c73e4a463 | |||
| 795f11be22 | |||
| 826da31486 | |||
| 849bf1278b | |||
| eaf31da0d1 | |||
| c2baa47026 | |||
| a70cfc42e3 | |||
| 9080bb833c | |||
| 68869e2589 | |||
| a557fb114a | |||
| abaea77248 | |||
| ebcec3af6b | |||
| c7718ece52 | |||
| eb0f88f764 | |||
| 43b469f70b | |||
| 66ea2892f8 | |||
| d3c9813f6e | |||
| 61840fc108 | |||
| c1971c8096 | |||
| 7047c57d29 | |||
| a1ef8a4744 | |||
| 67ed2ef478 | |||
| f489c56fb1 | |||
| d29d43f443 | |||
| aeb6010f3c | |||
| f0abace40b | |||
| 7468d573ec | |||
| 133cc97e55 | |||
| 516ec98a70 | |||
| bfe83d0119 | |||
| 38475318db | |||
| 59482d00f3 | |||
| 8df43cecd8 | |||
| a637f457f7 | |||
| 9804c1f55c | |||
| 534ddcd9bb | |||
| 5dc2530951 | |||
| f2fb71cd89 | |||
| 77c6dd66ca | |||
| 0471d58dec | |||
| f222b4d573 | |||
| 3576c4ea68 | |||
| e4fc327ffd | |||
| 193ed009bd | |||
| 7619d2bd9d | |||
| a841894015 | |||
| 4cac6f1a15 | |||
| 2e6d7d8e48 | |||
| 76f9e1a21f | |||
| 6a58b63223 | |||
| b536573622 | |||
| 9a3a92170e | |||
| 76af1a022f | |||
| 68dc1e6a18 | |||
| e6bf32b0f6 | |||
| 2b4c509d0d | |||
| 2376035524 | |||
| c1b1b7016e | |||
| 4eba8c2904 | |||
| f5dc552c08 | |||
| 4e07699460 | |||
| 08369e718e | |||
| 54355b8190 | |||
| 279b9230a4 | |||
| c2cb18cbdd |
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,15 +1,21 @@
|
|||||||
/*.o
|
/*.o
|
||||||
/strans
|
/strans
|
||||||
|
/imv2.c
|
||||||
|
/imv2.h
|
||||||
|
/vkv1.c
|
||||||
|
/vkv1.h
|
||||||
/tests/*.o
|
/tests/*.o
|
||||||
/tests/unit_test
|
/tests/unit_test
|
||||||
|
/tests/stress_test
|
||||||
/tests/ibus_live_test
|
/tests/ibus_live_test
|
||||||
|
/tests/ibus_client_smoke
|
||||||
|
/tests/gtk_live_test
|
||||||
|
/tests/xim_live_test
|
||||||
/tests/ipc_live_test
|
/tests/ipc_live_test
|
||||||
/tests/daemon_collision_test
|
/tests/daemon_collision_test
|
||||||
/tests/daemon_failure_test
|
/tests/daemon_failure_test
|
||||||
/tests/daemon_restart_test
|
/tests/daemon_restart_test
|
||||||
/xim/*.o
|
|
||||||
/xim/strans-xim
|
|
||||||
/xim/xim_test
|
|
||||||
/gtk/im-strans.so
|
/gtk/im-strans.so
|
||||||
/bench/bench
|
/bench/bench
|
||||||
/bench/perf.data*
|
/bench/perf.data*
|
||||||
|
/ref-*/
|
||||||
|
|||||||
27
Dockerfile
27
Dockerfile
@@ -1,27 +1,50 @@
|
|||||||
FROM archlinux:base@sha256:b0deabeb3d283da2c7f7dbf0eea051b7b2cd0554e0b737cc457fd21683bdcdd1
|
FROM archlinux:base@sha256:b0deabeb3d283da2c7f7dbf0eea051b7b2cd0554e0b737cc457fd21683bdcdd1
|
||||||
|
|
||||||
RUN pacman -Syu --noconfirm --needed \
|
RUN pacman -Syu --noconfirm --needed \
|
||||||
|
cairo \
|
||||||
|
fontconfig \
|
||||||
diffutils \
|
diffutils \
|
||||||
freetype2 \
|
|
||||||
gcc \
|
gcc \
|
||||||
gtk3 \
|
gtk3 \
|
||||||
|
ibus \
|
||||||
|
harfbuzz \
|
||||||
|
libx11 \
|
||||||
libxcb \
|
libxcb \
|
||||||
libxkbcommon \
|
libxkbcommon \
|
||||||
make \
|
make \
|
||||||
|
noto-fonts-emoji \
|
||||||
|
pango \
|
||||||
pkgconf \
|
pkgconf \
|
||||||
plan9port \
|
plan9port \
|
||||||
python \
|
python \
|
||||||
ttf-dejavu \
|
ttf-dejavu \
|
||||||
ttf-jigmo \
|
ttf-jigmo \
|
||||||
|
valgrind \
|
||||||
|
wayland \
|
||||||
which \
|
which \
|
||||||
|
xorg-server-xvfb \
|
||||||
xcb-imdkit \
|
xcb-imdkit \
|
||||||
|
xcb-util \
|
||||||
dbus \
|
dbus \
|
||||||
&& pacman -Scc --noconfirm
|
&& pacman -Scc --noconfirm
|
||||||
|
|
||||||
RUN printf "egrep='grep -E'\n" > /usr/lib/plan9/config
|
RUN export DEBUGINFOD_URLS=https://debuginfod.archlinux.org \
|
||||||
|
&& buildid="$(readelf -n /usr/lib/ld-linux-x86-64.so.2 | sed -n 's/.*Build ID: //p')" \
|
||||||
|
&& test -n "$buildid" \
|
||||||
|
&& debugdir="/usr/lib/debug/.build-id/$(printf '%.2s' "$buildid")" \
|
||||||
|
&& mkdir -p "$debugdir" \
|
||||||
|
&& install -m 0644 "$(debuginfod-find debuginfo /usr/lib/ld-linux-x86-64.so.2)" \
|
||||||
|
"$debugdir/${buildid#??}.debug" \
|
||||||
|
&& rm -rf /root/.cache/debuginfod_client
|
||||||
|
|
||||||
RUN dbus-uuidgen --ensure=/etc/machine-id
|
RUN dbus-uuidgen --ensure=/etc/machine-id
|
||||||
|
|
||||||
ENV PLAN9=/usr/lib/plan9
|
ENV PLAN9=/usr/lib/plan9
|
||||||
ENV PATH=$PATH:/usr/lib/plan9/bin
|
ENV PATH=$PATH:/usr/lib/plan9/bin
|
||||||
|
|
||||||
|
# 9c and 9l filter their own output through $egrep, which plan9port never
|
||||||
|
# assigns, so the pipeline dies and takes every warning and error with it.
|
||||||
|
# Both source $PLAN9/config, which the distribution does not ship.
|
||||||
|
RUN echo 'egrep="grep -E"' > $PLAN9/config
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
# License inventory
|
# License inventory
|
||||||
|
|
||||||
- `BSD-3-Clause-libhangul-hanja.txt` contains the terms for the
|
- `BSD-3-Clause-libhangul-hanja.txt` contains the terms for the Hanja data
|
||||||
single-character Hanja data in `map/hanja.src` and generated
|
in `map/hanja.src` and the symbol data in `map/mssymbol.src`, and for the
|
||||||
`map/hanja.dict`, derived from libhangul's `data/hanja/hanja.txt`.
|
generated `map/hanja.dict` they share, derived from libhangul's
|
||||||
|
`data/hanja/hanja.txt` and `data/hanja/mssymbol.txt`.
|
||||||
- `GPL-2.0-or-later.txt` contains GNU GPL version 2. The header of
|
- `GPL-2.0-or-later.txt` contains GNU GPL version 2. The header of
|
||||||
`map/kanji.dict` grants the option to use GPL version 2 or any later
|
`map/kanji.dict` grants the option to use GPL version 2 or any later
|
||||||
version.
|
version.
|
||||||
- License texts and provenance for the historically bundled fonts are retained
|
- `Unicode-3.0.txt` contains the Unicode License v3, the terms for the
|
||||||
in `font/`; no font binary remains in the source tree.
|
emoji names and keywords in `map/emoji.src` and generated
|
||||||
|
`map/emoji.dict`, derived from Unicode's `emoji-test.txt` and CLDR's
|
||||||
|
annotations.
|
||||||
|
- The two protocol descriptions in `proto/` are vendored verbatim from
|
||||||
|
wlroots `012ca825` and carry their own MIT terms inside the XML.
|
||||||
|
|
||||||
No repository-wide license has been declared for original strans source.
|
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.
|
||||||
108
Makefile
108
Makefile
@@ -1,37 +1,65 @@
|
|||||||
CC = 9c
|
CC = 9c
|
||||||
LD = 9l
|
LD = 9l
|
||||||
PKG_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
|
PKG_CONFIG ?= pkg-config
|
||||||
PKG_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
|
SCANNER ?= wayland-scanner
|
||||||
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
|
CFLAGS ?= -O2 -g
|
||||||
FT_LIBS = $(shell pkg-config --libs freetype2)
|
WARN_CFLAGS = -Wall -Wextra
|
||||||
CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS)
|
IBUS_CFLAGS = $(shell $(PKG_CONFIG) --cflags dbus-1 xkbcommon)
|
||||||
|
IBUS_LIBS = $(shell $(PKG_CONFIG) --libs dbus-1 xkbcommon)
|
||||||
|
XIM_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
||||||
|
XIM_LIBS = $(shell $(PKG_CONFIG) --libs xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
||||||
|
TEXT_CFLAGS = $(shell $(PKG_CONFIG) --cflags pangocairo cairo fontconfig)
|
||||||
|
TEXT_LIBS = $(shell $(PKG_CONFIG) --libs pangocairo cairo fontconfig)
|
||||||
|
POPUP_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-aux xcb-randr)
|
||||||
|
POPUP_LIBS = $(shell $(PKG_CONFIG) --libs xcb-aux xcb-randr)
|
||||||
|
WL_CFLAGS = $(shell $(PKG_CONFIG) --cflags wayland-client xkbcommon)
|
||||||
|
WL_LIBS = $(shell $(PKG_CONFIG) --libs wayland-client xkbcommon)
|
||||||
|
PROJECT_CPPFLAGS =
|
||||||
|
PROJECT_LDLIBS = -lthread -lbio -lxcb $(IBUS_LIBS) $(TEXT_LIBS) \
|
||||||
|
$(XIM_LIBS) $(POPUP_LIBS) $(WL_LIBS)
|
||||||
PROG = strans
|
PROG = strans
|
||||||
DOCKER_IMAGE = strans-build
|
DOCKER_IMAGE = strans-build
|
||||||
DOCKER_RUN = docker run --rm --user "$$(id -u):$$(id -g)" \
|
DOCKER_RUN = docker run --rm --user "$$(id -u):$$(id -g)" \
|
||||||
-v "$(CURDIR):/src" $(DOCKER_IMAGE)
|
-v "$(CURDIR):/src" $(DOCKER_IMAGE)
|
||||||
|
|
||||||
SRCS = dict.c font.c hash.c ibus.c ipc.c ko.c main.c popup_layout.c \
|
SRCS = 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 wl.c xim.c
|
||||||
OBJS = $(SRCS:.c=.o)
|
OBJS = $(SRCS:.c=.o)
|
||||||
|
# wayland-scanner writes these from the XML in proto/; only the XML is tracked.
|
||||||
|
PROTO = imv2.c imv2.h vkv1.c vkv1.h
|
||||||
|
PROTOOBJ = imv2.o vkv1.o
|
||||||
|
|
||||||
all: $(PROG) xim gtk
|
all: $(PROG) gtk
|
||||||
|
|
||||||
$(PROG): $(OBJS)
|
$(PROG): $(OBJS) $(PROTOOBJ)
|
||||||
$(LD) -o $@ $(OBJS) -lthread -lbio -lxcb $(PKG_LIBS) $(FT_LIBS)
|
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(PROTOOBJ) $(PROJECT_LDLIBS) $(LDLIBS)
|
||||||
|
|
||||||
$(OBJS): dat.h fn.h ipc.h
|
$(OBJS): dat.h fn.h ipc.h
|
||||||
font.o: CFLAGS += $(FT_CFLAGS)
|
wl.o: imv2.h vkv1.h
|
||||||
|
compose.o ibus.o: PROJECT_CPPFLAGS += $(IBUS_CFLAGS)
|
||||||
|
font.o: PROJECT_CPPFLAGS += $(TEXT_CFLAGS)
|
||||||
|
win.o: PROJECT_CPPFLAGS += $(POPUP_CFLAGS)
|
||||||
|
wl.o $(PROTOOBJ): PROJECT_CPPFLAGS += $(WL_CFLAGS)
|
||||||
|
xim.o: PROJECT_CPPFLAGS += $(XIM_CFLAGS)
|
||||||
|
|
||||||
|
%.o: %.c
|
||||||
|
$(CC) $(CPPFLAGS) $(PROJECT_CPPFLAGS) $(WARN_CFLAGS) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
imv2.h: proto/input-method-unstable-v2.xml
|
||||||
|
$(SCANNER) client-header $< $@
|
||||||
|
imv2.c: proto/input-method-unstable-v2.xml
|
||||||
|
$(SCANNER) private-code $< $@
|
||||||
|
vkv1.h: proto/virtual-keyboard-unstable-v1.xml
|
||||||
|
$(SCANNER) client-header $< $@
|
||||||
|
vkv1.c: proto/virtual-keyboard-unstable-v1.xml
|
||||||
|
$(SCANNER) private-code $< $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJS) $(PROG)
|
rm -f $(OBJS) $(PROG) $(PROTO) $(PROTOOBJ)
|
||||||
$(MAKE) -C tests clean
|
$(MAKE) -C tests clean
|
||||||
$(MAKE) -C xim/ clean
|
|
||||||
$(MAKE) -C gtk/ clean
|
$(MAKE) -C gtk/ clean
|
||||||
$(MAKE) -C bench/ clean
|
$(MAKE) -C bench/ clean
|
||||||
|
|
||||||
xim:
|
|
||||||
$(MAKE) -C xim/
|
|
||||||
|
|
||||||
gtk:
|
gtk:
|
||||||
$(MAKE) -C gtk/
|
$(MAKE) -C gtk/
|
||||||
|
|
||||||
@@ -42,31 +70,49 @@ docker-image: Dockerfile
|
|||||||
docker build -t $(DOCKER_IMAGE) - < Dockerfile
|
docker build -t $(DOCKER_IMAGE) - < Dockerfile
|
||||||
|
|
||||||
docker-build:
|
docker-build:
|
||||||
$(DOCKER_RUN) make -B all
|
$(DOCKER_RUN) sh -c 'make clean && make all'
|
||||||
|
|
||||||
docker-check:
|
docker-check:
|
||||||
$(DOCKER_RUN) make -B check TESTARGS="$(TESTARGS)"
|
$(DOCKER_RUN) sh -c 'make clean && \
|
||||||
|
make check check-live check-stress UNITARGS="$(UNITARGS)"'
|
||||||
|
|
||||||
|
docker-check-live:
|
||||||
|
$(DOCKER_RUN) sh -c 'make clean && make check-live'
|
||||||
|
|
||||||
|
docker-check-stress:
|
||||||
|
$(DOCKER_RUN) sh -c 'make clean && make check-stress'
|
||||||
|
|
||||||
docker-bench:
|
docker-bench:
|
||||||
$(DOCKER_RUN) make -B bench
|
$(DOCKER_RUN) sh -c 'make clean && make bench'
|
||||||
|
|
||||||
|
docker-valgrind:
|
||||||
|
$(DOCKER_RUN) sh -c 'make clean && make -C tests unit_test && \
|
||||||
|
cd tests && valgrind -q --error-exitcode=1 ./unit_test $(UNITARGS)'
|
||||||
|
|
||||||
docker: docker-image
|
docker: docker-image
|
||||||
$(MAKE) docker-build
|
$(MAKE) docker-build
|
||||||
|
|
||||||
check: verify-map test
|
check: verify-map
|
||||||
$(MAKE) -C xim check
|
$(MAKE) -C tests check UNITARGS="$(UNITARGS)"
|
||||||
|
|
||||||
test: $(PROG)
|
check-live: $(PROG) gtk
|
||||||
$(MAKE) -C tests check TESTARGS="$(TESTARGS)"
|
$(MAKE) -C tests check-live
|
||||||
|
|
||||||
|
check-stress: $(PROG)
|
||||||
|
$(MAKE) -C tests check-stress
|
||||||
|
|
||||||
|
test: check
|
||||||
|
|
||||||
verify-map:
|
verify-map:
|
||||||
|
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 test verify-map clean xim gtk bench docker docker-image \
|
.PHONY: all check check-live check-stress test verify-map clean gtk bench \
|
||||||
docker-build docker-check docker-bench
|
docker docker-image docker-build docker-check docker-check-live \
|
||||||
|
docker-check-stress docker-bench docker-valgrind
|
||||||
|
|||||||
340
README.md
340
README.md
@@ -1,210 +1,194 @@
|
|||||||
# 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,
|
||||||
and emoji. It provides one engine shared by IBus, XIM, and GTK 3 frontends.
|
emoji and symbols, with Vietnamese Telex as a compatibility mode. One
|
||||||
The existing Vietnamese Telex mode remains available for compatibility, but
|
engine serves four frontends: `zwp_input_method_v2` on Wayland — sway and
|
||||||
is not an actively developed language mode.
|
any other compositor that offers it — IBus, which GTK 4 and Qt use, XIM,
|
||||||
|
and a GTK 3 module.
|
||||||
|
|
||||||
## Input behavior
|
## Modes
|
||||||
|
|
||||||
English is direct passthrough. Korean uses 2-beolsik composition. Japanese
|
| Key | Mode |
|
||||||
Hiragana keeps the complete kana reading until it is committed, so `kanji`
|
| --- | --- |
|
||||||
forms `かんじ` and offers whole-reading candidates such as `漢字` and `幹事`.
|
| `Ctrl+S` | Korean Hangul (2-beolsik) |
|
||||||
An apostrophe resolves an ambiguous `n` without forwarding the apostrophe:
|
| `Ctrl+N` | Japanese Hiragana, converting to Kanji |
|
||||||
for example, `n'ya` forms `んや`. Katakana is direct Katakana composition
|
| `Ctrl+K` | Japanese Katakana |
|
||||||
and does not perform Kanji dictionary conversion.
|
| `Ctrl+T` | English |
|
||||||
|
| `Ctrl+V` | Vietnamese Telex |
|
||||||
|
| `Ctrl+E` | Emoji and symbol search |
|
||||||
|
| `Ctrl+H` | One-shot Hanja and symbol search |
|
||||||
|
|
||||||
A Japanese composition is committed by Enter, candidate selection, a real
|
Only a plain `Ctrl` chord is a strans key: with `Shift`, `Alt` or `Super`
|
||||||
input boundary, or an explicit mode change. Backspace first removes pending
|
held it commits what is pending and goes to the application, so
|
||||||
romaji, then accumulated kana. The Japanese modes support doubled consonants
|
`Ctrl+Shift+V` still pastes. `Backspace`, `Enter`, `Tab`, `Esc` and the
|
||||||
and combinations such as `nya`, `nnya`, and `matcha`.
|
arrow and page keys do the same under `Ctrl`, so `Ctrl+Backspace` still
|
||||||
|
deletes a word. The mode switched to — `한`, `あ`, `ア`, `ă`, `A` — shows
|
||||||
|
in the popup until the next key. A Korean keyboard's 한/영 and 한자 keys
|
||||||
|
stand for `Ctrl+S`/`Ctrl+T` and `Ctrl+H`; a Japanese one's 半角/全角,
|
||||||
|
ひらがな/カタカナ, 変換 and 無変換 for the kana modes, `Space` and English.
|
||||||
|
|
||||||
Modes and temporary searches use Ctrl and one letter:
|
## Composing
|
||||||
|
|
||||||
```text
|
Japanese composes a whole reading and then offers its Kanji with none
|
||||||
Ctrl+N Japanese Hiragana and Kanji conversion
|
chosen. `Space` and `Tab` step through the candidates and wrap (`Shift`
|
||||||
Ctrl+K Japanese Katakana
|
reverses), the reading in Katakana last, so a word the dictionary lacks
|
||||||
Ctrl+S Korean Hangul
|
converts with one `Space`; `Up`/`Down` and `PageUp`/`PageDown` move without
|
||||||
Ctrl+T English passthrough
|
wrapping. A chosen candidate takes the reading's place in the preedit, so
|
||||||
Ctrl+V Vietnamese Telex compatibility mode
|
it stands where it will land, and `Enter` commits it, or the reading, as
|
||||||
Ctrl+E Emoji and symbol search
|
`0` and typing on always do. Once a candidate is chosen `1`-`9` take that
|
||||||
Ctrl+H One-shot Hanja search
|
row of the page shown; before that the digits type. `Backspace` deletes
|
||||||
Ctrl+P Toggle popup preedit
|
the last kana shown, and romaji that made no kana stays as typed until it
|
||||||
```
|
is mended; `Esc` cancels the reading, and on a chosen candidate both go
|
||||||
|
back to it. The dictionary keys verbs and adjectives by stem and
|
||||||
|
okurigana, so `kaku` offers かく's readings and then 書く and its kin —
|
||||||
|
typing the okurigana with `Shift`, as SKK does, `kaKu`, asks for that
|
||||||
|
split first. Katakana mode does not convert, so there `Space` and `Tab`
|
||||||
|
commit the reading and go on to the application.
|
||||||
|
|
||||||
While candidates are visible, Up and Down move the selection, Enter accepts
|
Korean composes one syllable at a time. `Enter`, `Tab`, `Esc` and any key
|
||||||
the selected candidate, and an unmodified `1`-`9` selects the corresponding
|
that is not a jamo commit the syllable and go on to the application, so
|
||||||
visible row. Tab cycles temporary search results. `0` commits an unconverted
|
`Esc` still leaves insert mode. Two lone consonants join into the compound
|
||||||
language reading. Escape cancels the current composition. Ctrl, Alt, or
|
final they make (rt → ㄳ), and a vowel typed before its consonant is
|
||||||
Super combined with a number does not select a candidate.
|
reordered under it (kr → 가), as in libhangul.
|
||||||
|
|
||||||
Emoji search is temporary and returns to the previous mode. Queries may use
|
A search takes the keys until `Enter`, `Space` or `1`-`9` picks a result or
|
||||||
English aliases or the active language's input rules, including Japanese
|
`Esc` cancels, then returns to the previous mode. Emoji matches the typed
|
||||||
`egao` becoming `えがお` or `エガオ`:
|
keys, and what they spell in the current mode, against a prefix of every
|
||||||
|
emoji's CLDR name and keywords in English, Korean and Japanese, and against
|
||||||
|
ASCII aliases such as `->` and `<=`. `Ctrl+H` takes the syllable being
|
||||||
|
composed as its query and composes on from it, converting a word as well as
|
||||||
|
a syllable — 한자 gives 漢字, 대한민국 gives 大韓民國 — and `Esc` gives the
|
||||||
|
syllable back. A word is committed a syllable at a time, so the reading
|
||||||
|
also reaches back into the text the application already holds, as far as
|
||||||
|
the dictionary knows the whole of it: type 한자 and then `Ctrl+H` and the
|
||||||
|
query is 한자, not 자, and picking 漢字 takes the 한 back. `Backspace`
|
||||||
|
gives that reach back before it deletes what was typed, so 상 written
|
||||||
|
with 태 typed answers 狀態, and one `Backspace` answers 態 for the
|
||||||
|
syllable alone. A reading is also the start of the longer words it
|
||||||
|
begins, whose conversions follow its own, so 대한 answers 大寒 first and
|
||||||
|
大韓民國 further down. A lone consonant is a reading too, and answers
|
||||||
|
with the symbol table a Korean keyboard's 한자 key has always offered:
|
||||||
|
ㅁ gives ※ ○ △ ㈜, ㄴ the brackets 「」『』, ㄹ the units ℃ ㎏ ℓ, ㅇ the
|
||||||
|
circled numbers ①②③.
|
||||||
|
|
||||||
```text
|
Reaching back needs the application to hand over the text around its
|
||||||
Ctrl+E → smile → Enter → 😀
|
cursor and to take some of it away again. The Wayland input method, IBus
|
||||||
```
|
and the GTK 3 module all can; XIM has no such request, so there the
|
||||||
|
reading is what is still being composed and nothing else, and an IBus
|
||||||
|
client that reads a key's effects only after the call returns is not
|
||||||
|
asked for its text either, since a deletion could not be ordered before
|
||||||
|
the commit.
|
||||||
|
|
||||||
Hanja search is temporary and returns to the active language after one
|
Dead keys and Compose sequences are composed by strans itself for the
|
||||||
conversion. Press Ctrl+H and type with that language's normal input rules,
|
Wayland, XIM and IBus frontends, from `XCOMPOSEFILE` or the locale; the
|
||||||
then select with Up, Down, Tab, Enter, or `1`-`9`. The bundled dictionary
|
GTK 3 module leaves them to GtkIMContextSimple.
|
||||||
currently indexes exactly one modern Hangul syllable. Incomplete jamo,
|
|
||||||
multiple syllables, and other languages therefore have no candidates; Enter
|
## Preedit and candidates
|
||||||
commits the displayed reading unchanged. Escape cancels the search. It does
|
|
||||||
not convert words or text already surrounding the cursor.
|
| Frontend | Preedit | Candidates | Reaches back |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Wayland input method | inline in the client | popup | yes |
|
||||||
|
| GTK 3 and IBus | inline in the client | popup | yes |
|
||||||
|
| XIM PreeditCallbacks | inline through XIM callbacks | popup | no |
|
||||||
|
| XIM PreeditPosition, PreeditNothing | popup | popup | no |
|
||||||
|
|
||||||
|
On Wayland the popup is a surface the compositor places at the text cursor,
|
||||||
|
flipping it above the line when there is no room below. Elsewhere it is an
|
||||||
|
X11 window, placed from the XIM spot or from the GTK caret, and XIM text
|
||||||
|
travels as `COMPOUND_TEXT`. Either way `GDK_SCALE` sizes it for HiDPI
|
||||||
|
displays.
|
||||||
|
|
||||||
## Build and test
|
## Build and test
|
||||||
|
|
||||||
The supported build uses Docker so host and container toolchains are not
|
|
||||||
mixed. Only Docker and Make are needed on the host:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
make docker-image
|
make docker-image
|
||||||
make docker-build
|
make docker-build
|
||||||
make docker-check
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`docker-image` creates the Arch Linux dependency image, `docker-build`
|
That leaves `strans`, the daemon, and `gtk/im-strans.so`, the GTK 3 module.
|
||||||
compiles the runtime components, and `docker-check` runs the map verifiers and
|
Tests come in three tiers:
|
||||||
test suites. Docker targets force their own compilation, so newer host
|
|
||||||
objects cannot be reused. `docker` is a convenience alias for the image and
|
|
||||||
build steps.
|
|
||||||
The resulting runtime artifacts are:
|
|
||||||
|
|
||||||
```text
|
|
||||||
strans daemon and IBus frontend
|
|
||||||
xim/strans-xim XIM frontend
|
|
||||||
gtk/im-strans.so GTK 3 frontend
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `make docker-check TESTARGS=hangul` to filter the C unit tests. A native
|
|
||||||
build is still possible with the dependencies and Plan 9 setup recorded in
|
|
||||||
`Dockerfile`; use `make all`, `make check`, and `make bench` inside that
|
|
||||||
environment.
|
|
||||||
|
|
||||||
After editing an input source, regenerate and verify its dictionary with:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
make check # generated-map validation and unit tests
|
||||||
|
make check-live # one IBus, GTK, XIM and IPC daemon smoke each
|
||||||
|
make check-stress # randomized, capacity, collision, failure and restart
|
||||||
|
```
|
||||||
|
|
||||||
|
`make docker-check` runs all three in the container, and
|
||||||
|
`make docker-valgrind` the unit suite under Valgrind; rerun
|
||||||
|
`make docker-image` after changing `Dockerfile`. `UNITARGS=hangul` filters
|
||||||
|
the unit suite alone, not the other tiers.
|
||||||
|
|
||||||
|
A native build wants a C toolchain, Make, pkg-config and Plan 9 port, plus
|
||||||
|
development files for D-Bus, XCB and xcb-imdkit, Wayland and
|
||||||
|
`wayland-scanner`, xkbcommon, Pango and Cairo, and GTK 3; the tests also
|
||||||
|
want Xvfb, Python 3 and fonts covering Latin, CJK and emoji.
|
||||||
|
[`Dockerfile`](Dockerfile) names the exact packages.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./run.sh # restart the daemon in the background
|
||||||
|
./strans map # or run it in the foreground
|
||||||
|
```
|
||||||
|
|
||||||
|
`./strans DIR` reads `hira.map`, `kata.map`, `telex.map`, `kanji.dict`,
|
||||||
|
`emoji.dict` and `hanja.dict` from `DIR` at runtime; nothing but the GTK
|
||||||
|
module is installed. A service supervisor wants the session's
|
||||||
|
`XDG_RUNTIME_DIR` and its `WAYLAND_DISPLAY` or `DISPLAY`: the GTK module
|
||||||
|
finds the daemon at `$XDG_RUNTIME_DIR/strans.sock`, else
|
||||||
|
`/tmp/strans.UID`, and IBus clients through the address file libibus
|
||||||
|
expects under `~/.config/ibus/bus/`, which strans writes as `ibus-daemon`
|
||||||
|
would.
|
||||||
|
|
||||||
|
strans shows one popup per session, so it picks a frontend at startup: a
|
||||||
|
compositor offering `zwp_input_method_v2` gets the Wayland frontend, and
|
||||||
|
then neither XIM nor the X11 popup runs; every other session gets XIM. The
|
||||||
|
IBus endpoint and the GTK 3 module are served in both.
|
||||||
|
|
||||||
|
On Wayland, then, set none of the variables below. GTK, Qt and Firefox
|
||||||
|
speak text-input-v3 themselves — GTK 4 binds it with `GTK_IM_MODULE` unset
|
||||||
|
— and setting these is what pushes them off the path that works: such a
|
||||||
|
client still composes inline but shows no candidates, because the popup
|
||||||
|
belongs to the Wayland frontend. Chromium is beyond help either way, since
|
||||||
|
it asks for `text-input-v1`, which wlroots does not implement.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export XMODIFIERS=@im=strans # XIM
|
||||||
|
doas make -C gtk install # GTK 3 module
|
||||||
|
export GTK_IM_MODULE=strans
|
||||||
|
GLFW_IM_MODULE=ibus kitty # IBus client example
|
||||||
|
```
|
||||||
|
|
||||||
|
strans provides its own IBus endpoint, so `ibus-daemon` and fcitx are not
|
||||||
|
needed. Building the GTK module wants GTK development files; installing
|
||||||
|
one built elsewhere does not. The install target takes `GTK_MODULE_DIR`
|
||||||
|
when set, else asks GTK where its modules live; `doas make -C gtk uninstall`
|
||||||
|
removes it. With neither `WAYLAND_DISPLAY` nor `DISPLAY` the daemon still
|
||||||
|
serves IBus and its socket, but nothing draws a popup.
|
||||||
|
|
||||||
|
## Dictionary data
|
||||||
|
|
||||||
|
After changing an input source, regenerate and verify:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 map/mktelex.py >map/telex.map
|
||||||
map/mkemoji >map/emoji.dict
|
map/mkemoji >map/emoji.dict
|
||||||
map/mkhanja >map/hanja.dict
|
map/mkhanja >map/hanja.dict
|
||||||
make verify-map
|
make verify-map
|
||||||
```
|
```
|
||||||
|
|
||||||
Hanja import and Japanese dictionary regeneration are documented in
|
[`map/README`](map/README) says where the emoji, Hanja and Japanese data
|
||||||
[`map/README`](map/README). The converter output is deterministic UTF-8,
|
comes from.
|
||||||
tab-separated data consumable by the runtime dictionary reader.
|
|
||||||
|
|
||||||
## Run and install
|
## Benchmark
|
||||||
|
|
||||||
Install one or more scalable outline fonts, then pass their files in fallback
|
`make bench` builds `bench/bench`. With the daemon stopped, `./bench.sh`
|
||||||
order after the map directory. The daemon accepts at most four font files and
|
starts one, warms it up and records the workload with Linux `perf` into
|
||||||
does not search the system font database. For example, the Docker-tested Arch
|
`bench/perf.data`.
|
||||||
packages and paths are:
|
|
||||||
|
|
||||||
```sh
|
## Licensing
|
||||||
doas pacman -S ttf-dejavu ttf-jigmo
|
|
||||||
./strans map \
|
|
||||||
/usr/share/fonts/TTF/DejaVuSans.ttf \
|
|
||||||
/usr/share/fonts/TTF/Jigmo.ttf \
|
|
||||||
/usr/share/fonts/TTF/Jigmo2.ttf &
|
|
||||||
```
|
|
||||||
|
|
||||||
These fonts are test fixtures, not runtime requirements. Any suitable
|
Third-party notices are in [`LICENSES`](LICENSES). The repository declares
|
||||||
scalable outline files may be supplied; their argument order is the glyph
|
no license for the strans source as a whole.
|
||||||
fallback order. A collection file uses its first face. If any named file is
|
|
||||||
absent or invalid, the popup disables itself cleanly while input processing
|
|
||||||
continues. `run.sh` and `bench.sh` accept the same font-file arguments.
|
|
||||||
When `run.sh` is invoked without arguments, it uses the installed DejaVu,
|
|
||||||
Jigmo, or Noto CJK font files from the standard Arch Linux paths. Explicit
|
|
||||||
font-file arguments replace these defaults:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./run.sh
|
|
||||||
./run.sh /path/to/primary.ttf /path/to/fallback.ttc
|
|
||||||
```
|
|
||||||
|
|
||||||
`run.sh` remains in the foreground and owns only the daemon and XIM process
|
|
||||||
that it starts. It waits for that daemon's IPC socket and IBus address file
|
|
||||||
before reporting success. With `DISPLAY` set it then starts XIM; without a
|
|
||||||
display it keeps the headless daemon in the foreground and does not start
|
|
||||||
XIM. A signal or XIM exit stops and reaps the launcher's daemon. On repeated
|
|
||||||
invocation, it stops a previous live `run.sh` only after confirming that the
|
|
||||||
old launcher directly owns the daemon serving the same IPC and IBus
|
|
||||||
endpoints and that the daemon carries that launcher's PID and process-start
|
|
||||||
identity. A manually started or orphaned daemon is left untouched and
|
|
||||||
reported as a visible startup failure.
|
|
||||||
|
|
||||||
The IPC socket is placed at `$XDG_RUNTIME_DIR/strans.sock` when
|
|
||||||
`XDG_RUNTIME_DIR` is an absolute path. Otherwise strans uses the per-user
|
|
||||||
fallback `/tmp/strans.<uid>`. A second daemon will not replace a live
|
|
||||||
daemon's socket.
|
|
||||||
|
|
||||||
For XIM applications:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./xim/strans-xim &
|
|
||||||
export XMODIFIERS=@im=strans
|
|
||||||
xterm
|
|
||||||
```
|
|
||||||
|
|
||||||
For GTK 3 applications, install the module built by Docker. Installation
|
|
||||||
needs the GTK 3 runtime, but not its development package:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
doas make -C gtk install
|
|
||||||
```
|
|
||||||
|
|
||||||
`LIBDIR` defaults to `/usr/lib` and may be overridden for another system.
|
|
||||||
Normal `DESTDIR` semantics are supported, and a staged install does not update
|
|
||||||
the host GTK module cache:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
make -C gtk install DESTDIR="$pkgdir"
|
|
||||||
```
|
|
||||||
|
|
||||||
For IBus clients such as kitty:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
GLFW_IM_MODULE=ibus kitty
|
|
||||||
```
|
|
||||||
|
|
||||||
strans provides its own IBus endpoint; neither ibus-daemon nor fcitx is
|
|
||||||
required. Starting without `DISPLAY` is supported: input processing and IBus
|
|
||||||
continue to run while the optional X popup is disabled.
|
|
||||||
|
|
||||||
## Ownership and rendering constraints
|
|
||||||
|
|
||||||
There is one global composition, not one independent engine per frontend
|
|
||||||
context. The first meaningful key from a new context resets the previous
|
|
||||||
composition and transfers ownership. Reset, focus loss, context destruction,
|
|
||||||
and disconnect affect the engine only when they come from its active owner;
|
|
||||||
stale events cannot clear a newer owner's preedit.
|
|
||||||
|
|
||||||
The popup is an optional X window. It uses the explicit font-file order and
|
|
||||||
fixed-width rune cells; U+FE0E and U+FE0F variation selectors use zero cells.
|
|
||||||
It does not perform OpenType shaping, color-emoji rendering, or general
|
|
||||||
grapheme clustering. Multi-rune emoji are committed intact but are drawn as
|
|
||||||
their constituent rune cells under this boundary. A supplied caret rectangle
|
|
||||||
is used when available, with the pointer as fallback. The X renderer accepts
|
|
||||||
only a 24-depth, 32-bpp TrueColor root format and disables itself cleanly on
|
|
||||||
other visuals.
|
|
||||||
|
|
||||||
## Data, third-party code, and licensing
|
|
||||||
|
|
||||||
Data and third-party provenance is recorded in
|
|
||||||
[`docs/PROVENANCE.md`](docs/PROVENANCE.md), with font-specific details in
|
|
||||||
[`font/PROVENANCE`](font/PROVENANCE). No font binaries are bundled; that file
|
|
||||||
retains the provenance of the faces formerly kept in the repository. The
|
|
||||||
historical SKK-derived Kanji
|
|
||||||
dictionary is distributed under GPL version 2 or later; its license text is
|
|
||||||
in [`LICENSES/GPL-2.0-or-later.txt`](LICENSES/GPL-2.0-or-later.txt).
|
|
||||||
The single-character Hanja data derived from libhangul is distributed under
|
|
||||||
the BSD 3-Clause license in
|
|
||||||
[`LICENSES/BSD-3-Clause-libhangul-hanja.txt`](LICENSES/BSD-3-Clause-libhangul-hanja.txt).
|
|
||||||
|
|
||||||
The repository does not currently declare a license for the strans project
|
|
||||||
source as a whole. The licenses of bundled data, historical font assets, and
|
|
||||||
third-party components do not by themselves license the original strans
|
|
||||||
source.
|
|
||||||
|
|||||||
71
bench.sh
71
bench.sh
@@ -4,18 +4,13 @@ set -eu
|
|||||||
|
|
||||||
cd "$(dirname "$0")" || exit 1
|
cd "$(dirname "$0")" || exit 1
|
||||||
|
|
||||||
case ${XDG_RUNTIME_DIR-} in
|
if test "$#" -ne 0; then
|
||||||
/) ipc=/strans.sock ;;
|
echo "usage: bench.sh" >&2
|
||||||
/*/) ipc=${XDG_RUNTIME_DIR}strans.sock ;;
|
exit 1
|
||||||
/*) ipc=${XDG_RUNTIME_DIR}/strans.sock ;;
|
fi
|
||||||
*) ipc=/tmp/strans.$(id -u) ;;
|
|
||||||
esac
|
if ! test -x ./bench/bench; then
|
||||||
if test -n "${XDG_CONFIG_HOME-}"; then
|
echo "bench.sh: ./bench/bench is not executable; run make bench first" >&2
|
||||||
busdir=$XDG_CONFIG_HOME/ibus/bus
|
|
||||||
elif test "${HOME+x}" = x; then
|
|
||||||
busdir=$HOME/.config/ibus/bus
|
|
||||||
else
|
|
||||||
echo "bench.sh: HOME or XDG_CONFIG_HOME is required for IBus discovery" >&2
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -29,70 +24,36 @@ cleanup()
|
|||||||
if test -n "$perf_pid"; then
|
if test -n "$perf_pid"; then
|
||||||
kill -INT "$perf_pid" 2>/dev/null || :
|
kill -INT "$perf_pid" 2>/dev/null || :
|
||||||
wait "$perf_pid" 2>/dev/null || :
|
wait "$perf_pid" 2>/dev/null || :
|
||||||
perf_pid=
|
|
||||||
fi
|
fi
|
||||||
if test -n "$strans_pid"; then
|
if test -n "$strans_pid"; then
|
||||||
kill "$strans_pid" 2>/dev/null || :
|
kill "$strans_pid" 2>/dev/null || :
|
||||||
wait "$strans_pid" 2>/dev/null || :
|
wait "$strans_pid" 2>/dev/null || :
|
||||||
strans_pid=
|
|
||||||
fi
|
fi
|
||||||
exit "$status"
|
exit "$status"
|
||||||
}
|
}
|
||||||
|
|
||||||
ibusready()
|
|
||||||
{
|
|
||||||
for address in "$busdir"/*; do
|
|
||||||
test -f "$address" || continue
|
|
||||||
foundpid=
|
|
||||||
foundaddress=
|
|
||||||
while IFS= read -r line; do
|
|
||||||
case $line in
|
|
||||||
IBUS_DAEMON_PID="$strans_pid") foundpid=1 ;;
|
|
||||||
IBUS_ADDRESS=unix:abstract=strans-"$strans_pid",*) foundaddress=1 ;;
|
|
||||||
esac
|
|
||||||
done <"$address"
|
|
||||||
if test -n "$foundpid" && test -n "$foundaddress"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup 0
|
trap cleanup 0
|
||||||
trap 'exit 129' 1
|
trap 'exit 129' 1
|
||||||
trap 'exit 130' 2
|
trap 'exit 130' 2
|
||||||
trap 'exit 143' 15
|
trap 'exit 143' 15
|
||||||
|
|
||||||
./strans map "$@" &
|
./strans map &
|
||||||
strans_pid=$!
|
strans_pid=$!
|
||||||
|
|
||||||
|
# The daemon is ready once the benchmark client can complete a round.
|
||||||
attempt=0
|
attempt=0
|
||||||
while test "$attempt" -lt 10; do
|
until ./bench/bench bench/bench.keys 1 >/dev/null 2>&1; do
|
||||||
if test -S "$ipc" && ibusready; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
if ! kill -0 "$strans_pid" 2>/dev/null; then
|
if ! kill -0 "$strans_pid" 2>/dev/null; then
|
||||||
if wait "$strans_pid"; then
|
echo "bench.sh: daemon exited before accepting IPC clients" >&2
|
||||||
status=0
|
exit 1
|
||||||
else
|
|
||||||
status=$?
|
|
||||||
fi
|
|
||||||
strans_pid=
|
|
||||||
if test "$status" -eq 0; then
|
|
||||||
status=1
|
|
||||||
fi
|
|
||||||
echo "bench.sh: daemon exited before publishing IPC and IBus endpoints" >&2
|
|
||||||
exit "$status"
|
|
||||||
fi
|
fi
|
||||||
attempt=$((attempt + 1))
|
attempt=$((attempt + 1))
|
||||||
if test "$attempt" -lt 10; then
|
if test "$attempt" -ge 50; then
|
||||||
sleep 1
|
echo "bench.sh: daemon did not accept IPC clients within 10 seconds" >&2
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
sleep 0.2
|
||||||
done
|
done
|
||||||
if test "$attempt" -eq 10; then
|
|
||||||
echo "bench.sh: daemon did not publish IPC and IBus endpoints within 10 seconds" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# warm up the renderer
|
# warm up the renderer
|
||||||
./bench/bench bench/bench.keys 100
|
./bench/bench bench/bench.keys 100
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
CC = cc
|
CC = cc
|
||||||
CFLAGS = -Wall -O2
|
CFLAGS ?= -O2
|
||||||
|
|
||||||
bench: main.c ../ipc.c ../ipc.h
|
bench: main.c ../ipc.c ../ipc.h
|
||||||
$(CC) $(CFLAGS) -I.. -o $@ main.c ../ipc.c
|
$(CC) $(CPPFLAGS) -I.. -Wall $(CFLAGS) $(LDFLAGS) -o $@ \
|
||||||
|
main.c ../ipc.c $(LDLIBS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f bench
|
rm -f bench
|
||||||
|
|||||||
10
bench/main.c
10
bench/main.c
@@ -7,7 +7,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include "../ipc.h"
|
#include "ipc.h"
|
||||||
|
|
||||||
typedef struct Key Key;
|
typedef struct Key Key;
|
||||||
struct Key {
|
struct Key {
|
||||||
@@ -99,7 +99,7 @@ iterations(char *s)
|
|||||||
goto Bad;
|
goto Bad;
|
||||||
errno = 0;
|
errno = 0;
|
||||||
n = strtoumax(s, &end, 10);
|
n = strtoumax(s, &end, 10);
|
||||||
if(errno != 0 || *end != '\0' || n == 0 || n > UINT64_MAX)
|
if(errno != 0 || *end != '\0' || n == 0)
|
||||||
goto Bad;
|
goto Bad;
|
||||||
return n;
|
return n;
|
||||||
Bad:
|
Bad:
|
||||||
@@ -128,10 +128,10 @@ sendkey(int key, int mod)
|
|||||||
static int
|
static int
|
||||||
readresp(void)
|
readresp(void)
|
||||||
{
|
{
|
||||||
char buf[Ipcfieldmax+1];
|
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
|
|
||||||
return ipcreadresp(fd, 0, buf, sizeof buf, NULL, 0, &resp);
|
return ipcreadresp(fd, 0, commit, preedit, &resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static double
|
static double
|
||||||
@@ -157,7 +157,7 @@ main(int argc, char **argv)
|
|||||||
|
|
||||||
loadkeys(argv[1]);
|
loadkeys(argv[1]);
|
||||||
niter = argc == 3 ? iterations(argv[2]) : 1000;
|
niter = argc == 3 ? iterations(argv[2]) : 1000;
|
||||||
if(nkeys > UINT64_MAX || niter > UINT64_MAX / nkeys){
|
if(niter > UINT64_MAX / nkeys){
|
||||||
fprintf(stderr, "iteration count overflows key total\n");
|
fprintf(stderr, "iteration count overflows key total\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
82
compose.c
Normal file
82
compose.c
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
|
|
||||||
|
#include <locale.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
#include <xkbcommon/xkbcommon-compose.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One state per frontend, all made before the procs are: xkbcommon locks
|
||||||
|
* nothing, and even its table refcount is a plain increment. cowner is
|
||||||
|
* the context whose sequence a state holds.
|
||||||
|
*/
|
||||||
|
static struct xkb_compose_state *cstate[Ncompose];
|
||||||
|
static void *cowner[Ncompose];
|
||||||
|
|
||||||
|
/* Dead keys and Compose: no frontend's client does them for us. */
|
||||||
|
void
|
||||||
|
composeinit(void)
|
||||||
|
{
|
||||||
|
struct xkb_context *ctx;
|
||||||
|
struct xkb_compose_table *table;
|
||||||
|
char *locale;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
locale = setlocale(LC_CTYPE, "");
|
||||||
|
ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||||
|
if(ctx == nil)
|
||||||
|
return;
|
||||||
|
table = xkb_compose_table_new_from_locale(ctx,
|
||||||
|
locale != nil ? locale : "C", XKB_COMPOSE_COMPILE_NO_FLAGS);
|
||||||
|
if(table != nil)
|
||||||
|
for(i = 0; i < Ncompose; i++)
|
||||||
|
cstate[i] = xkb_compose_state_new(table,
|
||||||
|
XKB_COMPOSE_STATE_NO_FLAGS);
|
||||||
|
xkb_compose_table_unref(table);
|
||||||
|
xkb_context_unref(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The context is going, and the sequence it left half typed with it. */
|
||||||
|
void
|
||||||
|
composedrop(int who, void *owner)
|
||||||
|
{
|
||||||
|
if(cstate[who] == nil || owner != cowner[who])
|
||||||
|
return;
|
||||||
|
xkb_compose_state_reset(cstate[who]);
|
||||||
|
cowner[who] = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Feeds sym to the Compose table. Returns 1 while a sequence is
|
||||||
|
* unfinished: the key belongs to the sequence, not to the engine. A
|
||||||
|
* finished sequence leaves its text in buf, which the caller commits
|
||||||
|
* after whatever the engine had pending. A sequence belongs to the
|
||||||
|
* context that started it: a key from another one starts over.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
composekey(int who, void *owner, u32int sym, char *buf, int n)
|
||||||
|
{
|
||||||
|
struct xkb_compose_state *cs;
|
||||||
|
|
||||||
|
buf[0] = '\0';
|
||||||
|
cs = cstate[who];
|
||||||
|
if(cs == nil)
|
||||||
|
return 0;
|
||||||
|
if(owner != cowner[who]){
|
||||||
|
xkb_compose_state_reset(cs);
|
||||||
|
cowner[who] = owner;
|
||||||
|
}
|
||||||
|
if(xkb_compose_state_feed(cs, sym) != XKB_COMPOSE_FEED_ACCEPTED)
|
||||||
|
return 0;
|
||||||
|
switch(xkb_compose_state_get_status(cs)){
|
||||||
|
case XKB_COMPOSE_COMPOSING:
|
||||||
|
case XKB_COMPOSE_CANCELLED:
|
||||||
|
return 1;
|
||||||
|
case XKB_COMPOSE_COMPOSED:
|
||||||
|
xkb_compose_state_get_utf8(cs, buf, n);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
145
dat.h
145
dat.h
@@ -7,6 +7,14 @@
|
|||||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* plan9port runs the note handlers for a note it goes on to ignore, and
|
||||||
|
* the process lives through it, so what strans announced must outlive
|
||||||
|
* such a note. A daemon that forks nothing gets only the one.
|
||||||
|
*/
|
||||||
|
#define notefatal(note) (strcmp(note, "sys: write on closed pipe") != 0)
|
||||||
|
|
||||||
|
/* A language id is the control code of the Ctrl key that selects it. */
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
LangEN = 0x14,
|
LangEN = 0x14,
|
||||||
@@ -17,8 +25,6 @@ enum
|
|||||||
LangEMOJI = 0x05,
|
LangEMOJI = 0x05,
|
||||||
LangVI = 0x16,
|
LangVI = 0x16,
|
||||||
|
|
||||||
Fontsz = 32,
|
|
||||||
Maxfonts = 4,
|
|
||||||
Maxrunes = 64,
|
Maxrunes = 64,
|
||||||
Maxutf = Maxrunes * UTFmax + 1,
|
Maxutf = Maxrunes * UTFmax + 1,
|
||||||
};
|
};
|
||||||
@@ -26,16 +32,41 @@ enum
|
|||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Maxclients = 64,
|
Maxclients = 64,
|
||||||
Maxkouho = 32,
|
Ownerpoll = 200, /* ms between a frontend's owner checks while its preedit shows */
|
||||||
|
Maxkouho = 128,
|
||||||
Maxdisp = 9,
|
Maxdisp = 9,
|
||||||
Imgw = (Maxrunes + 3) * Fontsz,
|
|
||||||
Imgh = (Maxdisp + 1) * Fontsz,
|
/* Content purposes whose keys the engine never sees; IBus and
|
||||||
|
* text-input-v3 number them alike, both after GTK. */
|
||||||
|
Purposepassword = 8,
|
||||||
|
Purposepin = 9,
|
||||||
|
|
||||||
Colfg = 0x000000,
|
Colfg = 0x000000,
|
||||||
Colbg = 0xffffff,
|
Colbg = 0xffffff,
|
||||||
Colsel = 0xcccccc,
|
Colsep = 0xd0d0d0,
|
||||||
|
Colsel = 0x333333,
|
||||||
|
Colselfg = 0xffffff,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Each frontend composes on its own: they run in different procs. */
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Composeibus,
|
||||||
|
Composexim,
|
||||||
|
Composewl,
|
||||||
|
Ncompose,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Popup metrics in pixels; popupscale is GDK_SCALE, for HiDPI. */
|
||||||
|
extern int popupscale;
|
||||||
|
#define Fontsz (32*popupscale)
|
||||||
|
#define PopupPad (4*popupscale)
|
||||||
|
#define PopupSep popupscale
|
||||||
|
#define PopupBorder popupscale
|
||||||
|
#define PopupNumw Fontsz
|
||||||
|
#define PopupTextw (12*Fontsz)
|
||||||
|
#define PopupBasew (2*PopupPad + PopupNumw + PopupTextw)
|
||||||
|
|
||||||
typedef struct Str Str;
|
typedef struct Str Str;
|
||||||
struct Str
|
struct Str
|
||||||
{
|
{
|
||||||
@@ -43,53 +74,46 @@ struct Str
|
|||||||
int n;
|
int n;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* What one key does to a composition: text to commit, the new pending
|
||||||
|
* text, and the new raw state (Telex key history, or pending Japanese
|
||||||
|
* romaji), given the old ones in Im.
|
||||||
|
*/
|
||||||
typedef struct Emit Emit;
|
typedef struct Emit Emit;
|
||||||
struct Emit
|
struct Emit
|
||||||
{
|
{
|
||||||
int eat;
|
int eat;
|
||||||
Str s;
|
Str s;
|
||||||
Str next;
|
Str next;
|
||||||
Str dict;
|
Str raw;
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct Hnode Hnode;
|
|
||||||
struct Hnode
|
|
||||||
{
|
|
||||||
int filled;
|
|
||||||
int next;
|
|
||||||
char *key;
|
|
||||||
int klen;
|
|
||||||
char *val;
|
|
||||||
int vlen;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Tnode Tnode;
|
typedef struct Tnode Tnode;
|
||||||
struct Tnode
|
struct Tnode
|
||||||
{
|
{
|
||||||
int child;
|
|
||||||
int sibling;
|
|
||||||
char c;
|
|
||||||
char *val;
|
char *val;
|
||||||
int vlen;
|
int vlen;
|
||||||
|
int child;
|
||||||
|
int sibling;
|
||||||
|
Rune c;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Trie Trie;
|
typedef struct Trie Trie;
|
||||||
struct Trie
|
struct Trie
|
||||||
{
|
{
|
||||||
int root;
|
|
||||||
Tnode *nodes;
|
Tnode *nodes;
|
||||||
int n;
|
int n;
|
||||||
int cap;
|
int cap;
|
||||||
|
/* the last key put and its path, so a sorted file walks it once */
|
||||||
|
Str last;
|
||||||
|
int path[Maxrunes];
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Hmap Hmap;
|
enum
|
||||||
struct Hmap
|
|
||||||
{
|
{
|
||||||
int nbs;
|
TrieMiss,
|
||||||
int nsz;
|
TriePrefix,
|
||||||
int len;
|
TrieExact,
|
||||||
int cap;
|
|
||||||
uchar *nodes;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Lang Lang;
|
typedef struct Lang Lang;
|
||||||
@@ -101,9 +125,8 @@ struct Lang
|
|||||||
char *dictname;
|
char *dictname;
|
||||||
Emit (*trans)(Im*, Rune);
|
Emit (*trans)(Im*, Rune);
|
||||||
void (*back)(Im*);
|
void (*back)(Im*);
|
||||||
void (*dictq)(Im*);
|
|
||||||
Trie *map;
|
Trie *map;
|
||||||
Hmap *dict;
|
Trie *dict;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Im
|
struct Im
|
||||||
@@ -119,6 +142,15 @@ struct Im
|
|||||||
|
|
||||||
typedef struct Drawcmd Drawcmd;
|
typedef struct Drawcmd Drawcmd;
|
||||||
typedef struct Caret Caret;
|
typedef struct Caret Caret;
|
||||||
|
typedef struct Area Area;
|
||||||
|
struct Area
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
int w;
|
||||||
|
int h;
|
||||||
|
};
|
||||||
|
|
||||||
struct Caret
|
struct Caret
|
||||||
{
|
{
|
||||||
int valid;
|
int valid;
|
||||||
@@ -133,9 +165,35 @@ struct Drawcmd
|
|||||||
Str kouho[Maxdisp];
|
Str kouho[Maxdisp];
|
||||||
int nkouho;
|
int nkouho;
|
||||||
int sel;
|
int sel;
|
||||||
|
int first;
|
||||||
|
int total;
|
||||||
Caret caret;
|
Caret caret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Popup geometry from popuplayout: n rows from row0 of the candidates,
|
||||||
|
* each Fontsz tall; a section's y is -1 when it is absent. Numbers
|
||||||
|
* start at PopupPad, text at PopupPad+PopupNumw.
|
||||||
|
*/
|
||||||
|
typedef struct Popup Popup;
|
||||||
|
struct Popup
|
||||||
|
{
|
||||||
|
int w;
|
||||||
|
int h;
|
||||||
|
int n;
|
||||||
|
int row0;
|
||||||
|
int prey;
|
||||||
|
int sepy;
|
||||||
|
int rowsy;
|
||||||
|
int textw;
|
||||||
|
int marky;
|
||||||
|
int markx;
|
||||||
|
int markw;
|
||||||
|
Str mark;
|
||||||
|
int sely;
|
||||||
|
int selw;
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct Keyreq Keyreq;
|
typedef struct Keyreq Keyreq;
|
||||||
typedef struct Keyres Keyres;
|
typedef struct Keyres Keyres;
|
||||||
enum
|
enum
|
||||||
@@ -144,10 +202,12 @@ enum
|
|||||||
Keyreset,
|
Keyreset,
|
||||||
Keyrelease,
|
Keyrelease,
|
||||||
Keycaret,
|
Keycaret,
|
||||||
|
Keycap,
|
||||||
};
|
};
|
||||||
struct Keyres
|
struct Keyres
|
||||||
{
|
{
|
||||||
int eaten;
|
int eaten; /* Keycap: the caller is the owner and clientpre took. */
|
||||||
|
int del; /* runes of the client's own text to take back first */
|
||||||
Str commit;
|
Str commit;
|
||||||
Str preedit;
|
Str preedit;
|
||||||
};
|
};
|
||||||
@@ -155,33 +215,16 @@ 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 clientpre; /* the client draws the preedit; the popup does not */
|
||||||
int op;
|
int op;
|
||||||
u32int ks;
|
u32int ks;
|
||||||
u32int mod;
|
u32int mod;
|
||||||
Caret caret;
|
Caret caret;
|
||||||
|
Str surround; /* the client's own text just before the cursor */
|
||||||
Channel *reply;
|
Channel *reply;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Dictreq Dictreq;
|
|
||||||
struct Dictreq
|
|
||||||
{
|
|
||||||
Str key;
|
|
||||||
Str pre;
|
|
||||||
int lang;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct Dictres Dictres;
|
|
||||||
struct Dictres
|
|
||||||
{
|
|
||||||
Str key;
|
|
||||||
Str kouho[Maxkouho];
|
|
||||||
int nkouho;
|
|
||||||
int lang;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Lang langs[];
|
extern Lang langs[];
|
||||||
extern int nlang;
|
extern int nlang;
|
||||||
extern Channel *drawc;
|
extern Channel *drawc;
|
||||||
extern Channel *keyc;
|
extern Channel *keyc;
|
||||||
extern Channel *dictreqc;
|
|
||||||
extern Channel *dictresc;
|
|
||||||
|
|||||||
156
dict.c
156
dict.c
@@ -1,122 +1,90 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
void
|
/* Appends the space-separated words of a node's entry to out[], up to max. */
|
||||||
dictlookup(Dictreq *req, Dictres *res)
|
static int
|
||||||
|
words(Tnode *nd, Str *out, int n, int max)
|
||||||
{
|
{
|
||||||
Lang *l;
|
|
||||||
Hmap *dict;
|
|
||||||
Hnode *n;
|
|
||||||
char *p, *e, *sp;
|
char *p, *e, *sp;
|
||||||
Str tmp;
|
Str tmp;
|
||||||
|
|
||||||
res->key = req->pre;
|
if(nd->val == nil)
|
||||||
res->nkouho = 0;
|
return n;
|
||||||
res->lang = req->lang;
|
p = nd->val;
|
||||||
if(req->key.n == 0)
|
e = p + nd->vlen;
|
||||||
return;
|
while(n < max && p < e){
|
||||||
l = getlang(req->lang);
|
|
||||||
dict = l ? l->dict : nil;
|
|
||||||
if(dict == nil)
|
|
||||||
return;
|
|
||||||
n = hmapget(dict, &req->key);
|
|
||||||
if(n == nil || n->vlen == 0)
|
|
||||||
return;
|
|
||||||
p = n->val;
|
|
||||||
e = p + n->vlen;
|
|
||||||
while(res->nkouho < Maxkouho && p < e){
|
|
||||||
while(p < e && *p == ' ')
|
while(p < e && *p == ' ')
|
||||||
p++;
|
p++;
|
||||||
if(p == e)
|
|
||||||
break;
|
|
||||||
sp = p;
|
sp = p;
|
||||||
while(p < e && *p != ' ')
|
while(p < e && *p != ' ')
|
||||||
p++;
|
p++;
|
||||||
sinit(&tmp, sp, p - sp);
|
if(sinit(&tmp, sp, p - sp) && tmp.n > 0)
|
||||||
if(req->lang == LangEMOJI || scmp(&tmp, &req->key) != 0)
|
out[n++] = tmp;
|
||||||
res->kouho[res->nkouho++] = tmp;
|
|
||||||
if(p < e)
|
|
||||||
p++;
|
|
||||||
}
|
}
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
/* The entries at and below a node, the shortest keys first. */
|
||||||
dictthread(void*)
|
static int
|
||||||
|
below(Trie *t, int ni, Str *out, int n, int max)
|
||||||
{
|
{
|
||||||
Dictreq req;
|
Tnode *nd;
|
||||||
static Dictres res;
|
int ci;
|
||||||
|
|
||||||
threadsetname("dict");
|
nd = &t->nodes[ni];
|
||||||
for(;;){
|
n = words(nd, out, n, max);
|
||||||
if(chanrecv(dictreqc, &req) < 0)
|
for(ci = nd->child; ci >= 0 && n < max; ci = t->nodes[ci].sibling)
|
||||||
break;
|
n = below(t, ci, out, n, max);
|
||||||
while(channbrecv(dictreqc, &req) > 0)
|
return n;
|
||||||
;
|
|
||||||
dictlookup(&req, &res);
|
|
||||||
chansend(dictresc, &res);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Hmap*
|
/* Fills out[] with up to max candidates for key: the words of its entry. */
|
||||||
dictopen(char *path)
|
int
|
||||||
|
dictlookup(Trie *t, Str *key, Str *out, int max)
|
||||||
{
|
{
|
||||||
Hmap *h;
|
int ni;
|
||||||
Biobuf *b;
|
|
||||||
Str key;
|
|
||||||
char *line, *tab, *p, *e;
|
|
||||||
int len, lineno;
|
|
||||||
|
|
||||||
b = Bopen(path, OREAD);
|
ni = key->n == 0 ? -1 : trienode(t, key);
|
||||||
if(b == nil)
|
if(ni < 0)
|
||||||
die("can't open: %s", path);
|
return 0;
|
||||||
h = hmapalloc(4096);
|
return words(&t->nodes[ni], out, 0, max);
|
||||||
lineno = 0;
|
|
||||||
while((line = Brdstr(b, '\n', 1)) != nil){
|
|
||||||
lineno++;
|
|
||||||
len = strlen(line);
|
|
||||||
if(len > 0 && line[len-1] == '\r')
|
|
||||||
line[--len] = '\0';
|
|
||||||
if(len == 0 || line[0] == ';'){
|
|
||||||
free(line);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
tab = strchr(line, '\t');
|
|
||||||
if(tab == nil || tab == line || tab >= line + len - 1 ||
|
|
||||||
strchr(tab+1, '\t') != nil)
|
|
||||||
die("malformed dictionary: %s:%d", path, lineno);
|
|
||||||
*tab = '\0';
|
|
||||||
if(utflen(line) > Maxrunes)
|
|
||||||
die("dictionary key too long: %s:%d", path, lineno);
|
|
||||||
for(p = tab+1; p < line+len; p = e+1){
|
|
||||||
e = memchr(p, ' ', line+len-p);
|
|
||||||
if(e == nil)
|
|
||||||
e = line+len;
|
|
||||||
if(utfnlen(p, e-p) > Maxrunes)
|
|
||||||
die("dictionary candidate too long: %s:%d", path, lineno);
|
|
||||||
if(e == line+len)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
sinit(&key, line, tab - line);
|
|
||||||
hmapset(&h, &key, tab+1, len - (tab - line) - 1);
|
|
||||||
free(line);
|
|
||||||
}
|
|
||||||
Bterm(b);
|
|
||||||
return h;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
/* As dictlookup, for every entry key is a prefix of, key's own first. */
|
||||||
dictinit(char *dir)
|
int
|
||||||
|
dictprefix(Trie *t, Str *key, Str *out, int max)
|
||||||
|
{
|
||||||
|
int ni;
|
||||||
|
|
||||||
|
ni = key->n == 0 ? -1 : trienode(t, key);
|
||||||
|
if(ni < 0)
|
||||||
|
return 0;
|
||||||
|
return below(t, ni, out, 0, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Trie*
|
||||||
|
langopen(char *dir, char *name, char *ext)
|
||||||
{
|
{
|
||||||
char *path;
|
char *path;
|
||||||
int i;
|
Trie *t;
|
||||||
|
|
||||||
for(i = 0; i < nlang; i++){
|
path = smprint("%s/%s.%s", dir, name, ext);
|
||||||
if(langs[i].dictname == nil)
|
if(path == nil)
|
||||||
continue;
|
die("out of memory");
|
||||||
path = smprint("%s/%s.dict", dir, langs[i].dictname);
|
t = trieopen(path);
|
||||||
if(path == nil)
|
free(path);
|
||||||
die("out of memory");
|
return t;
|
||||||
langs[i].dict = dictopen(path);
|
}
|
||||||
free(path);
|
|
||||||
|
void
|
||||||
|
langinit(char *dir)
|
||||||
|
{
|
||||||
|
Lang *l;
|
||||||
|
|
||||||
|
for(l = langs; l < langs + nlang; l++){
|
||||||
|
if(l->mapname != nil)
|
||||||
|
l->map = langopen(dir, l->mapname, "map");
|
||||||
|
if(l->dictname != nil)
|
||||||
|
l->dict = langopen(dir, l->dictname, "dict");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
# Data and third-party provenance
|
|
||||||
|
|
||||||
This file records what can be established from repository contents and
|
|
||||||
history. It does not assign a license to original strans source code.
|
|
||||||
|
|
||||||
## Input maps and dictionaries
|
|
||||||
|
|
||||||
`map/hira.map`, `map/kata.map`, and the original map data entered the history
|
|
||||||
in commit `cc157c0b3c33506d1cbf5bd251ddc6a9ca909081`, whose subject says the
|
|
||||||
maps came from 9front. That commit does not identify a 9front revision or
|
|
||||||
carry a separate data license. `map/telex.map` is generated by
|
|
||||||
`map/mktelex.py`; Vietnamese behavior is retained for compatibility.
|
|
||||||
|
|
||||||
`map/emoji.dict` is generated deterministically from the repository-maintained
|
|
||||||
`map/emoji.src` by `map/mkemoji`. No external dataset is required.
|
|
||||||
|
|
||||||
`map/hanja.src` is a single-character subset of libhangul's
|
|
||||||
`data/hanja/hanja.txt` from release tag `libhangul-0.2.0`. Its immutable
|
|
||||||
identifiers are:
|
|
||||||
|
|
||||||
- annotated tag object: `20afc38922e3595ee3ed5b186f2ea05afe663763`
|
|
||||||
- peeled commit: `41c702f5d3581325b646ef6249f1f641b0427ae0`
|
|
||||||
- input blob: `199cfd70c4b306257ac2e018714a1285f7cf0ed3`
|
|
||||||
- input SHA-256: `dd44dcc856cf542b1022d0f39c2e9b9f8805fdcc5923be80f04849ed97ce0996`
|
|
||||||
|
|
||||||
`map/libhangul2hanja` keeps only a one-syllable modern Hangul reading paired
|
|
||||||
with one BMP Hanja rune. It writes one
|
|
||||||
`Hanja<TAB>Hangul syllable` pair per row in upstream order; word entries are
|
|
||||||
not part of this data. `map/mkhanja` groups those rows by reading for the
|
|
||||||
runtime dictionary without changing candidate order, retaining the first 32
|
|
||||||
candidates supported by the engine. Exact import and regeneration commands
|
|
||||||
are in `map/README`.
|
|
||||||
|
|
||||||
The upstream data header is BSD 3-Clause licensed, copyright 2005, 2006 Choe
|
|
||||||
Hwanjin. Its complete license text is in
|
|
||||||
`LICENSES/BSD-3-Clause-libhangul-hanja.txt`.
|
|
||||||
|
|
||||||
`map/kanji.dict` identifies itself as SKK Medium dictionary version 8.1,
|
|
||||||
May 24, 1995, rearranged for Plan 9 ktrans by Kenji Okamoto on February 17,
|
|
||||||
2000. Its header names its copyright holders and grants GPL version 2 or
|
|
||||||
later. The complete GPL version 2 text is in
|
|
||||||
`LICENSES/GPL-2.0-or-later.txt`; the dictionary header supplies the
|
|
||||||
"or later" option. `map/README` records the exact repository history,
|
|
||||||
repairs made to this copy, and the reproducible procedure for importing a
|
|
||||||
new SKK revision.
|
|
||||||
|
|
||||||
## Fonts
|
|
||||||
|
|
||||||
No font binaries are bundled. The daemon receives one to four font file paths
|
|
||||||
on its command line and tries them in that order; it does not discover fonts by
|
|
||||||
family name or directory contents. `font/PROVENANCE` retains the exact
|
|
||||||
checksums, source revisions, and license records for the three Noto faces that
|
|
||||||
were formerly stored in the repository.
|
|
||||||
|
|
||||||
The Docker renderer tests obtain their fonts from signed Arch packages rather
|
|
||||||
than the source checkout. On August 13, 2026 the tested package contract was:
|
|
||||||
|
|
||||||
- `ttf-dejavu` `2.37+18+g9b5d1b2f-8`, providing
|
|
||||||
`/usr/share/fonts/TTF/DejaVuSans.ttf` (SHA-256
|
|
||||||
`6038a160b491e121c1f12c7bccb4a9c8730296e3adc1086a059404ed84b7451c`);
|
|
||||||
- `ttf-jigmo` `20250912-1`, providing
|
|
||||||
`/usr/share/fonts/TTF/Jigmo.ttf` (SHA-256
|
|
||||||
`c8f295b9bd8f9f117a76b3a454aaaa1bb5b4babc18e254978c6deb88464e40cf`)
|
|
||||||
and `/usr/share/fonts/TTF/Jigmo2.ttf` (SHA-256
|
|
||||||
`5da3582efe77e22073b86b3b86b556d7111148a76b957cfb53318a91da2efff0`).
|
|
||||||
|
|
||||||
Pacman validates the package checksums and repository signatures. DejaVu Sans
|
|
||||||
supplies ASCII and the monochrome U+1F600 outline in the tests; Jigmo supplies
|
|
||||||
Hangul and BMP CJK, and Jigmo2 supplies U+2000B. Their installed license files
|
|
||||||
are `/usr/share/licenses/ttf-dejavu/LICENSE` and
|
|
||||||
`/usr/share/licenses/ttf-jigmo/LICENSE.txt`; the packages identify their terms
|
|
||||||
as the DejaVu custom license and CC0 1.0, respectively. These packages are
|
|
||||||
test fixtures, not names or paths hard-coded into the daemon.
|
|
||||||
|
|
||||||
## Git submodules
|
|
||||||
|
|
||||||
`cutest` is pinned by the repository as the unit-test framework. XIM uses
|
|
||||||
the system xcb-imdkit and xcb-util libraries at build time; those system
|
|
||||||
libraries are not bundled by this repository.
|
|
||||||
43
fn.h
43
fn.h
@@ -1,6 +1,7 @@
|
|||||||
void die(char*, ...);
|
void die(char*, ...);
|
||||||
|
|
||||||
void sinit(Str*, char*, int);
|
int sinit(Str*, char*, int);
|
||||||
|
void stail(Str*, char*, int);
|
||||||
void sclear(Str*);
|
void sclear(Str*);
|
||||||
void sputr(Str*, Rune);
|
void sputr(Str*, Rune);
|
||||||
void spopr(Str*);
|
void spopr(Str*);
|
||||||
@@ -9,28 +10,30 @@ int scmp(Str*, Str*);
|
|||||||
int stoutf(Str*, char*, int);
|
int stoutf(Str*, char*, int);
|
||||||
Rune slastr(Str*);
|
Rune slastr(Str*);
|
||||||
|
|
||||||
Hmap* hmapalloc(int);
|
|
||||||
void hmapfree(Hmap*);
|
|
||||||
void hmapset(Hmap**, Str*, const char*, int);
|
|
||||||
Hnode* hmapget(Hmap*, Str*);
|
|
||||||
int mapget(Trie*, Str*, Str*);
|
int mapget(Trie*, Str*, Str*);
|
||||||
|
|
||||||
|
Trie* trienew(void);
|
||||||
|
void trieput(Trie*, char*, int, char*, int);
|
||||||
Trie* trieopen(char*);
|
Trie* trieopen(char*);
|
||||||
void trieclose(Trie*);
|
void trieclose(Trie*);
|
||||||
char* trieget(Trie*, char*, int, int*);
|
int trienode(Trie*, Str*);
|
||||||
int trielookup(Trie*, char*, int, char**, int*);
|
int trielookup(Trie*, Str*, char**, int*);
|
||||||
|
|
||||||
Lang* getlang(int);
|
Lang* getlang(int);
|
||||||
void mapinit(char*);
|
void langinit(char*);
|
||||||
void dictinit(char*);
|
|
||||||
|
|
||||||
void dictthread(void*);
|
int dictlookup(Trie*, Str*, Str*, int);
|
||||||
void dictlookup(Dictreq*, Dictres*);
|
int dictprefix(Trie*, Str*, Str*, int);
|
||||||
|
|
||||||
void drawthread(void*);
|
void drawthread(void*);
|
||||||
int popupcells(Rune*, int);
|
void popuparea(Area*, int, Area*, int, int, Area*);
|
||||||
void popupposition(Caret*, int, int, int, int, int, int, int*, int*);
|
void popupposition(Caret*, int, int, Area*, int, int, int*, int*);
|
||||||
|
int pagemarker(char*, int, int, int, int);
|
||||||
|
void popuplayout(Drawcmd*, int, int, Popup*);
|
||||||
|
void popupdraw(u32int*, Drawcmd*, Popup*);
|
||||||
void imthread(void*);
|
void imthread(void*);
|
||||||
|
void impre(Im*, Str*);
|
||||||
|
void transstr(Lang*, Str*, Str*, Str*);
|
||||||
Emit transmap(Im*, Rune);
|
Emit transmap(Im*, Rune);
|
||||||
Emit transko(Im*, Rune);
|
Emit transko(Im*, Rune);
|
||||||
Emit transvi(Im*, Rune);
|
Emit transvi(Im*, Rune);
|
||||||
@@ -40,9 +43,19 @@ void backvi(Im*);
|
|||||||
void srvinit(void);
|
void srvinit(void);
|
||||||
void srvthread(void*);
|
void srvthread(void*);
|
||||||
void ibusthread(void*);
|
void ibusthread(void*);
|
||||||
|
void ximthread(void*);
|
||||||
|
int wlinit(void);
|
||||||
|
void wlthread(void*);
|
||||||
|
int keymeaningful(u32int);
|
||||||
|
|
||||||
|
void composeinit(void);
|
||||||
|
void composedrop(int, void*);
|
||||||
|
int composekey(int, void*, u32int, char*, int);
|
||||||
|
|
||||||
void* emalloc(ulong);
|
void* emalloc(ulong);
|
||||||
void* erealloc(void*, ulong);
|
void* erealloc(void*, ulong);
|
||||||
|
|
||||||
int fontinit(char**, int);
|
void textinit(void);
|
||||||
void putfont(u32int*, int, int, int, int, Rune);
|
void textclose(void);
|
||||||
|
int textwidth(Str*);
|
||||||
|
void textdraw(u32int*, int, int, int, int, int, u32int, Str*);
|
||||||
|
|||||||
280
font.c
280
font.c
@@ -1,168 +1,142 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include <ft2build.h>
|
#include <fontconfig/fontconfig.h>
|
||||||
#include FT_FREETYPE_H
|
#include <pango/pangocairo.h>
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
typedef struct Font Font;
|
static PangoFontMap *fontmap;
|
||||||
struct Font
|
static PangoContext *context;
|
||||||
|
static PangoLayout *layout;
|
||||||
|
|
||||||
|
void
|
||||||
|
textclose(void)
|
||||||
{
|
{
|
||||||
FT_Face face;
|
if(layout != nil)
|
||||||
int base;
|
g_object_unref(layout);
|
||||||
};
|
if(context != nil)
|
||||||
|
g_object_unref(context);
|
||||||
static FT_Library lib;
|
if(fontmap != nil)
|
||||||
static Font fonts[Maxfonts];
|
g_object_unref(fontmap);
|
||||||
static int nfonts;
|
layout = nil;
|
||||||
static u32int blendtab[2][256];
|
context = nil;
|
||||||
|
fontmap = nil;
|
||||||
static u32int
|
|
||||||
blend(u32int bg, u32int fg, int a)
|
|
||||||
{
|
|
||||||
int r, g, b, inv;
|
|
||||||
|
|
||||||
inv = 255 - a;
|
|
||||||
r = ((bg >> 16 & 0xff) * inv + (fg >> 16 & 0xff) * a) / 255;
|
|
||||||
g = ((bg >> 8 & 0xff) * inv + (fg >> 8 & 0xff) * a) / 255;
|
|
||||||
b = ((bg & 0xff) * inv + (fg & 0xff) * a) / 255;
|
|
||||||
return (r << 16) | (g << 8) | b;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
loadfont(char *path)
|
|
||||||
{
|
|
||||||
FT_Face face;
|
|
||||||
Font *f;
|
|
||||||
vlong height, pixels;
|
|
||||||
|
|
||||||
if(nfonts >= nelem(fonts))
|
|
||||||
return 0;
|
|
||||||
if(FT_New_Face(lib, path, 0, &face) != 0){
|
|
||||||
fprint(2, "strans: popup: can't load font: %s\n", path);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
height = (vlong)face->ascender - face->descender;
|
|
||||||
if(height <= 0 || face->units_per_EM == 0){
|
|
||||||
fprint(2, "strans: popup: invalid font metrics: %s\n", path);
|
|
||||||
FT_Done_Face(face);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
pixels = (vlong)Fontsz * face->units_per_EM / height;
|
|
||||||
if(pixels < 1)
|
|
||||||
pixels = 1;
|
|
||||||
if(FT_Set_Pixel_Sizes(face, 0, pixels) != 0){
|
|
||||||
fprint(2, "strans: popup: can't size font: %s\n", path);
|
|
||||||
FT_Done_Face(face);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
f = &fonts[nfonts];
|
|
||||||
f->face = face;
|
|
||||||
f->base = (vlong)Fontsz * face->ascender / height;
|
|
||||||
nfonts++;
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
clearfonts(void)
|
setfont(void)
|
||||||
{
|
{
|
||||||
int i;
|
PangoFontDescription *font;
|
||||||
|
PangoRectangle r;
|
||||||
|
int size;
|
||||||
|
|
||||||
for(i = 0; i < nfonts; i++){
|
font = pango_font_description_new();
|
||||||
FT_Done_Face(fonts[i].face);
|
pango_font_description_set_family(font, "sans");
|
||||||
fonts[i].face = nil;
|
pango_font_description_set_absolute_size(font, Fontsz * PANGO_SCALE);
|
||||||
|
pango_layout_set_font_description(layout, font);
|
||||||
|
pango_layout_set_text(layout, "Mg", -1);
|
||||||
|
pango_layout_get_pixel_extents(layout, nil, &r);
|
||||||
|
if(r.height > 0){
|
||||||
|
/* Fontsz is the popup row height, not a point size. */
|
||||||
|
size = Fontsz * PANGO_SCALE * Fontsz / r.height;
|
||||||
|
pango_font_description_set_absolute_size(font, max(size, PANGO_SCALE));
|
||||||
|
pango_layout_set_font_description(layout, font);
|
||||||
}
|
}
|
||||||
nfonts = 0;
|
pango_font_description_free(font);
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
fontinit(char **path, int npath)
|
|
||||||
{
|
|
||||||
int i, a;
|
|
||||||
|
|
||||||
if(path == nil || npath < 1 || npath > nelem(fonts)){
|
|
||||||
fprint(2, "strans: popup: need 1-%d font files\n", nelem(fonts));
|
|
||||||
clearfonts();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if(lib == nil && FT_Init_FreeType(&lib) != 0){
|
|
||||||
fprint(2, "strans: popup: can't initialize FreeType\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
clearfonts();
|
|
||||||
for(i = 0; i < npath; i++){
|
|
||||||
if(path[i] == nil || !loadfont(path[i])){
|
|
||||||
clearfonts();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(a = 0; a < 256; a++){
|
|
||||||
blendtab[0][a] = blend(Colbg, Colfg, a);
|
|
||||||
blendtab[1][a] = blend(Colsel, Colfg, a);
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
drawglyph(Font *f, u32int *buf, int w, int h, int px, int py)
|
|
||||||
{
|
|
||||||
FT_Bitmap *b;
|
|
||||||
FT_GlyphSlot g;
|
|
||||||
uchar *row;
|
|
||||||
vlong pitch, x0, x1, y0, y1;
|
|
||||||
int a, sel, x, xa, xb, y, ya, yb;
|
|
||||||
u32int *p;
|
|
||||||
|
|
||||||
g = f->face->glyph;
|
|
||||||
b = &g->bitmap;
|
|
||||||
if(b->pixel_mode != FT_PIXEL_MODE_GRAY || b->num_grays != 256)
|
|
||||||
return 0;
|
|
||||||
if(b->width == 0 || b->rows == 0)
|
|
||||||
return 1;
|
|
||||||
pitch = b->pitch;
|
|
||||||
if(pitch < 0)
|
|
||||||
pitch = -pitch;
|
|
||||||
if(b->buffer == nil || pitch < b->width)
|
|
||||||
return 0;
|
|
||||||
x0 = (vlong)px + g->bitmap_left;
|
|
||||||
y0 = (vlong)py + f->base - g->bitmap_top;
|
|
||||||
x1 = x0 + b->width;
|
|
||||||
y1 = y0 + b->rows;
|
|
||||||
if(x1 <= 0 || y1 <= 0 || x0 >= w || y0 >= h)
|
|
||||||
return 1;
|
|
||||||
xa = x0 < 0 ? 0 : x0;
|
|
||||||
xb = x1 > w ? w : x1;
|
|
||||||
ya = y0 < 0 ? 0 : y0;
|
|
||||||
yb = y1 > h ? h : y1;
|
|
||||||
for(y = ya; y < yb; y++){
|
|
||||||
if(b->pitch < 0)
|
|
||||||
row = b->buffer + ((vlong)b->rows - 1 - (y - y0)) * pitch;
|
|
||||||
else
|
|
||||||
row = b->buffer + ((vlong)y - y0) * pitch;
|
|
||||||
for(x = xa; x < xb; x++){
|
|
||||||
a = row[(vlong)x - x0];
|
|
||||||
if(a != 0){
|
|
||||||
p = &buf[y * w + x];
|
|
||||||
sel = *p == Colsel;
|
|
||||||
*p = blendtab[sel][a];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
|
textinit(void)
|
||||||
{
|
{
|
||||||
FT_UInt g;
|
FcInit();
|
||||||
int f;
|
fontmap = pango_cairo_font_map_new();
|
||||||
|
pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(fontmap), 96);
|
||||||
if(buf == nil || w <= 0 || h <= 0)
|
context = pango_font_map_create_context(fontmap);
|
||||||
return;
|
layout = pango_layout_new(context);
|
||||||
for(f = 0; f < nfonts; f++){
|
pango_layout_set_single_paragraph_mode(layout, TRUE);
|
||||||
g = FT_Get_Char_Index(fonts[f].face, r);
|
setfont();
|
||||||
if(g == 0 || FT_Load_Glyph(fonts[f].face, g, FT_LOAD_DEFAULT) != 0 ||
|
}
|
||||||
FT_Render_Glyph(fonts[f].face->glyph, FT_RENDER_MODE_NORMAL) != 0)
|
|
||||||
continue;
|
static int
|
||||||
if(drawglyph(&fonts[f], buf, w, h, px, py))
|
settext(Str *s)
|
||||||
return;
|
{
|
||||||
}
|
char utf[Maxutf];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
n = stoutf(s, utf, sizeof utf);
|
||||||
|
pango_layout_set_text(layout, utf, n);
|
||||||
|
return n > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
textextents(PangoRectangle *r)
|
||||||
|
{
|
||||||
|
PangoRectangle ink, logical;
|
||||||
|
int x0, y0, x1, y1;
|
||||||
|
|
||||||
|
pango_layout_get_pixel_extents(layout, &ink, &logical);
|
||||||
|
x0 = min(ink.x, logical.x);
|
||||||
|
y0 = min(ink.y, logical.y);
|
||||||
|
x1 = max(ink.x + ink.width, logical.x + logical.width);
|
||||||
|
y1 = max(ink.y + ink.height, logical.y + logical.height);
|
||||||
|
r->x = x0;
|
||||||
|
r->y = y0;
|
||||||
|
r->width = max(x1 - x0, 0);
|
||||||
|
r->height = max(y1 - y0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
textwidth(Str *s)
|
||||||
|
{
|
||||||
|
PangoRectangle r;
|
||||||
|
|
||||||
|
pango_layout_set_width(layout, -1);
|
||||||
|
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
|
||||||
|
if(!settext(s))
|
||||||
|
return 0;
|
||||||
|
textextents(&r);
|
||||||
|
return r.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Draws s at (x, y) into the w-by-h buffer, ellipsized to fit pixels and
|
||||||
|
* clipped to its own row so a tall fallback face cannot paint a
|
||||||
|
* neighbour.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
textdraw(u32int *buf, int w, int h, int x, int y, int fit, u32int color,
|
||||||
|
Str *s)
|
||||||
|
{
|
||||||
|
PangoRectangle r;
|
||||||
|
cairo_surface_t *surface;
|
||||||
|
cairo_t *cr;
|
||||||
|
int b, g, red;
|
||||||
|
|
||||||
|
if(fit <= 0)
|
||||||
|
return;
|
||||||
|
pango_layout_set_width(layout, fit * PANGO_SCALE);
|
||||||
|
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
|
||||||
|
if(w <= 0 || h <= 0 || !settext(s))
|
||||||
|
return;
|
||||||
|
surface = cairo_image_surface_create_for_data((uchar*)buf,
|
||||||
|
CAIRO_FORMAT_RGB24, w, h, w * sizeof buf[0]);
|
||||||
|
if(cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS){
|
||||||
|
cairo_surface_destroy(surface);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cairo_surface_mark_dirty(surface);
|
||||||
|
cr = cairo_create(surface);
|
||||||
|
if(cairo_status(cr) == CAIRO_STATUS_SUCCESS){
|
||||||
|
pango_cairo_update_layout(cr, layout);
|
||||||
|
textextents(&r);
|
||||||
|
cairo_rectangle(cr, x, y, fit, Fontsz);
|
||||||
|
cairo_clip(cr);
|
||||||
|
cairo_move_to(cr, x - r.x, y + (Fontsz - r.height) / 2 - r.y);
|
||||||
|
red = color >> 16 & 0xff;
|
||||||
|
g = color >> 8 & 0xff;
|
||||||
|
b = color & 0xff;
|
||||||
|
cairo_set_source_rgb(cr, red / 255.0, g / 255.0, b / 255.0);
|
||||||
|
pango_cairo_show_layout(cr, layout);
|
||||||
|
}
|
||||||
|
cairo_destroy(cr);
|
||||||
|
cairo_surface_flush(surface);
|
||||||
|
cairo_surface_destroy(surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
Copyright 2018 The Noto Project Authors (github.com/googlei18n/noto-fonts)
|
|
||||||
|
|
||||||
This Font Software is licensed under the SIL Open Font License,
|
|
||||||
Version 1.1.
|
|
||||||
|
|
||||||
This license is copied below, and is also available with a FAQ at:
|
|
||||||
http://scripts.sil.org/OFL
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
|
||||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
PREAMBLE
|
|
||||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
||||||
development of collaborative font projects, to support the font
|
|
||||||
creation efforts of academic and linguistic communities, and to
|
|
||||||
provide a free and open framework in which fonts may be shared and
|
|
||||||
improved in partnership with others.
|
|
||||||
|
|
||||||
The OFL allows the licensed fonts to be used, studied, modified and
|
|
||||||
redistributed freely as long as they are not sold by themselves. The
|
|
||||||
fonts, including any derivative works, can be bundled, embedded,
|
|
||||||
redistributed and/or sold with any software provided that any reserved
|
|
||||||
names are not used by derivative works. The fonts and derivatives,
|
|
||||||
however, cannot be released under any other type of license. The
|
|
||||||
requirement for fonts to remain under this license does not apply to
|
|
||||||
any document created using the fonts or their derivatives.
|
|
||||||
|
|
||||||
DEFINITIONS
|
|
||||||
"Font Software" refers to the set of files released by the Copyright
|
|
||||||
Holder(s) under this license and clearly marked as such. This may
|
|
||||||
include source files, build scripts and documentation.
|
|
||||||
|
|
||||||
"Reserved Font Name" refers to any names specified as such after the
|
|
||||||
copyright statement(s).
|
|
||||||
|
|
||||||
"Original Version" refers to the collection of Font Software
|
|
||||||
components as distributed by the Copyright Holder(s).
|
|
||||||
|
|
||||||
"Modified Version" refers to any derivative made by adding to,
|
|
||||||
deleting, or substituting -- in part or in whole -- any of the
|
|
||||||
components of the Original Version, by changing formats or by porting
|
|
||||||
the Font Software to a new environment.
|
|
||||||
|
|
||||||
"Author" refers to any designer, engineer, programmer, technical
|
|
||||||
writer or other person who contributed to the Font Software.
|
|
||||||
|
|
||||||
PERMISSION & CONDITIONS
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
|
||||||
modify, redistribute, and sell modified and unmodified copies of the
|
|
||||||
Font Software, subject to the following conditions:
|
|
||||||
|
|
||||||
1) Neither the Font Software nor any of its individual components, in
|
|
||||||
Original or Modified Versions, may be sold by itself.
|
|
||||||
|
|
||||||
2) Original or Modified Versions of the Font Software may be bundled,
|
|
||||||
redistributed and/or sold with any software, provided that each copy
|
|
||||||
contains the above copyright notice and this license. These can be
|
|
||||||
included either as stand-alone text files, human-readable headers or
|
|
||||||
in the appropriate machine-readable metadata fields within text or
|
|
||||||
binary files as long as those fields can be easily viewed by the user.
|
|
||||||
|
|
||||||
3) No Modified Version of the Font Software may use the Reserved Font
|
|
||||||
Name(s) unless explicit written permission is granted by the
|
|
||||||
corresponding Copyright Holder. This restriction only applies to the
|
|
||||||
primary font name as presented to the users.
|
|
||||||
|
|
||||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
||||||
Software shall not be used to promote, endorse or advertise any
|
|
||||||
Modified Version, except to acknowledge the contribution(s) of the
|
|
||||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
||||||
permission.
|
|
||||||
|
|
||||||
5) The Font Software, modified or unmodified, in part or in whole,
|
|
||||||
must be distributed entirely under this license, and must not be
|
|
||||||
distributed under any other license. The requirement for fonts to
|
|
||||||
remain under this license does not apply to any document created using
|
|
||||||
the Font Software.
|
|
||||||
|
|
||||||
TERMINATION
|
|
||||||
This license becomes null and void if any of the above conditions are
|
|
||||||
not met.
|
|
||||||
|
|
||||||
DISCLAIMER
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
||||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
||||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
||||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
||||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
This Font Software is licensed under the SIL Open Font License,
|
|
||||||
Version 1.1.
|
|
||||||
|
|
||||||
This license is copied below, and is also available with a FAQ at:
|
|
||||||
http://scripts.sil.org/OFL
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
|
||||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
PREAMBLE
|
|
||||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
||||||
development of collaborative font projects, to support the font
|
|
||||||
creation efforts of academic and linguistic communities, and to
|
|
||||||
provide a free and open framework in which fonts may be shared and
|
|
||||||
improved in partnership with others.
|
|
||||||
|
|
||||||
The OFL allows the licensed fonts to be used, studied, modified and
|
|
||||||
redistributed freely as long as they are not sold by themselves. The
|
|
||||||
fonts, including any derivative works, can be bundled, embedded,
|
|
||||||
redistributed and/or sold with any software provided that any reserved
|
|
||||||
names are not used by derivative works. The fonts and derivatives,
|
|
||||||
however, cannot be released under any other type of license. The
|
|
||||||
requirement for fonts to remain under this license does not apply to
|
|
||||||
any document created using the fonts or their derivatives.
|
|
||||||
|
|
||||||
DEFINITIONS
|
|
||||||
"Font Software" refers to the set of files released by the Copyright
|
|
||||||
Holder(s) under this license and clearly marked as such. This may
|
|
||||||
include source files, build scripts and documentation.
|
|
||||||
|
|
||||||
"Reserved Font Name" refers to any names specified as such after the
|
|
||||||
copyright statement(s).
|
|
||||||
|
|
||||||
"Original Version" refers to the collection of Font Software
|
|
||||||
components as distributed by the Copyright Holder(s).
|
|
||||||
|
|
||||||
"Modified Version" refers to any derivative made by adding to,
|
|
||||||
deleting, or substituting -- in part or in whole -- any of the
|
|
||||||
components of the Original Version, by changing formats or by porting
|
|
||||||
the Font Software to a new environment.
|
|
||||||
|
|
||||||
"Author" refers to any designer, engineer, programmer, technical
|
|
||||||
writer or other person who contributed to the Font Software.
|
|
||||||
|
|
||||||
PERMISSION & CONDITIONS
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
|
||||||
modify, redistribute, and sell modified and unmodified copies of the
|
|
||||||
Font Software, subject to the following conditions:
|
|
||||||
|
|
||||||
1) Neither the Font Software nor any of its individual components, in
|
|
||||||
Original or Modified Versions, may be sold by itself.
|
|
||||||
|
|
||||||
2) Original or Modified Versions of the Font Software may be bundled,
|
|
||||||
redistributed and/or sold with any software, provided that each copy
|
|
||||||
contains the above copyright notice and this license. These can be
|
|
||||||
included either as stand-alone text files, human-readable headers or
|
|
||||||
in the appropriate machine-readable metadata fields within text or
|
|
||||||
binary files as long as those fields can be easily viewed by the user.
|
|
||||||
|
|
||||||
3) No Modified Version of the Font Software may use the Reserved Font
|
|
||||||
Name(s) unless explicit written permission is granted by the
|
|
||||||
corresponding Copyright Holder. This restriction only applies to the
|
|
||||||
primary font name as presented to the users.
|
|
||||||
|
|
||||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
||||||
Software shall not be used to promote, endorse or advertise any
|
|
||||||
Modified Version, except to acknowledge the contribution(s) of the
|
|
||||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
||||||
permission.
|
|
||||||
|
|
||||||
5) The Font Software, modified or unmodified, in part or in whole,
|
|
||||||
must be distributed entirely under this license, and must not be
|
|
||||||
distributed under any other license. The requirement for fonts to
|
|
||||||
remain under this license does not apply to any document created using
|
|
||||||
the Font Software.
|
|
||||||
|
|
||||||
TERMINATION
|
|
||||||
This license becomes null and void if any of the above conditions are
|
|
||||||
not met.
|
|
||||||
|
|
||||||
DISCLAIMER
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
||||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
||||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
||||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
||||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
Historical popup fonts
|
|
||||||
======================
|
|
||||||
|
|
||||||
These binaries are no longer stored in the strans source tree. Before font
|
|
||||||
selection became an ordered list of command-line file paths, the renderer used
|
|
||||||
these repository files in this fixed order:
|
|
||||||
|
|
||||||
1. NotoSans-Regular.ttf
|
|
||||||
2. NotoSansMonoCJKjp-Regular.otf
|
|
||||||
3. NotoEmoji-Regular.ttf
|
|
||||||
|
|
||||||
NotoSans-Regular.ttf
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
SHA-256: 9cb49a54e520423033f9727be2e53e4805a60656deb09c219740d8e5f3e033ac
|
|
||||||
Internal family/version: Noto Sans Regular, version 2.005
|
|
||||||
License: SIL Open Font License 1.1 (OFL-NotoSans.txt)
|
|
||||||
Source revision: 7697007fcb3563290d73f41f56a70d5d559d828c
|
|
||||||
Source: https://raw.githubusercontent.com/notofonts/noto-fonts/7697007fcb3563290d73f41f56a70d5d559d828c/hinted/ttf/NotoSans/NotoSans-Regular.ttf
|
|
||||||
|
|
||||||
This file entered the strans repository in commit 874b941. Its source URL was
|
|
||||||
not recorded then; the immutable revision above was recovered later and is
|
|
||||||
byte-identical to the historical file.
|
|
||||||
|
|
||||||
NotoSansMonoCJKjp-Regular.otf
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
SHA-256: 4d01725be822d144cf9a56ade981e6fb920cd7a610b8fc24cc601a920beea5b9
|
|
||||||
Release: Noto Sans CJK 2.004, tag Sans2.004
|
|
||||||
Source revision: 523d033d6cb47f4a80c58a35753646f5c3608a78
|
|
||||||
Source: https://raw.githubusercontent.com/notofonts/noto-cjk/523d033d6cb47f4a80c58a35753646f5c3608a78/Sans/Mono/NotoSansMonoCJKjp-Regular.otf
|
|
||||||
License: SIL Open Font License 1.1 (OFL-NotoSansCJK.txt)
|
|
||||||
License source: https://raw.githubusercontent.com/notofonts/noto-cjk/523d033d6cb47f4a80c58a35753646f5c3608a78/LICENSE
|
|
||||||
|
|
||||||
NotoEmoji-Regular.ttf
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
SHA-256: 415dc6290378574135b64c808dc640c1df7531973290c4970c51fdeb849cb0c5
|
|
||||||
Internal family/version: Noto Emoji Regular, version 1.05
|
|
||||||
Source revision: 2f1ffdd6fbbd05d6f382138a3d3adcd89c5ce800
|
|
||||||
Source: https://raw.githubusercontent.com/googlei18n/noto-emoji/2f1ffdd6fbbd05d6f382138a3d3adcd89c5ce800/fonts/NotoEmoji-Regular.ttf
|
|
||||||
Font license: SIL Open Font License 1.1
|
|
||||||
Font license source: https://raw.githubusercontent.com/googlei18n/noto-emoji/2f1ffdd6fbbd05d6f382138a3d3adcd89c5ce800/fonts/LICENSE
|
|
||||||
Font license SHA-256: 6a73f9541c2de74158c0e7cf6b0a58ef774f5a780bf191f2d7ec9cc53efe2bf2
|
|
||||||
|
|
||||||
The font-specific license is byte-identical to `OFL-NotoSansCJK.txt`. The
|
|
||||||
previously recorded `Apache-2.0-NotoEmoji.txt` is retained as the license of
|
|
||||||
the upstream project at that revision; it is not the license declared by this
|
|
||||||
font binary.
|
|
||||||
41
gtk/Makefile
41
gtk/Makefile
@@ -1,24 +1,41 @@
|
|||||||
PROG = im-strans.so
|
PROG = im-strans.so
|
||||||
CFLAGS = -Wall -O2 -I.. $(shell pkg-config --cflags gtk+-3.0)
|
PKG_CONFIG ?= pkg-config
|
||||||
LDFLAGS = $(shell pkg-config --libs gtk+-3.0)
|
CFLAGS ?= -O2
|
||||||
LIBDIR ?= /usr/lib
|
GTK_CFLAGS = $(shell $(PKG_CONFIG) --cflags gtk+-3.0)
|
||||||
GTK_MODULE_DIR = $(LIBDIR)/gtk-3.0/3.0.0/immodules
|
GTK_LIBS = $(shell $(PKG_CONFIG) --libs gtk+-3.0)
|
||||||
|
GTK_QUERY_IMMODULES ?= gtk-query-immodules-3.0
|
||||||
|
GTK_LIBDIR ?= $(shell $(PKG_CONFIG) --variable=libdir gtk+-3.0 2>/dev/null)
|
||||||
|
GTK_BINARY_VERSION ?= $(shell $(PKG_CONFIG) \
|
||||||
|
--variable=gtk_binary_version gtk+-3.0 2>/dev/null)
|
||||||
|
# Without GTK development files, ask the runtime where its modules live.
|
||||||
|
GTK_RUNTIME_MODULE_DIR = $(shell GTK_PATH= GTK_IM_MODULE_FILE= \
|
||||||
|
$(GTK_QUERY_IMMODULES) 2>/dev/null | \
|
||||||
|
awk -F '"' '/^"\// { sub("/[^/]*$$", "", $$2); print $$2; exit }')
|
||||||
|
GTK_MODULE_DIR ?= $(strip $(if \
|
||||||
|
$(and $(strip $(GTK_LIBDIR)),$(strip $(GTK_BINARY_VERSION))),\
|
||||||
|
$(GTK_LIBDIR)/gtk-3.0/$(GTK_BINARY_VERSION)/immodules,\
|
||||||
|
$(GTK_RUNTIME_MODULE_DIR)))
|
||||||
|
|
||||||
all: $(PROG)
|
all: $(PROG)
|
||||||
|
|
||||||
$(PROG): main.c ../ipc.c ../ipc.h
|
$(PROG): main.c ../ipc.c ../ipc.h
|
||||||
$(CC) -shared -fPIC $(CFLAGS) -o $@ main.c ../ipc.c $(LDFLAGS)
|
$(CC) $(CPPFLAGS) -I.. $(GTK_CFLAGS) -Wall $(CFLAGS) $(LDFLAGS) \
|
||||||
|
-shared -fPIC -o $@ main.c ../ipc.c $(GTK_LIBS) $(LDLIBS)
|
||||||
|
|
||||||
|
CHECKDIR = test -n "$$module_dir" || { \
|
||||||
|
echo "cannot find GTK 3 module directory; set GTK_MODULE_DIR" >&2; \
|
||||||
|
exit 1; }
|
||||||
|
REFRESH = test -n "$(DESTDIR)" || { \
|
||||||
|
unset GTK_PATH GTK_IM_MODULE_FILE; $(GTK_QUERY_IMMODULES) --update-cache; }
|
||||||
|
|
||||||
install: $(PROG)
|
install: $(PROG)
|
||||||
test -n "$(LIBDIR)"
|
module_dir="$(GTK_MODULE_DIR)"; $(CHECKDIR); \
|
||||||
mkdir -p "$(DESTDIR)$(GTK_MODULE_DIR)"
|
mkdir -p "$(DESTDIR)$$module_dir" && \
|
||||||
cp $(PROG) "$(DESTDIR)$(GTK_MODULE_DIR)/"
|
cp "$(PROG)" "$(DESTDIR)$$module_dir/" && $(REFRESH)
|
||||||
if test -z "$(DESTDIR)"; then gtk-query-immodules-3.0 --update-cache; fi
|
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
test -n "$(LIBDIR)"
|
module_dir="$(GTK_MODULE_DIR)"; $(CHECKDIR); \
|
||||||
rm -f "$(DESTDIR)$(GTK_MODULE_DIR)/$(PROG)"
|
rm -f "$(DESTDIR)$$module_dir/$(PROG)" && $(REFRESH)
|
||||||
if test -z "$(DESTDIR)"; then gtk-query-immodules-3.0 --update-cache; fi
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(PROG)
|
rm -f $(PROG)
|
||||||
|
|||||||
526
gtk/main.c
526
gtk/main.c
@@ -1,132 +1,314 @@
|
|||||||
#include <stdio.h>
|
#include <errno.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
#ifdef GDK_WINDOWING_X11
|
||||||
|
#include <gdk/gdkx.h>
|
||||||
|
#endif
|
||||||
#include "ipc.h"
|
#include "ipc.h"
|
||||||
|
|
||||||
typedef struct Im Im;
|
typedef struct Im Im;
|
||||||
struct Im
|
struct Im
|
||||||
{
|
{
|
||||||
GtkIMContext parent;
|
GtkIMContextSimple parent;
|
||||||
int fd;
|
int fd;
|
||||||
|
int usepreedit;
|
||||||
|
int ext;
|
||||||
|
int private;
|
||||||
|
int simpleactive;
|
||||||
|
int simpledone;
|
||||||
char pre[Ipcfieldmax+1];
|
char pre[Ipcfieldmax+1];
|
||||||
int prelen;
|
int prelen;
|
||||||
|
GdkWindow *win;
|
||||||
|
GdkRectangle cursor;
|
||||||
|
int cursorvalid;
|
||||||
|
int caretsent;
|
||||||
|
unsigned char sent[Ipccaretsz];
|
||||||
|
char surround[Ipcfieldmax];
|
||||||
|
int nsurround;
|
||||||
|
int surroundsent;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct ImClass ImClass;
|
typedef struct ImClass ImClass;
|
||||||
struct ImClass
|
struct ImClass
|
||||||
{
|
{
|
||||||
GtkIMContextClass parent;
|
GtkIMContextSimpleClass parent;
|
||||||
};
|
};
|
||||||
|
|
||||||
static GType imtype;
|
static GType imtype;
|
||||||
|
static GObjectClass *parentobject;
|
||||||
|
static GtkIMContextClass *parentim;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
srvconnect(Im *im)
|
setpreedit(Im *im, const char *s, int n)
|
||||||
|
{
|
||||||
|
int was;
|
||||||
|
|
||||||
|
if(n < 0 || n > Ipcfieldmax)
|
||||||
|
n = 0;
|
||||||
|
if(n == 0 && im->prelen == 0)
|
||||||
|
return;
|
||||||
|
was = im->prelen;
|
||||||
|
if(n > 0)
|
||||||
|
memcpy(im->pre, s, n);
|
||||||
|
im->pre[n] = '\0';
|
||||||
|
im->prelen = n;
|
||||||
|
if(was == 0 && n > 0)
|
||||||
|
g_signal_emit_by_name(im, "preedit-start");
|
||||||
|
g_signal_emit_by_name(im, "preedit-changed");
|
||||||
|
if(was > 0 && n == 0)
|
||||||
|
g_signal_emit_by_name(im, "preedit-end");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
srvdrop(Im *im, int notify)
|
||||||
{
|
{
|
||||||
if(im->fd >= 0)
|
if(im->fd >= 0)
|
||||||
return;
|
close(im->fd);
|
||||||
im->fd = ipcconnect();
|
im->fd = -1;
|
||||||
|
im->ext = 0;
|
||||||
|
im->caretsent = 0;
|
||||||
|
im->surroundsent = 0;
|
||||||
|
if(notify)
|
||||||
|
setpreedit(im, "", 0);
|
||||||
|
else{
|
||||||
|
im->pre[0] = '\0';
|
||||||
|
im->prelen = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
srvclose(Im *im)
|
srvclose(Im *im)
|
||||||
{
|
{
|
||||||
int was;
|
srvdrop(im, 1);
|
||||||
|
}
|
||||||
|
|
||||||
if(im->fd >= 0)
|
/* Both buffers are char[Ipcfieldmax+1]. */
|
||||||
close(im->fd);
|
static int
|
||||||
im->fd = -1;
|
readresp(Im *im, int want, char *commit, char *pre, Ipcresp *resp)
|
||||||
was = im->prelen;
|
{
|
||||||
im->pre[0] = '\0';
|
if(ipcreadresp(im->fd, want, commit, pre, resp) < 0)
|
||||||
im->prelen = 0;
|
return -1;
|
||||||
if(was > 0){
|
if(!g_utf8_validate(commit, resp->commitlen, NULL) ||
|
||||||
g_signal_emit_by_name(im, "preedit-changed");
|
(want && !g_utf8_validate(pre, resp->preeditlen, NULL))){
|
||||||
g_signal_emit_by_name(im, "preedit-end");
|
errno = EPROTO;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dropwindow(Im *im)
|
||||||
|
{
|
||||||
|
if(im->win != NULL)
|
||||||
|
g_object_unref(im->win);
|
||||||
|
im->win = NULL;
|
||||||
|
im->cursorvalid = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Root-relative caret in device pixels; returns whether it is known. */
|
||||||
|
static int
|
||||||
|
caretget(Im *im, int32_t *x, int32_t *y, int32_t *h)
|
||||||
|
{
|
||||||
|
gint rx, ry, scale;
|
||||||
|
int64_t sx, sy, sh;
|
||||||
|
|
||||||
|
*x = *y = *h = 0;
|
||||||
|
if(im->win != NULL && gdk_window_is_destroyed(im->win))
|
||||||
|
dropwindow(im);
|
||||||
|
if(!im->cursorvalid || im->win == NULL || im->cursor.height < 0)
|
||||||
|
return 0;
|
||||||
|
#ifdef GDK_WINDOWING_X11
|
||||||
|
if(!GDK_IS_X11_WINDOW(im->win))
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
scale = gdk_window_get_scale_factor(im->win);
|
||||||
|
if(scale <= 0)
|
||||||
|
return 0;
|
||||||
|
/* GDK translates the client-relative point before device scaling. */
|
||||||
|
gdk_window_get_root_coords(im->win, 0, 0, &rx, &ry);
|
||||||
|
sx = ((int64_t)rx + im->cursor.x) * scale;
|
||||||
|
sy = ((int64_t)ry + im->cursor.y) * scale;
|
||||||
|
sh = (int64_t)im->cursor.height * scale;
|
||||||
|
if(sx < INT32_MIN || sx > INT32_MAX ||
|
||||||
|
sy < INT32_MIN || sy > INT32_MAX || sh > INT32_MAX)
|
||||||
|
return 0;
|
||||||
|
*x = sx;
|
||||||
|
*y = sy;
|
||||||
|
*h = sh;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
readresp(Im *im, char *buf, int bufsz)
|
sendcaret(Im *im)
|
||||||
{
|
{
|
||||||
Ipcresp resp;
|
unsigned char buf[Ipccaretsz];
|
||||||
int was;
|
int32_t x, y, h;
|
||||||
|
int valid;
|
||||||
|
|
||||||
was = im->prelen;
|
if(im->fd < 0 || !im->ext)
|
||||||
if(ipcreadresp(im->fd, 1, buf, bufsz, im->pre,
|
return 0;
|
||||||
sizeof(im->pre), &resp) < 0)
|
valid = caretget(im, &x, &y, &h);
|
||||||
|
ipcpackcaret(buf, valid, x, y, h);
|
||||||
|
if(im->caretsent && memcmp(buf, im->sent, sizeof buf) == 0)
|
||||||
|
return 0;
|
||||||
|
if(ipcsend(im->fd, buf, sizeof buf) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
im->prelen = resp.npreedit;
|
memcpy(im->sent, buf, sizeof buf);
|
||||||
if(was == 0 && im->prelen > 0)
|
im->caretsent = 1;
|
||||||
g_signal_emit_by_name(im, "preedit-start");
|
return 0;
|
||||||
if(was != 0 || im->prelen != 0)
|
|
||||||
g_signal_emit_by_name(im, "preedit-changed");
|
|
||||||
if(was > 0 && im->prelen == 0)
|
|
||||||
g_signal_emit_by_name(im, "preedit-end");
|
|
||||||
return resp.eaten;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t
|
/*
|
||||||
kget(uint32_t gdk)
|
* The text just before the cursor, as much of it as a reading can use,
|
||||||
|
* cut on a rune boundary. A widget that keeps none leaves it empty, and
|
||||||
|
* then the daemon never asks for any of it back.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
readsurround(Im *im, GtkIMContext *ctx)
|
||||||
{
|
{
|
||||||
uint32_t u;
|
char *text, *p;
|
||||||
|
gint cursor;
|
||||||
|
int start;
|
||||||
|
|
||||||
u = gdk_keyval_to_unicode(gdk);
|
im->nsurround = 0;
|
||||||
if((gdk & 0xff000000) == 0x01000000 && u != 0)
|
if(!gtk_im_context_get_surrounding(ctx, &text, &cursor))
|
||||||
return u;
|
return;
|
||||||
if(gdk >= 0xff00 && gdk <= 0xffff)
|
if(cursor < 0 || cursor > (gint)strlen(text)){
|
||||||
return Kspec + (gdk - 0xff00);
|
g_free(text);
|
||||||
return u;
|
return;
|
||||||
|
}
|
||||||
|
start = cursor > Ipcfieldmax ? cursor - Ipcfieldmax : 0;
|
||||||
|
for(p = text + start; p < text + cursor && (*p & 0xc0) == 0x80; p++)
|
||||||
|
;
|
||||||
|
im->nsurround = text + cursor - p;
|
||||||
|
memcpy(im->surround, p, im->nsurround);
|
||||||
|
g_free(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t
|
/* Sent only when it changes, as the caret is. */
|
||||||
mget(uint32_t state)
|
static int
|
||||||
|
sendsurround(Im *im, GtkIMContext *ctx)
|
||||||
{
|
{
|
||||||
uint32_t m;
|
unsigned char hdr[Ipcreqsz];
|
||||||
|
char was[Ipcfieldmax];
|
||||||
|
int nwas;
|
||||||
|
|
||||||
m = 0;
|
if(im->fd < 0 || !im->ext)
|
||||||
if(state & GDK_SHIFT_MASK)
|
return 0;
|
||||||
m |= Mshift;
|
nwas = im->nsurround;
|
||||||
if(state & GDK_CONTROL_MASK)
|
memcpy(was, im->surround, nwas);
|
||||||
m |= Mctrl;
|
readsurround(im, ctx);
|
||||||
if(state & GDK_MOD1_MASK)
|
if(im->surroundsent && nwas == im->nsurround &&
|
||||||
m |= Malt;
|
memcmp(was, im->surround, nwas) == 0)
|
||||||
if(state & GDK_SUPER_MASK)
|
return 0;
|
||||||
m |= Msuper;
|
ipcpacksurround(hdr, im->nsurround);
|
||||||
return m;
|
if(ipcsend(im->fd, hdr, sizeof hdr) < 0 ||
|
||||||
|
(im->nsurround > 0 &&
|
||||||
|
ipcsend(im->fd, im->surround, im->nsurround) < 0))
|
||||||
|
return -1;
|
||||||
|
im->surroundsent = 1;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
srvconnect(Im *im)
|
||||||
|
{
|
||||||
|
unsigned char buf[Ipcreqsz];
|
||||||
|
char commit[Ipcfieldmax+1];
|
||||||
|
char pre[Ipcfieldmax+1];
|
||||||
|
Ipcresp resp;
|
||||||
|
|
||||||
|
if(im->fd >= 0)
|
||||||
|
return 0;
|
||||||
|
im->fd = ipcconnect();
|
||||||
|
if(im->fd < 0)
|
||||||
|
return -1;
|
||||||
|
/* Marker zero is an old daemon's harmless key-zero response. */
|
||||||
|
ipcpackcap(buf, im->usepreedit);
|
||||||
|
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||||
|
readresp(im, im->usepreedit, commit, pre, &resp) < 0){
|
||||||
|
srvclose(im);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
im->ext = resp.eaten != 0;
|
||||||
|
if(sendcaret(im) < 0){
|
||||||
|
srvclose(im);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
simplecommit(GtkIMContext *ctx, const char *s, Im *im)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
(void)s;
|
||||||
|
im->simpledone = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
simpleend(GtkIMContext *ctx, Im *im)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
im->simpledone = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GtkIMContextSimple composes dead keys and Compose sequences; it keeps
|
||||||
|
* the key stream until it commits, ends its preedit, or rejects a press.
|
||||||
|
*/
|
||||||
|
static gboolean
|
||||||
|
simplefilter(GtkIMContext *ctx, GdkEventKey *ev, int release)
|
||||||
|
{
|
||||||
|
Im *im;
|
||||||
|
gboolean r;
|
||||||
|
|
||||||
|
im = (Im*)ctx;
|
||||||
|
im->simpleactive = 1;
|
||||||
|
im->simpledone = 0;
|
||||||
|
r = parentim->filter_keypress(ctx, ev);
|
||||||
|
if((!r && !release) || im->simpledone)
|
||||||
|
im->simpleactive = 0;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The daemon hands the pending text back; it becomes committed text. */
|
||||||
static void
|
static void
|
||||||
sendreset(Im *im)
|
sendreset(Im *im)
|
||||||
{
|
{
|
||||||
unsigned char buf[Ipcreqsz];
|
unsigned char buf[Ipcreqsz];
|
||||||
char resp[Ipcfieldmax+1];
|
char commit[Ipcfieldmax+1];
|
||||||
|
char pre[Ipcfieldmax+1];
|
||||||
|
Ipcresp resp;
|
||||||
|
|
||||||
if(im->fd < 0){
|
if(im->fd < 0){
|
||||||
|
setpreedit(im, "", 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ipcpackreset(buf, im->usepreedit);
|
||||||
|
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||||
|
readresp(im, im->usepreedit, commit, pre, &resp) < 0){
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ipcpackreset(buf, 1);
|
setpreedit(im, "", 0);
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
if(commit[0] != '\0')
|
||||||
readresp(im, resp, sizeof(resp)) < 0)
|
g_signal_emit_by_name(im, "commit", commit);
|
||||||
srvclose(im);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
/* A password entry hidden the older way sets no purpose and signals nothing. */
|
||||||
plaincommit(GtkIMContext *ctx, uint32_t key, uint32_t mod)
|
static int
|
||||||
|
ishidden(Im *im)
|
||||||
{
|
{
|
||||||
char u[8];
|
GtkWidget *w;
|
||||||
int n;
|
|
||||||
|
|
||||||
if(mod & (Mctrl|Malt|Msuper))
|
if(im->win == NULL)
|
||||||
return FALSE;
|
return 0;
|
||||||
if(key < 0x20 || key >= Kspec)
|
gdk_window_get_user_data(im->win, (gpointer*)&w);
|
||||||
return FALSE;
|
return GTK_IS_ENTRY(w) && !gtk_entry_get_visibility(GTK_ENTRY(w));
|
||||||
n = g_unichar_to_utf8(key, u);
|
|
||||||
u[n] = '\0';
|
|
||||||
g_signal_emit_by_name(ctx, "commit", u);
|
|
||||||
return TRUE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
@@ -134,41 +316,56 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
|||||||
{
|
{
|
||||||
Im *im;
|
Im *im;
|
||||||
unsigned char buf[Ipcreqsz];
|
unsigned char buf[Ipcreqsz];
|
||||||
char resp[Ipcfieldmax+1];
|
char commit[Ipcfieldmax+1];
|
||||||
|
char pre[Ipcfieldmax+1];
|
||||||
uint32_t key, mod;
|
uint32_t key, mod;
|
||||||
int r;
|
Ipcresp resp;
|
||||||
GtkInputPurpose purpose;
|
gboolean r;
|
||||||
|
|
||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
if(ev->type != GDK_KEY_PRESS)
|
if(ev->type != GDK_KEY_PRESS){
|
||||||
|
if(im->simpleactive)
|
||||||
|
return simplefilter(ctx, ev, 1);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
key = kget(ev->keyval);
|
|
||||||
if(key == 0)
|
|
||||||
return FALSE;
|
|
||||||
mod = mget(ev->state);
|
|
||||||
g_object_get(ctx, "input-purpose", &purpose, NULL);
|
|
||||||
if(purpose == GTK_INPUT_PURPOSE_PASSWORD || purpose == GTK_INPUT_PURPOSE_PIN)
|
|
||||||
return plaincommit(ctx, key, mod);
|
|
||||||
srvconnect(im);
|
|
||||||
if(im->fd < 0){
|
|
||||||
srvclose(im);
|
|
||||||
return plaincommit(ctx, key, mod);
|
|
||||||
}
|
}
|
||||||
ipcpackreq(buf, 1, mod, key);
|
if(im->simpleactive)
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0){
|
return simplefilter(ctx, ev, 0);
|
||||||
srvclose(im);
|
key = ipckeysym(ev->keyval, gdk_keyval_to_unicode(ev->keyval));
|
||||||
return plaincommit(ctx, key, mod);
|
mod = ipcmod(ev->state);
|
||||||
|
if(im->private || ishidden(im) || key == 0){
|
||||||
|
r = simplefilter(ctx, ev, 0);
|
||||||
|
/* A dead key starts a compose: pending text was typed first. */
|
||||||
|
if(im->simpleactive)
|
||||||
|
sendreset(im);
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
r = readresp(im, resp, sizeof(resp));
|
if(srvconnect(im) < 0)
|
||||||
if(r < 0){
|
return simplefilter(ctx, ev, 0);
|
||||||
|
/* A retained GDK window may have moved since the last cursor report. */
|
||||||
|
if(sendcaret(im) < 0 || sendsurround(im, ctx) < 0){
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
return plaincommit(ctx, key, mod);
|
return simplefilter(ctx, ev, 0);
|
||||||
}
|
}
|
||||||
if(resp[0] != '\0')
|
ipcpackreq(buf, im->usepreedit, mod, key);
|
||||||
g_signal_emit_by_name(ctx, "commit", resp);
|
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||||
if(r != 0)
|
readresp(im, im->usepreedit, commit, pre, &resp) < 0){
|
||||||
|
srvclose(im);
|
||||||
|
return simplefilter(ctx, ev, 0);
|
||||||
|
}
|
||||||
|
if(im->usepreedit && commit[0] != '\0' && im->prelen > 0)
|
||||||
|
setpreedit(im, "", 0);
|
||||||
|
/* The reading reached into the widget's own text: give it back. */
|
||||||
|
if(resp.del > 0){
|
||||||
|
gtk_im_context_delete_surrounding(ctx, -resp.del, resp.del);
|
||||||
|
im->surroundsent = 0;
|
||||||
|
}
|
||||||
|
if(commit[0] != '\0')
|
||||||
|
g_signal_emit_by_name(ctx, "commit", commit);
|
||||||
|
if(im->usepreedit)
|
||||||
|
setpreedit(im, pre, resp.preeditlen);
|
||||||
|
if(resp.eaten)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
return plaincommit(ctx, key, mod);
|
return simplefilter(ctx, ev, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -179,6 +376,10 @@ getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs,
|
|||||||
PangoAttribute *u;
|
PangoAttribute *u;
|
||||||
|
|
||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
|
if(im->simpleactive){
|
||||||
|
parentim->get_preedit_string(ctx, str, attrs, cursor_pos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(str)
|
if(str)
|
||||||
*str = g_strdup(im->pre);
|
*str = g_strdup(im->pre);
|
||||||
if(attrs){
|
if(attrs){
|
||||||
@@ -197,6 +398,9 @@ getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs,
|
|||||||
static void
|
static void
|
||||||
reset(GtkIMContext *ctx)
|
reset(GtkIMContext *ctx)
|
||||||
{
|
{
|
||||||
|
if(parentim->reset != NULL)
|
||||||
|
parentim->reset(ctx);
|
||||||
|
((Im*)ctx)->simpleactive = 0;
|
||||||
sendreset((Im*)ctx);
|
sendreset((Im*)ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,28 +410,136 @@ focusout(GtkIMContext *ctx)
|
|||||||
Im *im;
|
Im *im;
|
||||||
|
|
||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
|
if(parentim->focus_out != NULL)
|
||||||
|
parentim->focus_out(ctx);
|
||||||
|
im->simpleactive = 0;
|
||||||
|
/* Closing the connection releases the engine. */
|
||||||
sendreset(im);
|
sendreset(im);
|
||||||
/* Closing the context connection releases engine ownership. */
|
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
finalize(GObject *obj)
|
setusepreedit(GtkIMContext *ctx, gboolean use)
|
||||||
|
{
|
||||||
|
Im *im;
|
||||||
|
unsigned char buf[Ipcreqsz];
|
||||||
|
char commit[Ipcfieldmax+1];
|
||||||
|
char pre[Ipcfieldmax+1];
|
||||||
|
Ipcresp resp;
|
||||||
|
|
||||||
|
im = (Im*)ctx;
|
||||||
|
use = use != FALSE;
|
||||||
|
if(parentim->set_use_preedit != NULL)
|
||||||
|
parentim->set_use_preedit(ctx, use);
|
||||||
|
if(im->usepreedit == use)
|
||||||
|
return;
|
||||||
|
im->usepreedit = use;
|
||||||
|
if(!use)
|
||||||
|
setpreedit(im, "", 0);
|
||||||
|
if(im->fd < 0 || !im->ext)
|
||||||
|
return;
|
||||||
|
ipcpackcap(buf, use);
|
||||||
|
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||||
|
readresp(im, use, commit, pre, &resp) < 0 ||
|
||||||
|
!resp.eaten){
|
||||||
|
srvclose(im);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(use)
|
||||||
|
setpreedit(im, pre, resp.preeditlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
setclientwindow(GtkIMContext *ctx, GdkWindow *win)
|
||||||
|
{
|
||||||
|
Im *im;
|
||||||
|
|
||||||
|
im = (Im*)ctx;
|
||||||
|
if(win != NULL && gdk_window_is_destroyed(win))
|
||||||
|
win = NULL;
|
||||||
|
if(parentim->set_client_window != NULL)
|
||||||
|
parentim->set_client_window(ctx, win);
|
||||||
|
if(im->win == win)
|
||||||
|
return;
|
||||||
|
if(win != NULL)
|
||||||
|
g_object_ref(win);
|
||||||
|
dropwindow(im);
|
||||||
|
im->win = win;
|
||||||
|
if(sendcaret(im) < 0)
|
||||||
|
srvclose(im);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
setcursorlocation(GtkIMContext *ctx, GdkRectangle *area)
|
||||||
|
{
|
||||||
|
Im *im;
|
||||||
|
|
||||||
|
im = (Im*)ctx;
|
||||||
|
if(parentim->set_cursor_location != NULL)
|
||||||
|
parentim->set_cursor_location(ctx, area);
|
||||||
|
if(im->win != NULL && gdk_window_is_destroyed(im->win))
|
||||||
|
dropwindow(im);
|
||||||
|
if(area == NULL || im->win == NULL)
|
||||||
|
im->cursorvalid = 0;
|
||||||
|
else{
|
||||||
|
im->cursor = *area;
|
||||||
|
im->cursorvalid = 1;
|
||||||
|
}
|
||||||
|
if(sendcaret(im) < 0)
|
||||||
|
srvclose(im);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
isprivate(GtkInputPurpose purpose)
|
||||||
|
{
|
||||||
|
return purpose == GTK_INPUT_PURPOSE_PASSWORD ||
|
||||||
|
purpose == GTK_INPUT_PURPOSE_PIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
purposechanged(GObject *obj, GParamSpec *pspec, gpointer data)
|
||||||
|
{
|
||||||
|
Im *im;
|
||||||
|
GtkInputPurpose purpose;
|
||||||
|
int private;
|
||||||
|
|
||||||
|
(void)pspec;
|
||||||
|
(void)data;
|
||||||
|
im = (Im*)obj;
|
||||||
|
g_object_get(obj, "input-purpose", &purpose, NULL);
|
||||||
|
private = isprivate(purpose);
|
||||||
|
if(private == im->private)
|
||||||
|
return;
|
||||||
|
im->private = private;
|
||||||
|
if(private)
|
||||||
|
reset(GTK_IM_CONTEXT(im));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dispose(GObject *obj)
|
||||||
{
|
{
|
||||||
Im *im;
|
Im *im;
|
||||||
|
|
||||||
im = (Im*)obj;
|
im = (Im*)obj;
|
||||||
if(im->fd >= 0)
|
srvdrop(im, 0);
|
||||||
close(im->fd);
|
dropwindow(im);
|
||||||
G_OBJECT_CLASS(g_type_class_peek_parent(G_OBJECT_GET_CLASS(obj)))->finalize(obj);
|
parentobject->dispose(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
init(Im *im)
|
init(Im *im)
|
||||||
{
|
{
|
||||||
|
GtkInputPurpose purpose;
|
||||||
|
|
||||||
im->fd = -1;
|
im->fd = -1;
|
||||||
|
im->usepreedit = 1;
|
||||||
im->pre[0] = '\0';
|
im->pre[0] = '\0';
|
||||||
im->prelen = 0;
|
g_object_get(im, "input-purpose", &purpose, NULL);
|
||||||
|
im->private = isprivate(purpose);
|
||||||
|
g_signal_connect(im, "notify::input-purpose",
|
||||||
|
G_CALLBACK(purposechanged), NULL);
|
||||||
|
g_signal_connect(im, "commit", G_CALLBACK(simplecommit), im);
|
||||||
|
g_signal_connect(im, "preedit-end", G_CALLBACK(simpleend), im);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -238,11 +550,16 @@ classinit(ImClass *klass)
|
|||||||
|
|
||||||
ic = GTK_IM_CONTEXT_CLASS(klass);
|
ic = GTK_IM_CONTEXT_CLASS(klass);
|
||||||
oc = G_OBJECT_CLASS(klass);
|
oc = G_OBJECT_CLASS(klass);
|
||||||
|
parentim = g_type_class_peek_parent(klass);
|
||||||
|
parentobject = G_OBJECT_CLASS(parentim);
|
||||||
ic->filter_keypress = kpress;
|
ic->filter_keypress = kpress;
|
||||||
ic->get_preedit_string = getpreedit;
|
ic->get_preedit_string = getpreedit;
|
||||||
ic->reset = reset;
|
ic->reset = reset;
|
||||||
ic->focus_out = focusout;
|
ic->focus_out = focusout;
|
||||||
oc->finalize = finalize;
|
ic->set_use_preedit = setusepreedit;
|
||||||
|
ic->set_client_window = setclientwindow;
|
||||||
|
ic->set_cursor_location = setcursorlocation;
|
||||||
|
oc->dispose = dispose;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const GtkIMContextInfo info = {
|
static const GtkIMContextInfo info = {
|
||||||
@@ -267,7 +584,8 @@ im_module_init(GTypeModule *mod)
|
|||||||
0,
|
0,
|
||||||
(GInstanceInitFunc)init,
|
(GInstanceInitFunc)init,
|
||||||
};
|
};
|
||||||
imtype = g_type_module_register_type(mod, GTK_TYPE_IM_CONTEXT, "strans-gtk", &ti, 0);
|
imtype = g_type_module_register_type(mod, GTK_TYPE_IM_CONTEXT_SIMPLE,
|
||||||
|
"strans-gtk", &ti, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
G_MODULE_EXPORT void
|
G_MODULE_EXPORT void
|
||||||
|
|||||||
168
hash.c
168
hash.c
@@ -1,168 +0,0 @@
|
|||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
|
|
||||||
enum {
|
|
||||||
Tagsize = sizeof(Hnode),
|
|
||||||
};
|
|
||||||
|
|
||||||
static uvlong
|
|
||||||
hash(Str *s)
|
|
||||||
{
|
|
||||||
uvlong h;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
h = 7;
|
|
||||||
for(i = 0; i < s->n; i++)
|
|
||||||
h = h*31 + s->r[i];
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
Hmap*
|
|
||||||
hmapalloc(int nbuckets)
|
|
||||||
{
|
|
||||||
void *store;
|
|
||||||
Hmap *h;
|
|
||||||
int nsz;
|
|
||||||
|
|
||||||
if(nbuckets < 1)
|
|
||||||
return nil;
|
|
||||||
nsz = Tagsize;
|
|
||||||
store = emalloc(sizeof(*h) + nbuckets * nsz);
|
|
||||||
h = store;
|
|
||||||
h->nbs = nbuckets;
|
|
||||||
h->nsz = nsz;
|
|
||||||
h->len = h->cap = nbuckets;
|
|
||||||
h->nodes = (uchar*)store + sizeof(*h);
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
keycmp(Hnode *n, Str *key)
|
|
||||||
{
|
|
||||||
char buf[Maxutf];
|
|
||||||
int len;
|
|
||||||
|
|
||||||
len = stoutf(key, buf, sizeof(buf));
|
|
||||||
if(n->klen != len)
|
|
||||||
return 1;
|
|
||||||
return memcmp(n->key, buf, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
Hnode*
|
|
||||||
hmapget(Hmap *h, Str *key)
|
|
||||||
{
|
|
||||||
Hnode *n;
|
|
||||||
uchar *v;
|
|
||||||
|
|
||||||
if(h == nil || key == nil || key->n < 0 || key->n > Maxrunes)
|
|
||||||
return nil;
|
|
||||||
v = h->nodes + (hash(key) % h->nbs) * h->nsz;
|
|
||||||
for(;;){
|
|
||||||
n = (Hnode*)v;
|
|
||||||
if(n->filled && keycmp(n, key) == 0)
|
|
||||||
return n;
|
|
||||||
if(n->next == 0)
|
|
||||||
break;
|
|
||||||
v = h->nodes + n->next * h->nsz;
|
|
||||||
}
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char*
|
|
||||||
sdup(Str *s, int *len)
|
|
||||||
{
|
|
||||||
char buf[Maxutf];
|
|
||||||
char *p;
|
|
||||||
int n;
|
|
||||||
|
|
||||||
n = stoutf(s, buf, sizeof(buf));
|
|
||||||
p = emalloc(n + 1);
|
|
||||||
memmove(p, buf, n);
|
|
||||||
p[n] = '\0';
|
|
||||||
*len = n;
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char*
|
|
||||||
memdup(const char *src, int n)
|
|
||||||
{
|
|
||||||
char *p;
|
|
||||||
|
|
||||||
if(n == 0)
|
|
||||||
return nil;
|
|
||||||
p = emalloc(n + 1);
|
|
||||||
memmove(p, src, n);
|
|
||||||
p[n] = '\0';
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
hmapfree(Hmap *h)
|
|
||||||
{
|
|
||||||
Hnode *n;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if(h == nil)
|
|
||||||
return;
|
|
||||||
for(i = 0; i < h->len; i++){
|
|
||||||
n = (Hnode*)(h->nodes + i * h->nsz);
|
|
||||||
if(!n->filled)
|
|
||||||
continue;
|
|
||||||
free(n->key);
|
|
||||||
free(n->val);
|
|
||||||
}
|
|
||||||
free(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
hmapset(Hmap **store, Str *key, const char *val, int vlen)
|
|
||||||
{
|
|
||||||
char *newval;
|
|
||||||
Hnode *n;
|
|
||||||
uchar *v;
|
|
||||||
Hmap *h;
|
|
||||||
int next;
|
|
||||||
vlong diff;
|
|
||||||
|
|
||||||
if(store == nil || *store == nil || key == nil ||
|
|
||||||
key->n < 0 || key->n > Maxrunes || vlen < 0 ||
|
|
||||||
(vlen > 0 && val == nil))
|
|
||||||
return;
|
|
||||||
newval = memdup(val, vlen);
|
|
||||||
h = *store;
|
|
||||||
v = h->nodes + (hash(key) % h->nbs) * h->nsz;
|
|
||||||
for(;;){
|
|
||||||
n = (Hnode*)v;
|
|
||||||
next = n->next;
|
|
||||||
if(n->filled == 0)
|
|
||||||
goto replace;
|
|
||||||
if(keycmp(n, key) == 0)
|
|
||||||
goto replace;
|
|
||||||
if(next == 0)
|
|
||||||
break;
|
|
||||||
v = h->nodes + next * h->nsz;
|
|
||||||
}
|
|
||||||
if(h->cap == h->len){
|
|
||||||
diff = v - h->nodes;
|
|
||||||
h->cap *= 2;
|
|
||||||
*store = erealloc(*store, sizeof(*h) + h->cap * h->nsz);
|
|
||||||
h = *store;
|
|
||||||
h->nodes = (uchar*)*store + sizeof(*h);
|
|
||||||
v = h->nodes + diff;
|
|
||||||
n = (Hnode*)v;
|
|
||||||
}
|
|
||||||
n->next = h->len;
|
|
||||||
memset(h->nodes + h->len * h->nsz, 0, h->nsz);
|
|
||||||
h->len++;
|
|
||||||
v = h->nodes + n->next * h->nsz;
|
|
||||||
n = (Hnode*)v;
|
|
||||||
replace:
|
|
||||||
if(n->filled == 0){
|
|
||||||
n->key = sdup(key, &n->klen);
|
|
||||||
n->filled = 1;
|
|
||||||
}
|
|
||||||
n->next = next;
|
|
||||||
free(n->val);
|
|
||||||
n->val = newval;
|
|
||||||
n->vlen = vlen;
|
|
||||||
}
|
|
||||||
347
ipc.c
347
ipc.c
@@ -1,12 +1,87 @@
|
|||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <poll.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include "ipc.h"
|
#include "ipc.h"
|
||||||
|
|
||||||
|
static int64_t
|
||||||
|
nowms(void)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t
|
||||||
|
deadline(void)
|
||||||
|
{
|
||||||
|
return nowms() + Ipcwaitms;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
waitfd(int fd, short events, int64_t until)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
int64_t left;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
pfd.fd = fd;
|
||||||
|
pfd.events = events;
|
||||||
|
for(;;){
|
||||||
|
left = until - nowms();
|
||||||
|
if(left <= 0){
|
||||||
|
errno = ETIMEDOUT;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
n = poll(&pfd, 1, left);
|
||||||
|
if(n < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if(n < 0)
|
||||||
|
return -1;
|
||||||
|
if(n == 0){
|
||||||
|
errno = ETIMEDOUT;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
readwait(int fd, void *buf, size_t n, int64_t until)
|
||||||
|
{
|
||||||
|
unsigned char *p;
|
||||||
|
ssize_t r;
|
||||||
|
|
||||||
|
p = buf;
|
||||||
|
while(n > 0){
|
||||||
|
r = recv(fd, p, n, MSG_DONTWAIT);
|
||||||
|
if(r < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){
|
||||||
|
if(waitfd(fd, POLLIN, until) < 0)
|
||||||
|
return -1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(r < 0)
|
||||||
|
return -1;
|
||||||
|
if(r == 0){
|
||||||
|
errno = ECONNRESET;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
p += r;
|
||||||
|
n -= r;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
putlen(unsigned char p[Ipclensz], size_t n)
|
putlen(unsigned char p[Ipclensz], size_t n)
|
||||||
{
|
{
|
||||||
@@ -20,14 +95,33 @@ getlen(const unsigned char p[Ipclensz])
|
|||||||
return p[0] | (p[1] << 8);
|
return p[0] | (p[1] << 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
put32(unsigned char *p, int32_t v)
|
||||||
|
{
|
||||||
|
uint32_t u;
|
||||||
|
|
||||||
|
u = v;
|
||||||
|
p[0] = u;
|
||||||
|
p[1] = u >> 8;
|
||||||
|
p[2] = u >> 16;
|
||||||
|
p[3] = u >> 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t
|
||||||
|
get32(const unsigned char *p)
|
||||||
|
{
|
||||||
|
return (int32_t)((uint32_t)p[0] |
|
||||||
|
((uint32_t)p[1] << 8) |
|
||||||
|
((uint32_t)p[2] << 16) |
|
||||||
|
((uint32_t)p[3] << 24));
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
ipcpath(char *dst, size_t cap)
|
ipcpath(char *dst, size_t cap)
|
||||||
{
|
{
|
||||||
const char *dir, *sep;
|
const char *dir, *sep;
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
if(dst == NULL || cap == 0)
|
|
||||||
return -1;
|
|
||||||
dir = getenv("XDG_RUNTIME_DIR");
|
dir = getenv("XDG_RUNTIME_DIR");
|
||||||
if(dir != NULL && dir[0] == '/'){
|
if(dir != NULL && dir[0] == '/'){
|
||||||
sep = dir[strlen(dir)-1] == '/' ? "" : "/";
|
sep = dir[strlen(dir)-1] == '/' ? "" : "/";
|
||||||
@@ -40,6 +134,11 @@ ipcpath(char *dst, size_t cap)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A nonblocking AF_UNIX connect completes at once or fails with EAGAIN
|
||||||
|
* when the daemon's backlog is full; the socket stays nonblocking because
|
||||||
|
* every transfer below polls with a deadline.
|
||||||
|
*/
|
||||||
int
|
int
|
||||||
ipcconnect(void)
|
ipcconnect(void)
|
||||||
{
|
{
|
||||||
@@ -52,15 +151,60 @@ ipcconnect(void)
|
|||||||
errno = ENAMETOOLONG;
|
errno = ENAMETOOLONG;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
|
||||||
if(fd < 0)
|
if(fd < 0)
|
||||||
return -1;
|
return -1;
|
||||||
if(connect(fd, (struct sockaddr*)&addr, sizeof addr) == 0)
|
if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0){
|
||||||
return fd;
|
e = errno;
|
||||||
e = errno;
|
close(fd);
|
||||||
close(fd);
|
errno = e;
|
||||||
errno = e;
|
return -1;
|
||||||
return -1;
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Engine key for an X keysym and its Unicode value: printable characters
|
||||||
|
* as themselves, the function keysyms 0xff00-0xffff as Kspec+offset, and
|
||||||
|
* no key at all for a modifier. The keypad keys and Shift+Tab
|
||||||
|
* (ISO_Left_Tab) fold onto their plain counterparts.
|
||||||
|
*/
|
||||||
|
uint32_t
|
||||||
|
ipckeysym(uint32_t sym, uint32_t unicode)
|
||||||
|
{
|
||||||
|
if(unicode >= ' ' && unicode != 0x7f)
|
||||||
|
return unicode;
|
||||||
|
if(sym >= 0xffe1 && sym <= 0xffee)
|
||||||
|
return 0;
|
||||||
|
switch(sym){
|
||||||
|
case 0xfe20:
|
||||||
|
case 0xff89:
|
||||||
|
return Ktab;
|
||||||
|
case 0xff8d:
|
||||||
|
return Kret;
|
||||||
|
case 0xff97:
|
||||||
|
return Kup;
|
||||||
|
case 0xff99:
|
||||||
|
return Kdown;
|
||||||
|
case 0xff9a:
|
||||||
|
return Kpgup;
|
||||||
|
case 0xff9b:
|
||||||
|
return Kpgdown;
|
||||||
|
}
|
||||||
|
if(sym >= 0xff00 && sym <= 0xffff)
|
||||||
|
return Kspec + (sym - 0xff00);
|
||||||
|
return unicode;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
ipcmod(uint32_t state)
|
||||||
|
{
|
||||||
|
uint32_t m;
|
||||||
|
|
||||||
|
m = state & Mmask;
|
||||||
|
if(state & Msupervirt)
|
||||||
|
m |= Msuper;
|
||||||
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -82,6 +226,48 @@ ipcpackreset(unsigned char req[Ipcreqsz], int want)
|
|||||||
req[0] = Ipcreqreset | (want ? Ipcreqwant : 0);
|
req[0] = Ipcreqreset | (want ? Ipcreqwant : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* An old server sees this reserved frame as a key-zero request. */
|
||||||
|
void
|
||||||
|
ipcpackcap(unsigned char req[Ipcreqsz], int want)
|
||||||
|
{
|
||||||
|
memset(req, 0, Ipcreqsz);
|
||||||
|
req[0] = Ipcext | (want ? Ipcreqwant : 0);
|
||||||
|
req[1] = Ipcversion;
|
||||||
|
req[2] = Ipcopcap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ipcpackcaret(unsigned char req[Ipccaretsz], int valid, int32_t x,
|
||||||
|
int32_t y, int32_t h)
|
||||||
|
{
|
||||||
|
memset(req, 0, Ipccaretsz);
|
||||||
|
req[0] = Ipcext;
|
||||||
|
req[1] = Ipcversion;
|
||||||
|
req[2] = Ipcopcaret;
|
||||||
|
req[3] = valid != 0;
|
||||||
|
if(valid){
|
||||||
|
put32(req + 4, x);
|
||||||
|
put32(req + 8, y);
|
||||||
|
put32(req + 12, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ipcpacksurround(unsigned char req[Ipcreqsz], size_t n)
|
||||||
|
{
|
||||||
|
memset(req, 0, Ipcreqsz);
|
||||||
|
req[0] = Ipcext;
|
||||||
|
req[1] = Ipcversion;
|
||||||
|
req[2] = Ipcopsurround;
|
||||||
|
putlen(req + 4, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
ipcsurroundlen(const unsigned char req[Ipcreqsz])
|
||||||
|
{
|
||||||
|
return getlen(req + 4);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
||||||
uint32_t *key)
|
uint32_t *key)
|
||||||
@@ -94,6 +280,45 @@ ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
|||||||
((uint32_t)req[5] << 24);
|
((uint32_t)req[5] << 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ipcreqtype(const unsigned char req[Ipcreqsz])
|
||||||
|
{
|
||||||
|
if((req[0] & Ipcext) == 0)
|
||||||
|
return Ipckey;
|
||||||
|
if(req[1] != Ipcversion)
|
||||||
|
return Ipcunknown;
|
||||||
|
switch(req[2]){
|
||||||
|
case Ipcopcap:
|
||||||
|
if((req[0] & ~(Ipcext|Ipcreqwant)) != 0 ||
|
||||||
|
req[3] != 0 || req[4] != 0 || req[5] != 0)
|
||||||
|
return Ipcunknown;
|
||||||
|
return Ipccap;
|
||||||
|
case Ipcopcaret:
|
||||||
|
if(req[0] != Ipcext || req[3] > 1)
|
||||||
|
return Ipcunknown;
|
||||||
|
return Ipccaret;
|
||||||
|
case Ipcopsurround:
|
||||||
|
if(req[0] != Ipcext || req[3] != 0 ||
|
||||||
|
getlen(req + 4) > Ipcfieldmax)
|
||||||
|
return Ipcunknown;
|
||||||
|
return Ipcsurround;
|
||||||
|
}
|
||||||
|
return Ipcunknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ipcunpackcaret(const unsigned char req[Ipccaretsz], int *valid,
|
||||||
|
int32_t *x, int32_t *y, int32_t *h)
|
||||||
|
{
|
||||||
|
if(ipcreqtype(req) != Ipccaret || get32(req + 12) < 0)
|
||||||
|
return -1;
|
||||||
|
*valid = req[3];
|
||||||
|
*x = *valid ? get32(req + 4) : 0;
|
||||||
|
*y = *valid ? get32(req + 8) : 0;
|
||||||
|
*h = *valid ? get32(req + 12) : 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
ipcreqreset(const unsigned char req[Ipcreqsz])
|
ipcreqreset(const unsigned char req[Ipcreqsz])
|
||||||
{
|
{
|
||||||
@@ -101,29 +326,25 @@ ipcreqreset(const unsigned char req[Ipcreqsz])
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
ipcpackresp(unsigned char *dst, size_t cap, int eaten,
|
ipcpackresp(unsigned char *dst, size_t cap, int eaten, int del,
|
||||||
const char *commit, size_t ncommit, const char *preedit, size_t npreedit,
|
const char *commit, size_t ncommit, const char *preedit, size_t npreedit,
|
||||||
int want)
|
int want)
|
||||||
{
|
{
|
||||||
size_t n;
|
size_t n;
|
||||||
|
|
||||||
if(ncommit > Ipcfieldmax || npreedit > Ipcfieldmax)
|
if(ncommit > Ipcfieldmax || npreedit > Ipcfieldmax ||
|
||||||
return -1;
|
del < 0 || del > 0xff)
|
||||||
if((ncommit > 0 && commit == NULL) ||
|
|
||||||
(want && npreedit > 0 && preedit == NULL))
|
|
||||||
return -1;
|
return -1;
|
||||||
n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0);
|
n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0);
|
||||||
if(dst == NULL || cap < n)
|
if(cap < n)
|
||||||
return -1;
|
return -1;
|
||||||
dst[0] = eaten != 0;
|
dst[0] = eaten != 0;
|
||||||
putlen(dst + 1, ncommit);
|
dst[1] = del;
|
||||||
if(ncommit > 0)
|
putlen(dst + 2, ncommit);
|
||||||
memcpy(dst + Ipcresphdrsz, commit, ncommit);
|
memcpy(dst + Ipcresphdrsz, commit, ncommit);
|
||||||
if(want){
|
if(want){
|
||||||
putlen(dst + Ipcresphdrsz + ncommit, npreedit);
|
putlen(dst + Ipcresphdrsz + ncommit, npreedit);
|
||||||
if(npreedit > 0)
|
memcpy(dst + Ipcresphdrsz + ncommit + Ipclensz, preedit, npreedit);
|
||||||
memcpy(dst + Ipcresphdrsz + ncommit + Ipclensz,
|
|
||||||
preedit, npreedit);
|
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
@@ -152,73 +373,79 @@ ipcsend(int fd, const void *buf, size_t n)
|
|||||||
{
|
{
|
||||||
const unsigned char *p;
|
const unsigned char *p;
|
||||||
ssize_t r;
|
ssize_t r;
|
||||||
|
int64_t until;
|
||||||
|
|
||||||
p = buf;
|
p = buf;
|
||||||
|
until = deadline();
|
||||||
while(n > 0){
|
while(n > 0){
|
||||||
r = send(fd, p, n, MSG_NOSIGNAL);
|
r = send(fd, p, n, MSG_NOSIGNAL|MSG_DONTWAIT);
|
||||||
if(r < 0 && errno == EINTR)
|
if(r < 0 && errno == EINTR)
|
||||||
continue;
|
continue;
|
||||||
if(r <= 0)
|
if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){
|
||||||
|
if(waitfd(fd, POLLOUT, until) < 0)
|
||||||
|
return -1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(r < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
if(r == 0){
|
||||||
|
errno = EPIPE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
p += r;
|
p += r;
|
||||||
n -= r;
|
n -= r;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Reads a length-prefixed field into dst[Ipcfieldmax+1]. */
|
||||||
static int
|
static int
|
||||||
readfield(int fd, size_t n, char *dst, size_t cap)
|
readfield(int fd, char *dst, int64_t until)
|
||||||
{
|
{
|
||||||
unsigned char discard[128];
|
unsigned char len[Ipclensz];
|
||||||
size_t keep, part;
|
size_t n;
|
||||||
|
|
||||||
if(cap > 0 && dst == NULL)
|
if(readwait(fd, len, sizeof len, until) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
keep = 0;
|
n = getlen(len);
|
||||||
if(cap > 0)
|
if(n > Ipcfieldmax){
|
||||||
keep = n >= cap ? cap - 1 : n;
|
errno = EPROTO;
|
||||||
if(keep > 0 && ipcreadn(fd, dst, keep) < 0)
|
|
||||||
return -1;
|
return -1;
|
||||||
if(cap > 0)
|
|
||||||
dst[keep] = '\0';
|
|
||||||
n -= keep;
|
|
||||||
while(n > 0){
|
|
||||||
part = n < sizeof discard ? n : sizeof discard;
|
|
||||||
if(ipcreadn(fd, discard, part) < 0)
|
|
||||||
return -1;
|
|
||||||
n -= part;
|
|
||||||
}
|
}
|
||||||
return 0;
|
if(readwait(fd, dst, n, until) < 0)
|
||||||
|
return -1;
|
||||||
|
dst[n] = '\0';
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
ipcreadresp(int fd, int want, char *commit, size_t ccap,
|
ipcreadresp(int fd, int want, char *commit, char *preedit, Ipcresp *resp)
|
||||||
char *preedit, size_t pcap, Ipcresp *resp)
|
|
||||||
{
|
{
|
||||||
unsigned char hdr[Ipcresphdrsz], npreedit[Ipclensz];
|
unsigned char head[2];
|
||||||
|
int64_t until;
|
||||||
|
int n;
|
||||||
|
|
||||||
if(resp == NULL || (ccap > 0 && commit == NULL) ||
|
commit[0] = '\0';
|
||||||
(pcap > 0 && preedit == NULL))
|
preedit[0] = '\0';
|
||||||
return -1;
|
|
||||||
if(ccap > 0)
|
|
||||||
commit[0] = '\0';
|
|
||||||
if(pcap > 0)
|
|
||||||
preedit[0] = '\0';
|
|
||||||
memset(resp, 0, sizeof *resp);
|
memset(resp, 0, sizeof *resp);
|
||||||
if(ipcreadn(fd, hdr, sizeof hdr) < 0)
|
until = deadline();
|
||||||
|
if(readwait(fd, head, sizeof head, until) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
resp->eaten = hdr[0] != 0;
|
if(head[0] > 1){
|
||||||
resp->ncommit = getlen(hdr + 1);
|
errno = EPROTO;
|
||||||
if(resp->ncommit > Ipcfieldmax)
|
|
||||||
return -1;
|
return -1;
|
||||||
if(readfield(fd, resp->ncommit, commit, ccap) < 0)
|
}
|
||||||
|
resp->eaten = head[0];
|
||||||
|
resp->del = head[1];
|
||||||
|
n = readfield(fd, commit, until);
|
||||||
|
if(n < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
resp->commitlen = n;
|
||||||
if(!want)
|
if(!want)
|
||||||
return 0;
|
return 0;
|
||||||
if(ipcreadn(fd, npreedit, sizeof npreedit) < 0)
|
n = readfield(fd, preedit, until);
|
||||||
|
if(n < 0)
|
||||||
return -1;
|
return -1;
|
||||||
resp->npreedit = getlen(npreedit);
|
resp->preeditlen = n;
|
||||||
if(resp->npreedit > Ipcfieldmax)
|
return 0;
|
||||||
return -1;
|
|
||||||
return readfield(fd, resp->npreedit, preedit, pcap);
|
|
||||||
}
|
}
|
||||||
|
|||||||
72
ipc.h
72
ipc.h
@@ -2,21 +2,40 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Request: [flags, modifiers, key byte 0, ..., key byte 3].
|
* Key request: [flags, modifiers, key byte 0, ..., key byte 3].
|
||||||
* Flags request preedit and distinguish lifecycle reset from physical Escape.
|
* The want flag asks for the preedit text: the client then draws it and
|
||||||
|
* the popup does not. The reset flag is a lifecycle reset, not Escape.
|
||||||
* The server identifies the connection as the engine owner; that identity is
|
* The server identifies the connection as the engine owner; that identity is
|
||||||
* not sent on the wire.
|
* not sent on the wire.
|
||||||
* Only Mmask modifier bits are retained.
|
* Only Mmask modifier bits are retained.
|
||||||
* Response: [eaten, commit-length-low, commit-length-high, commit...],
|
* Response: [eaten, take-back, commit-length-low, commit-length-high,
|
||||||
* followed, when requested, by
|
* commit...], followed, when requested, by
|
||||||
* [preedit-length-low, preedit-length-high, preedit...].
|
* [preedit-length-low, preedit-length-high, preedit...].
|
||||||
* Lengths are little-endian byte counts; fields are at most Ipcfieldmax bytes.
|
* Take-back is the count of runes of the client's own text, just before the
|
||||||
|
* cursor, to remove before inserting the commit.
|
||||||
|
* Lengths are little-endian byte counts; fields are at most Ipcfieldmax bytes,
|
||||||
|
* so callers read them into char[Ipcfieldmax+1].
|
||||||
|
*
|
||||||
|
* Capability control is six bytes: [0x80|want, version, 0, 0, 0, 0]; the
|
||||||
|
* server always answers it eaten, which tells the client that extension
|
||||||
|
* frames are understood.
|
||||||
|
* Caret control is sixteen bytes: [0x80, version, 1, valid, x, y, h],
|
||||||
|
* with the signed coordinates and height encoded as little-endian 32-bit words.
|
||||||
|
* Surrounding-text control is six bytes, [0x80, version, 2, 0, length-low,
|
||||||
|
* length-high], and then that many bytes of the text just before the cursor.
|
||||||
|
* It changes what rides along with the requests that follow and is not
|
||||||
|
* answered.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Ipcreqwant = 1<<0,
|
Ipcreqwant = 1<<0,
|
||||||
Ipcreqreset = 1<<1,
|
Ipcreqreset = 1<<1,
|
||||||
|
Ipcext = 1<<7,
|
||||||
|
Ipcversion = 2,
|
||||||
|
Ipcopcap = 0,
|
||||||
|
Ipcopcaret = 1,
|
||||||
|
Ipcopsurround = 2,
|
||||||
|
|
||||||
Kspec = 0x110000,
|
Kspec = 0x110000,
|
||||||
Kback = Kspec|0x08,
|
Kback = Kspec|0x08,
|
||||||
@@ -25,37 +44,66 @@ enum
|
|||||||
Kesc = Kspec|0x1b,
|
Kesc = Kspec|0x1b,
|
||||||
Kup = Kspec|0x52,
|
Kup = Kspec|0x52,
|
||||||
Kdown = Kspec|0x54,
|
Kdown = Kspec|0x54,
|
||||||
Kmodfirst = Kspec|0xe1,
|
Kpgup = Kspec|0x55,
|
||||||
Kmodlast = Kspec|0xee,
|
Kpgdown = Kspec|0x56,
|
||||||
|
Khangul = Kspec|0x31,
|
||||||
|
Khanja = Kspec|0x34,
|
||||||
|
Kmuhenkan = Kspec|0x22,
|
||||||
|
Khenkan = Kspec|0x23,
|
||||||
|
Kkana = Kspec|0x27,
|
||||||
|
Kzenkaku = Kspec|0x2a,
|
||||||
|
|
||||||
|
/* X core modifier bits; GDK and IBus also flag Super at bit 26. */
|
||||||
Mshift = 1<<0,
|
Mshift = 1<<0,
|
||||||
Mctrl = 1<<2,
|
Mctrl = 1<<2,
|
||||||
Malt = 1<<3,
|
Malt = 1<<3,
|
||||||
Msuper = 1<<6,
|
Msuper = 1<<6,
|
||||||
Mmask = Mshift|Mctrl|Malt|Msuper,
|
Mmask = Mshift|Mctrl|Malt|Msuper,
|
||||||
|
Msupervirt = 1<<26,
|
||||||
|
|
||||||
Ipcreqsz = 6,
|
Ipcreqsz = 6,
|
||||||
|
Ipccaretsz = 16,
|
||||||
Ipclensz = 2,
|
Ipclensz = 2,
|
||||||
Ipcresphdrsz = 1 + Ipclensz,
|
Ipcresphdrsz = 2 + Ipclensz,
|
||||||
Ipcfieldmax = 256,
|
Ipcfieldmax = 256,
|
||||||
Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax,
|
Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax,
|
||||||
|
Ipcwaitms = 250,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Ipcunknown = -1,
|
||||||
|
Ipckey,
|
||||||
|
Ipccap,
|
||||||
|
Ipccaret,
|
||||||
|
Ipcsurround,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Ipcresp Ipcresp;
|
typedef struct Ipcresp Ipcresp;
|
||||||
struct Ipcresp
|
struct Ipcresp
|
||||||
{
|
{
|
||||||
int eaten;
|
int eaten;
|
||||||
size_t ncommit;
|
int del; /* runes of the client's own text to take back first */
|
||||||
size_t npreedit;
|
/* Bytes copied to each caller buffer, excluding its trailing NUL. */
|
||||||
|
size_t commitlen;
|
||||||
|
size_t preeditlen;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
uint32_t ipckeysym(uint32_t, uint32_t);
|
||||||
|
uint32_t ipcmod(uint32_t);
|
||||||
void ipcpackreq(unsigned char[Ipcreqsz], int, uint32_t, uint32_t);
|
void ipcpackreq(unsigned char[Ipcreqsz], int, uint32_t, uint32_t);
|
||||||
void ipcpackreset(unsigned char[Ipcreqsz], int);
|
void ipcpackreset(unsigned char[Ipcreqsz], int);
|
||||||
|
void ipcpackcap(unsigned char[Ipcreqsz], int);
|
||||||
|
void ipcpackcaret(unsigned char[Ipccaretsz], int, int32_t, int32_t, int32_t);
|
||||||
|
void ipcpacksurround(unsigned char[Ipcreqsz], size_t);
|
||||||
|
size_t ipcsurroundlen(const unsigned char[Ipcreqsz]);
|
||||||
void ipcunpackreq(const unsigned char[Ipcreqsz], int*, uint32_t*, uint32_t*);
|
void ipcunpackreq(const unsigned char[Ipcreqsz], int*, uint32_t*, uint32_t*);
|
||||||
|
int ipcunpackcaret(const unsigned char[Ipccaretsz], int*, int32_t*, int32_t*, int32_t*);
|
||||||
|
int ipcreqtype(const unsigned char[Ipcreqsz]);
|
||||||
int ipcreqreset(const unsigned char[Ipcreqsz]);
|
int ipcreqreset(const unsigned char[Ipcreqsz]);
|
||||||
int ipcpackresp(unsigned char*, size_t, int, const char*, size_t, const char*, size_t, int);
|
int ipcpackresp(unsigned char*, size_t, int, int, const char*, size_t, const char*, size_t, int);
|
||||||
int ipcreadn(int, void*, size_t);
|
int ipcreadn(int, void*, size_t);
|
||||||
int ipcsend(int, const void*, size_t);
|
int ipcsend(int, const void*, size_t);
|
||||||
int ipcreadresp(int, int, char*, size_t, char*, size_t, Ipcresp*);
|
int ipcreadresp(int, int, char*, char*, Ipcresp*);
|
||||||
int ipcpath(char*, size_t);
|
int ipcpath(char*, size_t);
|
||||||
int ipcconnect(void);
|
int ipcconnect(void);
|
||||||
|
|||||||
95
ko.c
95
ko.c
@@ -4,8 +4,6 @@
|
|||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Sbase = 0xAC00,
|
Sbase = 0xAC00,
|
||||||
Jbase = 0x3131,
|
|
||||||
Jrange = 51,
|
|
||||||
Ncho = 19,
|
Ncho = 19,
|
||||||
Njung = 21,
|
Njung = 21,
|
||||||
Njong = 28,
|
Njong = 28,
|
||||||
@@ -35,41 +33,21 @@ static Rune jong[] = {
|
|||||||
L'ㅌ', L'ㅍ', L'ㅎ',
|
L'ㅌ', L'ㅍ', L'ㅎ',
|
||||||
};
|
};
|
||||||
|
|
||||||
static int choidx[Jrange] = {
|
/* A jamo's index in cho, jung, or jong, or -1; jong[0] is no jong. */
|
||||||
[L'ㄱ'-Jbase] = 1, [L'ㄲ'-Jbase] = 2, [L'ㄴ'-Jbase] = 3,
|
static int
|
||||||
[L'ㄷ'-Jbase] = 4, [L'ㄸ'-Jbase] = 5, [L'ㄹ'-Jbase] = 6,
|
idx(Rune *t, int n, Rune r)
|
||||||
[L'ㅁ'-Jbase] = 7, [L'ㅂ'-Jbase] = 8, [L'ㅃ'-Jbase] = 9,
|
{
|
||||||
[L'ㅅ'-Jbase] = 10, [L'ㅆ'-Jbase] = 11, [L'ㅇ'-Jbase] = 12,
|
int i;
|
||||||
[L'ㅈ'-Jbase] = 13, [L'ㅉ'-Jbase] = 14, [L'ㅊ'-Jbase] = 15,
|
|
||||||
[L'ㅋ'-Jbase] = 16, [L'ㅌ'-Jbase] = 17, [L'ㅍ'-Jbase] = 18,
|
|
||||||
[L'ㅎ'-Jbase] = 19,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int jungidx[Jrange] = {
|
for(i = 0; i < n; i++)
|
||||||
[L'ㅏ'-Jbase] = 1, [L'ㅐ'-Jbase] = 2, [L'ㅑ'-Jbase] = 3,
|
if(t[i] == r)
|
||||||
[L'ㅒ'-Jbase] = 4, [L'ㅓ'-Jbase] = 5, [L'ㅔ'-Jbase] = 6,
|
return i;
|
||||||
[L'ㅕ'-Jbase] = 7, [L'ㅖ'-Jbase] = 8, [L'ㅗ'-Jbase] = 9,
|
return -1;
|
||||||
[L'ㅘ'-Jbase] = 10, [L'ㅙ'-Jbase] = 11, [L'ㅚ'-Jbase] = 12,
|
}
|
||||||
[L'ㅛ'-Jbase] = 13, [L'ㅜ'-Jbase] = 14, [L'ㅝ'-Jbase] = 15,
|
|
||||||
[L'ㅞ'-Jbase] = 16, [L'ㅟ'-Jbase] = 17, [L'ㅠ'-Jbase] = 18,
|
|
||||||
[L'ㅡ'-Jbase] = 19, [L'ㅢ'-Jbase] = 20, [L'ㅣ'-Jbase] = 21,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int jongidx[Jrange] = {
|
#define Choidx(r) idx(cho, Ncho, r)
|
||||||
[L'ㄱ'-Jbase] = 2, [L'ㄲ'-Jbase] = 3, [L'ㄳ'-Jbase] = 4,
|
#define Jungidx(r) idx(jung, Njung, r)
|
||||||
[L'ㄴ'-Jbase] = 5, [L'ㄵ'-Jbase] = 6, [L'ㄶ'-Jbase] = 7,
|
#define Jongidx(r) idx(jong, Njong, r)
|
||||||
[L'ㄷ'-Jbase] = 8, [L'ㄹ'-Jbase] = 9, [L'ㄺ'-Jbase] = 10,
|
|
||||||
[L'ㄻ'-Jbase] = 11, [L'ㄼ'-Jbase] = 12, [L'ㄽ'-Jbase] = 13,
|
|
||||||
[L'ㄾ'-Jbase] = 14, [L'ㄿ'-Jbase] = 15, [L'ㅀ'-Jbase] = 16,
|
|
||||||
[L'ㅁ'-Jbase] = 17, [L'ㅂ'-Jbase] = 18, [L'ㅄ'-Jbase] = 19,
|
|
||||||
[L'ㅅ'-Jbase] = 20, [L'ㅆ'-Jbase] = 21, [L'ㅇ'-Jbase] = 22,
|
|
||||||
[L'ㅈ'-Jbase] = 23, [L'ㅊ'-Jbase] = 24, [L'ㅋ'-Jbase] = 25,
|
|
||||||
[L'ㅌ'-Jbase] = 26, [L'ㅍ'-Jbase] = 27, [L'ㅎ'-Jbase] = 28,
|
|
||||||
};
|
|
||||||
|
|
||||||
#define Choidx(r) (choidx[(r) - Jbase] - 1)
|
|
||||||
#define Jungidx(r) (jungidx[(r) - Jbase] - 1)
|
|
||||||
#define Jongidx(r) (jongidx[(r) - Jbase] - 1)
|
|
||||||
|
|
||||||
static Rune jamomap[128] = {
|
static Rune jamomap[128] = {
|
||||||
['r'] = L'ㄱ', ['R'] = L'ㄲ',
|
['r'] = L'ㄱ', ['R'] = L'ㄲ',
|
||||||
@@ -189,6 +167,10 @@ issyl(Rune r)
|
|||||||
return r >= Sbase && r < Sbase + Ncho*Njung*Njong;
|
return r >= Sbase && r < Sbase + Ncho*Njung*Njong;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The pending text is at most one syllable; a key either extends it,
|
||||||
|
* commits it and starts another, or commits it and passes through.
|
||||||
|
*/
|
||||||
Emit
|
Emit
|
||||||
transko(Im *im, Rune c)
|
transko(Im *im, Rune c)
|
||||||
{
|
{
|
||||||
@@ -199,10 +181,7 @@ transko(Im *im, Rune c)
|
|||||||
memset(&e, 0, sizeof e);
|
memset(&e, 0, sizeof e);
|
||||||
jm = keytojamo(c);
|
jm = keytojamo(c);
|
||||||
if(jm == 0){
|
if(jm == 0){
|
||||||
if(im->pre.n > 0){
|
e.s = im->pre;
|
||||||
e.s = im->pre;
|
|
||||||
sclear(&im->pre);
|
|
||||||
}
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,17 +196,26 @@ transko(Im *im, Rune c)
|
|||||||
ci = Choidx(last);
|
ci = Choidx(last);
|
||||||
ji = Jungidx(jm);
|
ji = Jungidx(jm);
|
||||||
if(ci >= 0 && ji >= 0){
|
if(ci >= 0 && ji >= 0){
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&e.next, compose(ci, ji, 0));
|
sputr(&e.next, compose(ci, ji, 0));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
if(Jungidx(last) >= 0 && ji >= 0){
|
/* A vowel typed first is reordered under its consonant. */
|
||||||
comb = combine(cvow, nelem(cvow), last, jm);
|
if(Jungidx(last) >= 0 && Choidx(jm) >= 0){
|
||||||
if(comb){
|
sputr(&e.next, compose(Choidx(jm), Jungidx(last), 0));
|
||||||
spopr(&im->pre);
|
return e;
|
||||||
sputr(&e.next, comb);
|
}
|
||||||
return e;
|
/* Two vowels or two consonants may be one jamo, ㅘ or ㄳ. */
|
||||||
}
|
comb = combine(cvow, nelem(cvow), last, jm);
|
||||||
|
if(comb == 0)
|
||||||
|
comb = combine(cjong, nelem(cjong), last, jm);
|
||||||
|
if(comb){
|
||||||
|
sputr(&e.next, comb);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
if(ji >= 0 && splitpair(cjong, nelem(cjong), last, &stay, &next)){
|
||||||
|
sputr(&e.s, stay);
|
||||||
|
sputr(&e.next, compose(Choidx(next), ji, 0));
|
||||||
|
return e;
|
||||||
}
|
}
|
||||||
e.s = im->pre;
|
e.s = im->pre;
|
||||||
sputr(&e.next, jm);
|
sputr(&e.next, jm);
|
||||||
@@ -239,7 +227,6 @@ transko(Im *im, Rune c)
|
|||||||
if(joi == 0){
|
if(joi == 0){
|
||||||
ni = Jongidx(jm);
|
ni = Jongidx(jm);
|
||||||
if(ni > 0){
|
if(ni > 0){
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&e.next, compose(ci, ji, ni));
|
sputr(&e.next, compose(ci, ji, ni));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -248,7 +235,6 @@ transko(Im *im, Rune c)
|
|||||||
comb = combine(cvow, nelem(cvow), jung[ji], jm);
|
comb = combine(cvow, nelem(cvow), jung[ji], jm);
|
||||||
if(comb){
|
if(comb){
|
||||||
ni = Jungidx(comb);
|
ni = Jungidx(comb);
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&e.next, compose(ci, ni, 0));
|
sputr(&e.next, compose(ci, ni, 0));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -260,7 +246,6 @@ transko(Im *im, Rune c)
|
|||||||
if(comb){
|
if(comb){
|
||||||
ni = Jongidx(comb);
|
ni = Jongidx(comb);
|
||||||
if(ni > 0){
|
if(ni > 0){
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&e.next, compose(ci, ji, ni));
|
sputr(&e.next, compose(ci, ji, ni));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -271,14 +256,12 @@ transko(Im *im, Rune c)
|
|||||||
if(splitpair(cjong, nelem(cjong), jc, &stay, &next)){
|
if(splitpair(cjong, nelem(cjong), jc, &stay, &next)){
|
||||||
si = Jongidx(stay);
|
si = Jongidx(stay);
|
||||||
ni = Choidx(next);
|
ni = Choidx(next);
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&e.s, compose(ci, ji, si));
|
sputr(&e.s, compose(ci, ji, si));
|
||||||
sputr(&e.next, compose(ni, vi, 0));
|
sputr(&e.next, compose(ni, vi, 0));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
ni = Choidx(jc);
|
ni = Choidx(jc);
|
||||||
if(ni >= 0){
|
if(ni >= 0){
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&e.s, compose(ci, ji, 0));
|
sputr(&e.s, compose(ci, ji, 0));
|
||||||
sputr(&e.next, compose(ni, vi, 0));
|
sputr(&e.next, compose(ni, vi, 0));
|
||||||
return e;
|
return e;
|
||||||
@@ -300,12 +283,10 @@ backko(Im *im)
|
|||||||
|
|
||||||
last = slastr(&im->pre);
|
last = slastr(&im->pre);
|
||||||
if(!issyl(last)){
|
if(!issyl(last)){
|
||||||
if(splitpair(cvow, nelem(cvow), last, &stay, &next)){
|
|
||||||
spopr(&im->pre);
|
|
||||||
sputr(&im->pre, stay);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
spopr(&im->pre);
|
spopr(&im->pre);
|
||||||
|
if(splitpair(cvow, nelem(cvow), last, &stay, &next) ||
|
||||||
|
splitpair(cjong, nelem(cjong), last, &stay, &next))
|
||||||
|
sputr(&im->pre, stay);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
decompose(last, &c, &j, &jo);
|
decompose(last, &c, &j, &jo);
|
||||||
|
|||||||
42
main.c
42
main.c
@@ -3,15 +3,11 @@
|
|||||||
|
|
||||||
Channel *drawc;
|
Channel *drawc;
|
||||||
Channel *keyc;
|
Channel *keyc;
|
||||||
Channel *dictreqc;
|
|
||||||
Channel *dictresc;
|
|
||||||
char **fontfiles;
|
|
||||||
int nfontfiles;
|
|
||||||
|
|
||||||
void
|
void
|
||||||
usage(void)
|
usage(void)
|
||||||
{
|
{
|
||||||
fprint(2, "usage: strans mapdir fontfile [fontfile ...]\n");
|
fprint(2, "usage: strans mapdir\n");
|
||||||
threadexitsall("usage");
|
threadexitsall("usage");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,25 +47,31 @@ erealloc(void *p, ulong n)
|
|||||||
void
|
void
|
||||||
threadmain(int argc, char **argv)
|
threadmain(int argc, char **argv)
|
||||||
{
|
{
|
||||||
if(argc < 3 || argc > Maxfonts + 2)
|
char *display, *scale;
|
||||||
|
|
||||||
|
if(argc != 2)
|
||||||
usage();
|
usage();
|
||||||
|
|
||||||
fontfiles = argv + 2;
|
/* Whichever popup runs is sized from it, and setfont reads Fontsz. */
|
||||||
nfontfiles = argc - 2;
|
scale = getenv("GDK_SCALE");
|
||||||
|
if(scale != nil && atoi(scale) > 1)
|
||||||
|
popupscale = min(atoi(scale), 4);
|
||||||
drawc = chancreate(sizeof(Drawcmd), 4);
|
drawc = chancreate(sizeof(Drawcmd), 4);
|
||||||
keyc = chancreate(sizeof(Keyreq), 0);
|
keyc = chancreate(sizeof(Keyreq), 0);
|
||||||
dictreqc = chancreate(sizeof(Dictreq), 4);
|
langinit(argv[1]);
|
||||||
dictresc = chancreate(sizeof(Dictres), 0);
|
composeinit();
|
||||||
mapinit(argv[1]);
|
|
||||||
dictinit(argv[1]);
|
|
||||||
srvinit();
|
srvinit();
|
||||||
if(proccreate(drawthread, nil, 16384) < 0)
|
proccreate(srvthread, nil, 16384);
|
||||||
die("can't create draw worker");
|
proccreate(ibusthread, nil, 32768);
|
||||||
if(proccreate(srvthread, nil, 16384) < 0)
|
/* One popup per session: the Wayland frontend draws its own, so the
|
||||||
die("can't create server worker");
|
* X11 one and the XIM it serves stand down. */
|
||||||
if(proccreate(ibusthread, nil, 32768) < 0)
|
if(getenv("WAYLAND_DISPLAY") != nil && wlinit())
|
||||||
die("can't create IBus worker");
|
proccreate(wlthread, nil, 32768);
|
||||||
if(threadcreate(dictthread, nil, 16384) < 0)
|
else{
|
||||||
die("can't create dictionary worker");
|
proccreate(drawthread, nil, 16384);
|
||||||
|
display = getenv("DISPLAY");
|
||||||
|
if(display != nil && display[0] != '\0')
|
||||||
|
proccreate(ximthread, nil, 32768);
|
||||||
|
}
|
||||||
imthread(nil);
|
imthread(nil);
|
||||||
}
|
}
|
||||||
|
|||||||
91
map/README
91
map/README
@@ -1,42 +1,90 @@
|
|||||||
# Dictionary data
|
# Dictionary data
|
||||||
|
|
||||||
## Korean Hanja data
|
## Korean Hanja and symbol data
|
||||||
|
|
||||||
`hanja.src` is the tracked, reviewable source for Korean Hanja conversion.
|
`hanja.src` and `mssymbol.src` are the tracked, reviewable sources for what
|
||||||
Every data row is exactly one Hanja character, a tab, and one modern Hangul
|
the Hanja search converts. Every data row is a result, a tab, and its
|
||||||
syllable:
|
Hangul reading. A Hanja reads as a syllable or a word; a symbol reads as
|
||||||
|
the lone consonant a Korean keyboard's 한자 key offers it under:
|
||||||
|
|
||||||
```
|
```
|
||||||
漢 한
|
漢 한
|
||||||
|
漢字 한자
|
||||||
|
※ ㅁ
|
||||||
```
|
```
|
||||||
|
|
||||||
It contains no word rows such as `견출지` or `방학`. `mkhanja` validates this
|
`mkhanja` validates that contract and groups the rows of both sources by
|
||||||
contract and groups rows by their Hangul reading to produce the existing
|
their reading to produce the runtime dictionary. The two keyspaces do not
|
||||||
runtime dictionary format. Candidate order follows source order. The source
|
meet: a reading is either syllables or one jamo. Candidate order follows
|
||||||
keeps all retained pairs for review; the generated dictionary stores the first
|
source order. The sources keep all retained pairs for review; the
|
||||||
32 candidates per reading because that is the engine's lookup limit.
|
generated dictionary stores the first 128 candidates per reading because
|
||||||
|
that is the engine's lookup limit.
|
||||||
|
|
||||||
The source is derived from libhangul release tag `libhangul-0.2.0`. The
|
The sources are derived from libhangul release tag `libhangul-0.2.0`. The
|
||||||
annotated tag object is `20afc38922e3595ee3ed5b186f2ea05afe663763`, its
|
annotated tag object is `20afc38922e3595ee3ed5b186f2ea05afe663763`, its
|
||||||
peeled commit is `41c702f5d3581325b646ef6249f1f641b0427ae0`, and
|
peeled commit is `41c702f5d3581325b646ef6249f1f641b0427ae0`, and
|
||||||
`data/hanja/hanja.txt` has blob ID
|
`data/hanja/hanja.txt` and `data/hanja/mssymbol.txt` have blob IDs
|
||||||
`199cfd70c4b306257ac2e018714a1285f7cf0ed3`. Import and regenerate it from
|
`199cfd70c4b306257ac2e018714a1285f7cf0ed3` and
|
||||||
the repository root with:
|
`31c4e63d74293b6a759d4a2005cd1e7333746631`. Import and regenerate them
|
||||||
|
from the repository root with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl -L https://raw.githubusercontent.com/libhangul/libhangul/41c702f5d3581325b646ef6249f1f641b0427ae0/data/hanja/hanja.txt -o upstream
|
curl -L https://raw.githubusercontent.com/libhangul/libhangul/41c702f5d3581325b646ef6249f1f641b0427ae0/data/hanja/hanja.txt -o upstream
|
||||||
printf '%s %s\n' dd44dcc856cf542b1022d0f39c2e9b9f8805fdcc5923be80f04849ed97ce0996 upstream | sha256sum -c -
|
printf '%s %s\n' dd44dcc856cf542b1022d0f39c2e9b9f8805fdcc5923be80f04849ed97ce0996 upstream | sha256sum -c -
|
||||||
map/libhangul2hanja upstream >map/hanja.src
|
map/libhangul2hanja upstream >map/hanja.src
|
||||||
|
curl -L https://raw.githubusercontent.com/libhangul/libhangul/41c702f5d3581325b646ef6249f1f641b0427ae0/data/hanja/mssymbol.txt -o upstream
|
||||||
|
printf '%s %s\n' b685a4ebe2716b25eb29c42f6da6493716ecd78b604b44a33420987d21948e99 upstream | sha256sum -c -
|
||||||
|
map/libhangul2hanja upstream >map/mssymbol.src
|
||||||
map/mkhanja >map/hanja.dict
|
map/mkhanja >map/hanja.dict
|
||||||
```
|
```
|
||||||
|
|
||||||
`libhangul2hanja` retains only rows whose reading is one modern Hangul
|
`libhangul2hanja` keeps a syllable reading only when its value is Hanja the
|
||||||
syllable and whose value is one Hanja character supported by the current
|
popup can draw: U+3400–U+4DBF, U+4E00–U+9FFF, or U+F900–U+FAFF. Thus mixed
|
||||||
popup: U+3400–U+4DBF, U+4E00–U+9FFF, or U+F900–U+FAFF. Thus word readings,
|
values and supplementary-plane ideographs are omitted. It keeps a jamo
|
||||||
multi-character values, jamo readings, and supplementary-plane ideographs
|
reading, U+3131–U+314E, only when its value is one rune the popup can draw
|
||||||
are omitted. The import preserves the upstream license header and row order.
|
and a candidate row can carry — so `mssymbol.txt`'s ideographic space and
|
||||||
The retained data is BSD 3-Clause licensed by Choe Hwanjin; the complete text
|
soft hyphen are omitted, leaving 985 of its 987 rows, and a Hanja under a
|
||||||
is in `LICENSES/BSD-3-Clause-libhangul-hanja.txt`.
|
jamo reading is omitted as noise. The import preserves each upstream
|
||||||
|
license header and row order. The retained data is BSD 3-Clause licensed
|
||||||
|
by Choe Hwanjin; the complete text is in
|
||||||
|
`LICENSES/BSD-3-Clause-libhangul-hanja.txt`.
|
||||||
|
|
||||||
|
## Emoji and symbol data
|
||||||
|
|
||||||
|
`symbol.src` is hand-written: a symbol, a tab, and its ASCII aliases, kept
|
||||||
|
so that a bare `1`-`9` picks the matching superscript or subscript from a
|
||||||
|
`^` or `_` prefix search. `emoji.src` is generated: every fully-qualified
|
||||||
|
emoji of Unicode's `emoji-test.txt` except the skin-tone variants, with
|
||||||
|
its CLDR names and keywords in English, Korean, and Japanese. `mkemoji`
|
||||||
|
folds the aliases, adds each Japanese one in the other kana so a query
|
||||||
|
typed in either mode finds it, and writes one row per alias to
|
||||||
|
`emoji.dict`; the engine searches that dictionary by prefix, so the rows
|
||||||
|
carry no prefixes.
|
||||||
|
|
||||||
|
The sources are Emoji 17.0 and CLDR 48.2.0. Regenerate them from the
|
||||||
|
repository root with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -L https://www.unicode.org/Public/17.0.0/emoji/emoji-test.txt -o emoji-test.txt
|
||||||
|
printf '%s %s\n' 1d8a944f88d7952f7ef7c5167fef3c67995bcae24543949710231b03a201acda emoji-test.txt | sha256sum -c -
|
||||||
|
for l in en ko ja; do
|
||||||
|
curl -L https://raw.githubusercontent.com/unicode-org/cldr-json/48.2.0/cldr-json/cldr-annotations-full/annotations/$l/annotations.json -o $l.json
|
||||||
|
curl -L https://raw.githubusercontent.com/unicode-org/cldr-json/48.2.0/cldr-json/cldr-annotations-derived-full/annotationsDerived/$l/annotations.json -o $l-derived.json
|
||||||
|
done
|
||||||
|
map/cldr2emoji emoji-test.txt en.json en-derived.json ko.json ko-derived.json ja.json ja-derived.json >map/emoji.src
|
||||||
|
map/mkemoji >map/emoji.dict
|
||||||
|
```
|
||||||
|
|
||||||
|
The data is under the Unicode License v3; the complete text is in
|
||||||
|
`LICENSES/Unicode-3.0.txt`.
|
||||||
|
|
||||||
|
## Japanese romaji
|
||||||
|
|
||||||
|
`hira.map` and `kata.map` are Mozc's default romaji table, one row per key,
|
||||||
|
in gojūon order; `kata.map` is the same table in Katakana, and
|
||||||
|
`make verify-map` checks that the two have the same keys. A doubled
|
||||||
|
consonant, or `tch`, maps to っ and keeps the consonant pending; the engine
|
||||||
|
knows that rule, the table only lists the rows.
|
||||||
|
|
||||||
## Japanese dictionary data
|
## Japanese dictionary data
|
||||||
|
|
||||||
@@ -78,4 +126,5 @@ rejected; rewrite or omit those rows before import.
|
|||||||
|
|
||||||
`verifymap.py` checks UTF-8, row structure, unique keys, 64-rune keys and
|
`verifymap.py` checks UTF-8, row structure, unique keys, 64-rune keys and
|
||||||
values, canonical candidate spacing, and duplicate dictionary candidates.
|
values, canonical candidate spacing, and duplicate dictionary candidates.
|
||||||
Pass it the exact `.map` and `.dict` files installed by the build.
|
Pass it the exact `.map` and `.dict` files used for runtime or validation.
|
||||||
|
The normal build does not install map data.
|
||||||
|
|||||||
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())
|
||||||
19134
map/emoji.dict
19134
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
187337
map/hanja.dict
187337
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
367
map/hira.map
367
map/hira.map
@@ -1,170 +1,321 @@
|
|||||||
a あ
|
a あ
|
||||||
- ー
|
|
||||||
i い
|
i い
|
||||||
u う
|
u う
|
||||||
e え
|
e え
|
||||||
o お
|
o お
|
||||||
ka か
|
|
||||||
ga が
|
|
||||||
ki き
|
|
||||||
kya きゃ
|
kya きゃ
|
||||||
|
kyi きぃ
|
||||||
kyu きゅ
|
kyu きゅ
|
||||||
|
kye きぇ
|
||||||
kyo きょ
|
kyo きょ
|
||||||
gi ぎ
|
ka か
|
||||||
gya ぎゃ
|
ki き
|
||||||
gyu ぎゅ
|
|
||||||
gyo ぎょ
|
|
||||||
ku く
|
ku く
|
||||||
gu ぐ
|
|
||||||
ke け
|
ke け
|
||||||
ge げ
|
|
||||||
ko こ
|
ko こ
|
||||||
|
kwa くぁ
|
||||||
|
kwi くぃ
|
||||||
|
kwu くぅ
|
||||||
|
kwe くぇ
|
||||||
|
kwo くぉ
|
||||||
|
gya ぎゃ
|
||||||
|
gyi ぎぃ
|
||||||
|
gyu ぎゅ
|
||||||
|
gye ぎぇ
|
||||||
|
gyo ぎょ
|
||||||
|
ga が
|
||||||
|
gi ぎ
|
||||||
|
gu ぐ
|
||||||
|
ge げ
|
||||||
go ご
|
go ご
|
||||||
sa さ
|
gwa ぐぁ
|
||||||
za ざ
|
gwi ぐぃ
|
||||||
si し
|
gwu ぐぅ
|
||||||
shi し
|
gwe ぐぇ
|
||||||
|
gwo ぐぉ
|
||||||
|
sya しゃ
|
||||||
|
syi しぃ
|
||||||
|
syu しゅ
|
||||||
|
sye しぇ
|
||||||
|
syo しょ
|
||||||
sha しゃ
|
sha しゃ
|
||||||
|
shi し
|
||||||
shu しゅ
|
shu しゅ
|
||||||
she しぇ
|
she しぇ
|
||||||
sho しょ
|
sho しょ
|
||||||
syo しょ
|
sa さ
|
||||||
zi ぢ
|
si し
|
||||||
|
su す
|
||||||
|
se せ
|
||||||
|
so そ
|
||||||
|
swa すぁ
|
||||||
|
swi すぃ
|
||||||
|
swu すぅ
|
||||||
|
swe すぇ
|
||||||
|
swo すぉ
|
||||||
|
z/ ・
|
||||||
|
z. …
|
||||||
|
z, ‥
|
||||||
|
zh ←
|
||||||
|
zj ↓
|
||||||
|
zk ↑
|
||||||
|
zl →
|
||||||
|
z- 〜
|
||||||
|
z[ 『
|
||||||
|
z] 』
|
||||||
|
zya じゃ
|
||||||
|
zyi じぃ
|
||||||
|
zyu じゅ
|
||||||
|
zye じぇ
|
||||||
|
zyo じょ
|
||||||
|
zwa ずぁ
|
||||||
|
zwi ずぃ
|
||||||
|
zwu ずぅ
|
||||||
|
zwe ずぇ
|
||||||
|
zwo ずぉ
|
||||||
|
za ざ
|
||||||
|
zi じ
|
||||||
|
zu ず
|
||||||
|
ze ぜ
|
||||||
|
zo ぞ
|
||||||
ja じゃ
|
ja じゃ
|
||||||
|
ji じ
|
||||||
ju じゅ
|
ju じゅ
|
||||||
je じぇ
|
je じぇ
|
||||||
jo じょ
|
jo じょ
|
||||||
su す
|
jya じゃ
|
||||||
zu ず
|
jyi じぃ
|
||||||
se せ
|
jyu じゅ
|
||||||
ze ぜ
|
jye じぇ
|
||||||
so そ
|
jyo じょ
|
||||||
zo ぞ
|
tya ちゃ
|
||||||
ta た
|
tyi ちぃ
|
||||||
da だ
|
|
||||||
ti ち
|
|
||||||
chi ち
|
|
||||||
cha ちゃ
|
|
||||||
chu ちゅ
|
|
||||||
tyu ちゅ
|
tyu ちゅ
|
||||||
cho ちょ
|
tye ちぇ
|
||||||
ji じ
|
tyo ちょ
|
||||||
|
tsa つぁ
|
||||||
|
tsi つぃ
|
||||||
|
tse つぇ
|
||||||
|
tso つぉ
|
||||||
|
tha てゃ
|
||||||
|
thi てぃ
|
||||||
|
t'i てぃ
|
||||||
|
thu てゅ
|
||||||
|
the てぇ
|
||||||
|
tho てょ
|
||||||
|
t'yu てゅ
|
||||||
|
twa とぁ
|
||||||
|
twi とぃ
|
||||||
|
twu とぅ
|
||||||
|
twe とぇ
|
||||||
|
two とぉ
|
||||||
|
t'u とぅ
|
||||||
|
ta た
|
||||||
|
ti ち
|
||||||
tu つ
|
tu つ
|
||||||
tsu つ
|
tsu つ
|
||||||
du づ
|
|
||||||
te て
|
te て
|
||||||
de で
|
|
||||||
to と
|
to と
|
||||||
|
dya ぢゃ
|
||||||
|
dyi ぢぃ
|
||||||
|
dyu ぢゅ
|
||||||
|
dye ぢぇ
|
||||||
|
dyo ぢょ
|
||||||
|
dha でゃ
|
||||||
|
dhi でぃ
|
||||||
|
d'i でぃ
|
||||||
|
dhu でゅ
|
||||||
|
dhe でぇ
|
||||||
|
dho でょ
|
||||||
|
d'yu でゅ
|
||||||
|
dwa どぁ
|
||||||
|
dwi どぃ
|
||||||
|
dwu どぅ
|
||||||
|
dwe どぇ
|
||||||
|
dwo どぉ
|
||||||
|
d'u どぅ
|
||||||
|
da だ
|
||||||
|
di ぢ
|
||||||
|
du づ
|
||||||
|
de で
|
||||||
do ど
|
do ど
|
||||||
|
cha ちゃ
|
||||||
|
chi ち
|
||||||
|
chu ちゅ
|
||||||
|
che ちぇ
|
||||||
|
cho ちょ
|
||||||
|
cya ちゃ
|
||||||
|
cyi ちぃ
|
||||||
|
cyu ちゅ
|
||||||
|
cye ちぇ
|
||||||
|
cyo ちょ
|
||||||
|
ca か
|
||||||
|
ci し
|
||||||
|
cu く
|
||||||
|
ce せ
|
||||||
|
co こ
|
||||||
|
nya にゃ
|
||||||
|
nyi にぃ
|
||||||
|
nyu にゅ
|
||||||
|
nye にぇ
|
||||||
|
nyo にょ
|
||||||
|
n' ん
|
||||||
|
nn ん
|
||||||
|
n ん
|
||||||
na な
|
na な
|
||||||
ni に
|
ni に
|
||||||
nya にゃ
|
|
||||||
nyu にゅ
|
|
||||||
nyo にょ
|
|
||||||
nu ぬ
|
nu ぬ
|
||||||
ne ね
|
ne ね
|
||||||
no の
|
no の
|
||||||
ha は
|
|
||||||
ba ば
|
|
||||||
va ば
|
|
||||||
pa ぱ
|
|
||||||
hi ひ
|
|
||||||
hya ひゃ
|
hya ひゃ
|
||||||
|
hyi ひぃ
|
||||||
hyu ひゅ
|
hyu ひゅ
|
||||||
|
hye ひぇ
|
||||||
hyo ひょ
|
hyo ひょ
|
||||||
bi び
|
hwa ふぁ
|
||||||
bya びゃ
|
hwi ふぃ
|
||||||
byu びゅ
|
hwe ふぇ
|
||||||
byo びょ
|
hwo ふぉ
|
||||||
vi び
|
hwyu ふゅ
|
||||||
pi ぴ
|
ha は
|
||||||
pya ぴゃ
|
hi ひ
|
||||||
pyu ぴゅ
|
|
||||||
pyo ぴょ
|
|
||||||
hu ふ
|
hu ふ
|
||||||
fu ふ
|
|
||||||
bu ぶ
|
|
||||||
vu ぶ
|
|
||||||
pu ぷ
|
|
||||||
he へ
|
he へ
|
||||||
be べ
|
|
||||||
ve べ
|
|
||||||
pe ぺ
|
|
||||||
ho ほ
|
ho ほ
|
||||||
|
fa ふぁ
|
||||||
|
fi ふぃ
|
||||||
|
fu ふ
|
||||||
|
fe ふぇ
|
||||||
|
fo ふぉ
|
||||||
|
fya ふゃ
|
||||||
|
fyu ふゅ
|
||||||
|
fyo ふょ
|
||||||
|
bya びゃ
|
||||||
|
byi びぃ
|
||||||
|
byu びゅ
|
||||||
|
bye びぇ
|
||||||
|
byo びょ
|
||||||
|
ba ば
|
||||||
|
bi び
|
||||||
|
bu ぶ
|
||||||
|
be べ
|
||||||
bo ぼ
|
bo ぼ
|
||||||
vo ぼ
|
pya ぴゃ
|
||||||
|
pyi ぴぃ
|
||||||
|
pyu ぴゅ
|
||||||
|
pye ぴぇ
|
||||||
|
pyo ぴょ
|
||||||
|
pa ぱ
|
||||||
|
pi ぴ
|
||||||
|
pu ぷ
|
||||||
|
pe ぺ
|
||||||
po ぽ
|
po ぽ
|
||||||
|
mya みゃ
|
||||||
|
myi みぃ
|
||||||
|
myu みゅ
|
||||||
|
mye みぇ
|
||||||
|
myo みょ
|
||||||
ma ま
|
ma ま
|
||||||
mi み
|
mi み
|
||||||
mya みゃ
|
|
||||||
myu みゅ
|
|
||||||
myo みょ
|
|
||||||
mu む
|
mu む
|
||||||
me め
|
me め
|
||||||
mo も
|
mo も
|
||||||
|
ye いぇ
|
||||||
ya や
|
ya や
|
||||||
yu ゆ
|
yu ゆ
|
||||||
yo よ
|
yo よ
|
||||||
|
rya りゃ
|
||||||
|
ryi りぃ
|
||||||
|
ryu りゅ
|
||||||
|
rye りぇ
|
||||||
|
ryo りょ
|
||||||
ra ら
|
ra ら
|
||||||
ri り
|
ri り
|
||||||
rya りゃ
|
|
||||||
ryu りゅ
|
|
||||||
ryo りょ
|
|
||||||
ru る
|
ru る
|
||||||
re れ
|
re れ
|
||||||
ro ろ
|
ro ろ
|
||||||
|
wu う
|
||||||
|
wyi ゐ
|
||||||
|
wye ゑ
|
||||||
wa わ
|
wa わ
|
||||||
wi ゐ
|
wi うぃ
|
||||||
we ゑ
|
we うぇ
|
||||||
wo を
|
wo を
|
||||||
n ん
|
wha うぁ
|
||||||
|
whi うぃ
|
||||||
|
whu う
|
||||||
|
whe うぇ
|
||||||
|
who うぉ
|
||||||
|
va ゔぁ
|
||||||
|
vi ゔぃ
|
||||||
|
vu ゔ
|
||||||
|
ve ゔぇ
|
||||||
|
vo ゔぉ
|
||||||
|
vya ゔゃ
|
||||||
|
vyi ゔぃ
|
||||||
|
vyu ゔゅ
|
||||||
|
vye ゔぇ
|
||||||
|
vyo ゔょ
|
||||||
|
qa くぁ
|
||||||
|
qi くぃ
|
||||||
|
qu く
|
||||||
|
qe くぇ
|
||||||
|
qo くぉ
|
||||||
xn ん
|
xn ん
|
||||||
xa ぁ
|
xa ぁ
|
||||||
xi ぃ
|
xi ぃ
|
||||||
xu ぅ
|
xu ぅ
|
||||||
xe ぇ
|
xe ぇ
|
||||||
xo ぉ
|
xo ぉ
|
||||||
kka っか
|
xyi ぃ
|
||||||
kki っき
|
xye ぇ
|
||||||
kkya っきゃ
|
xka ヵ
|
||||||
kkyu っきゅ
|
xke ヶ
|
||||||
kkyo っきょ
|
xtu っ
|
||||||
kku っく
|
xtsu っ
|
||||||
kke っけ
|
xya ゃ
|
||||||
kko っこ
|
xyu ゅ
|
||||||
ssa っさ
|
xyo ょ
|
||||||
ssi っし
|
xwa ゎ
|
||||||
sshi っし
|
la ぁ
|
||||||
ssha っしゃ
|
li ぃ
|
||||||
sshu っしゅ
|
lu ぅ
|
||||||
sshe っしぇ
|
le ぇ
|
||||||
ssho っしょ
|
lo ぉ
|
||||||
ssyo っしょ
|
lyi ぃ
|
||||||
ssu っす
|
lye ぇ
|
||||||
sse っせ
|
lka ヵ
|
||||||
sso っそ
|
lke ヶ
|
||||||
tta った
|
ltu っ
|
||||||
dda っだ
|
ltsu っ
|
||||||
tti っち
|
lya ゃ
|
||||||
cchi っち
|
lyu ゅ
|
||||||
ccha っちゃ
|
lyo ょ
|
||||||
cchu っちゅ
|
lwa ゎ
|
||||||
ttyu っちゅ
|
qq っ
|
||||||
ccho っちょ
|
vv っ
|
||||||
ttu っつ
|
ll っ
|
||||||
ttsu っつ
|
xx っ
|
||||||
ddu っづ
|
kk っ
|
||||||
tte って
|
gg っ
|
||||||
dde っで
|
ss っ
|
||||||
tto っと
|
zz っ
|
||||||
ddo っど
|
jj っ
|
||||||
ppa っぱ
|
tt っ
|
||||||
ppi っぴ
|
tch っ
|
||||||
ppya っぴゃ
|
dd っ
|
||||||
ppyu っぴゅ
|
hh っ
|
||||||
ppyo っぴょ
|
ff っ
|
||||||
ppu っぷ
|
bb っ
|
||||||
ppe っぺ
|
pp っ
|
||||||
ppo っぽ
|
mm っ
|
||||||
|
yy っ
|
||||||
|
rr っ
|
||||||
|
ww っ
|
||||||
|
cc っ
|
||||||
|
- ー
|
||||||
|
~ 〜
|
||||||
. 。
|
. 。
|
||||||
, 、
|
, 、
|
||||||
|
[ 「
|
||||||
|
] 」
|
||||||
|
|||||||
347
map/kata.map
347
map/kata.map
@@ -1,142 +1,321 @@
|
|||||||
a ア
|
a ア
|
||||||
- ー
|
|
||||||
i イ
|
i イ
|
||||||
u ウ
|
u ウ
|
||||||
e エ
|
e エ
|
||||||
o オ
|
o オ
|
||||||
ka カ
|
|
||||||
ga ガ
|
|
||||||
ki キ
|
|
||||||
kya キャ
|
kya キャ
|
||||||
|
kyi キィ
|
||||||
kyu キュ
|
kyu キュ
|
||||||
|
kye キェ
|
||||||
kyo キョ
|
kyo キョ
|
||||||
gi ギ
|
ka カ
|
||||||
gya ギャ
|
ki キ
|
||||||
gyu ギュ
|
|
||||||
gyo ギョ
|
|
||||||
ku ク
|
ku ク
|
||||||
gu グ
|
|
||||||
ke ケ
|
ke ケ
|
||||||
ge ゲ
|
|
||||||
ko コ
|
ko コ
|
||||||
|
kwa クァ
|
||||||
|
kwi クィ
|
||||||
|
kwu クゥ
|
||||||
|
kwe クェ
|
||||||
|
kwo クォ
|
||||||
|
gya ギャ
|
||||||
|
gyi ギィ
|
||||||
|
gyu ギュ
|
||||||
|
gye ギェ
|
||||||
|
gyo ギョ
|
||||||
|
ga ガ
|
||||||
|
gi ギ
|
||||||
|
gu グ
|
||||||
|
ge ゲ
|
||||||
go ゴ
|
go ゴ
|
||||||
sa サ
|
gwa グァ
|
||||||
za ザ
|
gwi グィ
|
||||||
si シ
|
gwu グゥ
|
||||||
shi シ
|
gwe グェ
|
||||||
ji ジ
|
gwo グォ
|
||||||
sha シャ
|
sya シャ
|
||||||
she シェ
|
syi シィ
|
||||||
shu シュ
|
syu シュ
|
||||||
je ジェ
|
sye シェ
|
||||||
sho ショ
|
|
||||||
syo ショ
|
syo ショ
|
||||||
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 ッ
|
|
||||||
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,38 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Extract single-character Hanja readings from libhangul data."""
|
"""Extract Hanja and symbol readings from libhangul data."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import unicodedata
|
||||||
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 isjamo(s):
|
||||||
|
return len(s) == 1 and 0x3131 <= ord(s) <= 0x314E
|
||||||
|
|
||||||
|
|
||||||
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 issymbol(s):
|
||||||
|
"""One rune the popup can draw and a candidate row can carry: not a
|
||||||
|
space, which the row separates candidates with, and not a formatting
|
||||||
|
character, which would leave a blank candidate to pick."""
|
||||||
|
return (len(s) == 1 and not s.isspace() and not ishanja(s)
|
||||||
|
and not unicodedata.category(s).startswith("C"))
|
||||||
|
|
||||||
|
|
||||||
|
def keeps(reading, value):
|
||||||
|
"""A syllable reading gives Hanja; a jamo reading gives a symbol."""
|
||||||
|
if ishangul(reading):
|
||||||
|
return ishanja(value)
|
||||||
|
return isjamo(reading) and issymbol(value)
|
||||||
|
|
||||||
|
|
||||||
def extract(src, name):
|
def extract(src, name):
|
||||||
@@ -35,16 +52,16 @@ def extract(src, name):
|
|||||||
fields = line.split(":")
|
fields = line.split(":")
|
||||||
if len(fields) != 3:
|
if len(fields) != 3:
|
||||||
raise ValueError(f"{name}:{lineno}: need key:value:comment")
|
raise ValueError(f"{name}:{lineno}: need key:value:comment")
|
||||||
reading, hanja, _ = fields
|
reading, value, _ = fields
|
||||||
if not ishangul(reading) or not ishanja(hanja):
|
if not keeps(reading, value):
|
||||||
continue
|
continue
|
||||||
pair = (hanja, reading)
|
pair = (value, reading)
|
||||||
if pair in seen:
|
if pair in seen:
|
||||||
raise ValueError(f"{name}:{lineno}: duplicate Hanja reading")
|
raise ValueError(f"{name}:{lineno}: duplicate reading")
|
||||||
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 readings")
|
||||||
return comments, entries
|
return comments, entries
|
||||||
|
|
||||||
|
|
||||||
@@ -66,8 +83,8 @@ def main():
|
|||||||
print(line)
|
print(line)
|
||||||
if comments:
|
if comments:
|
||||||
print()
|
print()
|
||||||
for hanja, reading in entries:
|
for value, reading in entries:
|
||||||
print(f"{hanja}\t{reading}")
|
print(f"{value}\t{reading}")
|
||||||
except (OSError, UnicodeError, ValueError) as error:
|
except (OSError, UnicodeError, ValueError) as error:
|
||||||
print(error, file=sys.stderr)
|
print(error, file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
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)
|
||||||
|
|||||||
76
map/mkhanja
76
map/mkhanja
@@ -1,32 +1,43 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Write hanja.dict from a one-Hanja-per-row UTF-8 source."""
|
"""Write hanja.dict from result-per-row UTF-8 sources."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import unicodedata
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
SOURCE = Path(__file__).with_name("hanja.src")
|
SOURCES = [Path(__file__).with_name(name)
|
||||||
MAXCANDIDATES = 32
|
for name in ("hanja.src", "mssymbol.src")]
|
||||||
|
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 isjamo(s):
|
||||||
|
return len(s) == 1 and 0x3131 <= ord(s) <= 0x314E
|
||||||
|
|
||||||
|
|
||||||
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 issymbol(s):
|
||||||
|
"""One rune the popup can draw and a candidate row can carry: not a
|
||||||
|
space, which the row separates candidates with, and not a formatting
|
||||||
|
character, which would leave a blank candidate to pick."""
|
||||||
|
return (len(s) == 1 and not s.isspace() and not ishanja(s)
|
||||||
|
and not unicodedata.category(s).startswith("C"))
|
||||||
|
|
||||||
|
|
||||||
|
def read(path, table, seen):
|
||||||
|
"""Adds path's rows to table, keyed by reading, and returns its header."""
|
||||||
comments = []
|
comments = []
|
||||||
table = {}
|
|
||||||
seen = set()
|
|
||||||
leading = True
|
leading = True
|
||||||
|
found = False
|
||||||
with path.open(encoding="utf-8") as src:
|
with path.open(encoding="utf-8") as src:
|
||||||
for lineno, raw in enumerate(src, 1):
|
for lineno, raw in enumerate(src, 1):
|
||||||
line = raw.rstrip("\r\n")
|
line = raw.rstrip("\r\n")
|
||||||
@@ -39,31 +50,36 @@ def read(path):
|
|||||||
leading = False
|
leading = False
|
||||||
fields = line.split("\t")
|
fields = line.split("\t")
|
||||||
if len(fields) != 2:
|
if len(fields) != 2:
|
||||||
raise ValueError(f"{path}:{lineno}: need Hanja<TAB>reading")
|
raise ValueError(f"{path}:{lineno}: need result<TAB>reading")
|
||||||
hanja, reading = fields
|
value, reading = fields
|
||||||
if not ishanja(hanja):
|
if ishangul(reading):
|
||||||
raise ValueError(f"{path}:{lineno}: need one BMP Hanja character")
|
if not ishanja(value):
|
||||||
if not ishangul(reading):
|
raise ValueError(f"{path}:{lineno}: need BMP Hanja")
|
||||||
raise ValueError(f"{path}:{lineno}: need one Hangul syllable")
|
elif isjamo(reading):
|
||||||
pair = (hanja, reading)
|
if not issymbol(value):
|
||||||
|
raise ValueError(f"{path}:{lineno}: need one symbol rune")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"{path}:{lineno}: need a syllable or jamo "
|
||||||
|
"reading")
|
||||||
|
pair = (value, reading)
|
||||||
if pair in seen:
|
if pair in seen:
|
||||||
raise ValueError(f"{path}:{lineno}: duplicate Hanja reading")
|
raise ValueError(f"{path}:{lineno}: duplicate reading")
|
||||||
seen.add(pair)
|
seen.add(pair)
|
||||||
table.setdefault(reading, []).append(hanja)
|
found = True
|
||||||
if not seen:
|
table.setdefault(reading, []).append(value)
|
||||||
raise ValueError(f"{path}: no Hanja readings")
|
if not found:
|
||||||
return comments, table
|
raise ValueError(f"{path}: no readings")
|
||||||
|
return comments
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
if len(sys.argv) > 2:
|
paths = [Path(arg) for arg in sys.argv[1:]] or SOURCES
|
||||||
print(f"usage: {sys.argv[0]} [hanja.src]", file=sys.stderr)
|
table = {}
|
||||||
return 2
|
seen = set()
|
||||||
path = Path(sys.argv[1]) if len(sys.argv) == 2 else SOURCE
|
|
||||||
try:
|
try:
|
||||||
comments, table = read(path)
|
comments = [line for path in paths for line in read(path, table, seen)]
|
||||||
for line in comments:
|
for line in comments:
|
||||||
print(line)
|
print(line)
|
||||||
if comments:
|
if comments:
|
||||||
|
|||||||
@@ -55,11 +55,15 @@ def vowel2():
|
|||||||
tab = [
|
tab = [
|
||||||
("oa", "oa", "a"), ("oe", "oe", "e"), ("ai", "ai", "a"),
|
("oa", "oa", "a"), ("oe", "oe", "e"), ("ai", "ai", "a"),
|
||||||
("ao", "ao", "a"), ("au", "au", "a"), ("ay", "ay", "a"),
|
("ao", "ao", "a"), ("au", "au", "a"), ("ay", "ay", "a"),
|
||||||
("eu", "eu", "e"), ("iu", "iu", "i"), ("oi", "oi", "o"),
|
("eo", "eo", "e"), ("eu", "eu", "e"), ("ia", "ia", "i"),
|
||||||
|
("iu", "iu", "i"), ("oi", "oi", "o"), ("ua", "ua", "u"),
|
||||||
("ui", "ui", "u"), ("uy", "uy", "y"),
|
("ui", "ui", "u"), ("uy", "uy", "y"),
|
||||||
|
("aau", "âu", "â"), ("aay", "ây", "â"), ("eeu", "êu", "ê"),
|
||||||
|
("ooi", "ôi", "ô"), ("owi", "ơi", "ơ"), ("uwi", "ưi", "ư"),
|
||||||
|
("uwu", "ưu", "ư"), ("uwa", "ưa", "ư"), ("uee", "uê", "ê"),
|
||||||
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
||||||
("uow", "ươ", "ơ"), ("uaa", "uâ", "â"), ("oaw", "oă", "ă"),
|
("uow", "ươ", "ơ"), ("uaa", "uâ", "â"), ("oaw", "oă", "ă"),
|
||||||
("uwa", "ưa", "a"), ("uwow", "ươ", "ơ"),
|
("uwow", "ươ", "ơ"),
|
||||||
]
|
]
|
||||||
for i, o, v in tab:
|
for i, o, v in tab:
|
||||||
emit(i, o)
|
emit(i, o)
|
||||||
@@ -68,21 +72,21 @@ def vowel2():
|
|||||||
emit("ie", "ie")
|
emit("ie", "ie")
|
||||||
emit("ye", "ye")
|
emit("ye", "ye")
|
||||||
emit("uo", "uo")
|
emit("uo", "uo")
|
||||||
emit("ua", "ua")
|
|
||||||
|
|
||||||
def vowel3():
|
def vowel3():
|
||||||
# input, output, vowel
|
# input, output, vowel
|
||||||
tab = [
|
tab = [
|
||||||
("ieeu", "iêu", "ê"), ("yeeu", "yêu", "ê"),
|
("ieeu", "iêu", "ê"), ("yeeu", "yêu", "ê"),
|
||||||
("uooi", "uôi", "ô"), ("uowi", "ươi", "ơ"),
|
("uooi", "uôi", "ô"), ("uowi", "ươi", "ơ"),
|
||||||
("oai", "oai", "a"), ("oay", "oay", "a"),
|
("uowu", "ươu", "ơ"), ("uwowi", "ươi", "ơ"),
|
||||||
("uyee", "uyê", "ê"),
|
("uwowu", "ươu", "ơ"), ("uaay", "uây", "â"),
|
||||||
|
("oai", "oai", "a"), ("oay", "oay", "a"), ("oeo", "oeo", "e"),
|
||||||
|
("uya", "uya", "y"), ("uyu", "uyu", "y"), ("uyee", "uyê", "ê"),
|
||||||
]
|
]
|
||||||
for i, o, v in tab:
|
for i, o, v in tab:
|
||||||
emit(i, o)
|
emit(i, o)
|
||||||
for t in tone:
|
for t in tone:
|
||||||
emit(i+t, o.replace(v, addtone(v, t), 1))
|
emit(i+t, o.replace(v, addtone(v, t), 1))
|
||||||
emit("uya", "uya")
|
|
||||||
|
|
||||||
def modvowels():
|
def modvowels():
|
||||||
for i, o in modvowel:
|
for i, o in modvowel:
|
||||||
@@ -132,10 +136,11 @@ def final():
|
|||||||
("aw", "ă", "ă"), ("aa", "â", "â"), ("ee", "ê", "ê"),
|
("aw", "ă", "ă"), ("aa", "â", "â"), ("ee", "ê", "ê"),
|
||||||
("oo", "ô", "ô"), ("ow", "ơ", "ơ"), ("uw", "ư", "ư"),
|
("oo", "ô", "ô"), ("ow", "ơ", "ơ"), ("uw", "ư", "ư"),
|
||||||
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
||||||
("uow", "ươ", "ơ"), ("uaa", "uâ", "â"), ("oaw", "oă", "ă"),
|
("uow", "ươ", "ơ"), ("uwow", "ươ", "ơ"), ("uyee", "uyê", "ê"),
|
||||||
|
("uaa", "uâ", "â"), ("uee", "uê", "ê"), ("oaw", "oă", "ă"),
|
||||||
("ai", "ai", "a"), ("ao", "ao", "a"), ("au", "au", "a"),
|
("ai", "ai", "a"), ("ao", "ao", "a"), ("au", "au", "a"),
|
||||||
("ay", "ay", "a"), ("oa", "oa", "a"), ("oi", "oi", "o"),
|
("ay", "ay", "a"), ("oa", "oa", "a"), ("oe", "oe", "e"),
|
||||||
("ui", "ui", "u"), ("uy", "uy", "y"),
|
("oi", "oi", "o"), ("ui", "ui", "u"), ("uy", "uy", "y"),
|
||||||
]
|
]
|
||||||
for i, o, v in tab:
|
for i, o, v in tab:
|
||||||
for c in coda:
|
for c in coda:
|
||||||
|
|||||||
1012
map/mssymbol.src
Normal file
1012
map/mssymbol.src
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
768
map/telex.map
768
map/telex.map
@@ -142,6 +142,18 @@ ayx ãy
|
|||||||
Ayx Ãy
|
Ayx Ãy
|
||||||
ayj ạy
|
ayj ạy
|
||||||
Ayj Ạy
|
Ayj Ạy
|
||||||
|
eo eo
|
||||||
|
Eo Eo
|
||||||
|
eos éo
|
||||||
|
Eos Éo
|
||||||
|
eof èo
|
||||||
|
Eof Èo
|
||||||
|
eor ẻo
|
||||||
|
Eor Ẻo
|
||||||
|
eox ẽo
|
||||||
|
Eox Ẽo
|
||||||
|
eoj ẹo
|
||||||
|
Eoj Ẹo
|
||||||
eu eu
|
eu eu
|
||||||
Eu Eu
|
Eu Eu
|
||||||
eus éu
|
eus éu
|
||||||
@@ -154,6 +166,18 @@ eux ẽu
|
|||||||
Eux Ẽu
|
Eux Ẽu
|
||||||
euj ẹu
|
euj ẹu
|
||||||
Euj Ẹu
|
Euj Ẹu
|
||||||
|
ia ia
|
||||||
|
Ia Ia
|
||||||
|
ias ía
|
||||||
|
Ias Ía
|
||||||
|
iaf ìa
|
||||||
|
Iaf Ìa
|
||||||
|
iar ỉa
|
||||||
|
Iar Ỉa
|
||||||
|
iax ĩa
|
||||||
|
Iax Ĩa
|
||||||
|
iaj ịa
|
||||||
|
Iaj Ịa
|
||||||
iu iu
|
iu iu
|
||||||
Iu Iu
|
Iu Iu
|
||||||
ius íu
|
ius íu
|
||||||
@@ -178,6 +202,18 @@ oix õi
|
|||||||
Oix Õi
|
Oix Õi
|
||||||
oij ọi
|
oij ọi
|
||||||
Oij Ọi
|
Oij Ọi
|
||||||
|
ua ua
|
||||||
|
Ua Ua
|
||||||
|
uas úa
|
||||||
|
Uas Úa
|
||||||
|
uaf ùa
|
||||||
|
Uaf Ùa
|
||||||
|
uar ủa
|
||||||
|
Uar Ủa
|
||||||
|
uax ũa
|
||||||
|
Uax Ũa
|
||||||
|
uaj ụa
|
||||||
|
Uaj Ụa
|
||||||
ui ui
|
ui ui
|
||||||
Ui Ui
|
Ui Ui
|
||||||
uis úi
|
uis úi
|
||||||
@@ -202,6 +238,114 @@ uyx uỹ
|
|||||||
Uyx Uỹ
|
Uyx Uỹ
|
||||||
uyj uỵ
|
uyj uỵ
|
||||||
Uyj Uỵ
|
Uyj Uỵ
|
||||||
|
aau âu
|
||||||
|
Aau Âu
|
||||||
|
aaus ấu
|
||||||
|
Aaus Ấu
|
||||||
|
aauf ầu
|
||||||
|
Aauf Ầu
|
||||||
|
aaur ẩu
|
||||||
|
Aaur Ẩu
|
||||||
|
aaux ẫu
|
||||||
|
Aaux Ẫu
|
||||||
|
aauj ậu
|
||||||
|
Aauj Ậu
|
||||||
|
aay ây
|
||||||
|
Aay Ây
|
||||||
|
aays ấy
|
||||||
|
Aays Ấy
|
||||||
|
aayf ầy
|
||||||
|
Aayf Ầy
|
||||||
|
aayr ẩy
|
||||||
|
Aayr Ẩy
|
||||||
|
aayx ẫy
|
||||||
|
Aayx Ẫy
|
||||||
|
aayj ậy
|
||||||
|
Aayj Ậy
|
||||||
|
eeu êu
|
||||||
|
Eeu Êu
|
||||||
|
eeus ếu
|
||||||
|
Eeus Ếu
|
||||||
|
eeuf ều
|
||||||
|
Eeuf Ều
|
||||||
|
eeur ểu
|
||||||
|
Eeur Ểu
|
||||||
|
eeux ễu
|
||||||
|
Eeux Ễu
|
||||||
|
eeuj ệu
|
||||||
|
Eeuj Ệu
|
||||||
|
ooi ôi
|
||||||
|
Ooi Ôi
|
||||||
|
oois ối
|
||||||
|
Oois Ối
|
||||||
|
ooif ồi
|
||||||
|
Ooif Ồi
|
||||||
|
ooir ổi
|
||||||
|
Ooir Ổi
|
||||||
|
ooix ỗi
|
||||||
|
Ooix Ỗi
|
||||||
|
ooij ội
|
||||||
|
Ooij Ội
|
||||||
|
owi ơi
|
||||||
|
Owi Ơi
|
||||||
|
owis ới
|
||||||
|
Owis Ới
|
||||||
|
owif ời
|
||||||
|
Owif Ời
|
||||||
|
owir ởi
|
||||||
|
Owir Ởi
|
||||||
|
owix ỡi
|
||||||
|
Owix Ỡi
|
||||||
|
owij ợi
|
||||||
|
Owij Ợi
|
||||||
|
uwi ưi
|
||||||
|
Uwi Ưi
|
||||||
|
uwis ứi
|
||||||
|
Uwis Ứi
|
||||||
|
uwif ừi
|
||||||
|
Uwif Ừi
|
||||||
|
uwir ửi
|
||||||
|
Uwir Ửi
|
||||||
|
uwix ữi
|
||||||
|
Uwix Ữi
|
||||||
|
uwij ựi
|
||||||
|
Uwij Ựi
|
||||||
|
uwu ưu
|
||||||
|
Uwu Ưu
|
||||||
|
uwus ứu
|
||||||
|
Uwus Ứu
|
||||||
|
uwuf ừu
|
||||||
|
Uwuf Ừu
|
||||||
|
uwur ửu
|
||||||
|
Uwur Ửu
|
||||||
|
uwux ữu
|
||||||
|
Uwux Ữu
|
||||||
|
uwuj ựu
|
||||||
|
Uwuj Ựu
|
||||||
|
uwa ưa
|
||||||
|
Uwa Ưa
|
||||||
|
uwas ứa
|
||||||
|
Uwas Ứa
|
||||||
|
uwaf ừa
|
||||||
|
Uwaf Ừa
|
||||||
|
uwar ửa
|
||||||
|
Uwar Ửa
|
||||||
|
uwax ữa
|
||||||
|
Uwax Ữa
|
||||||
|
uwaj ựa
|
||||||
|
Uwaj Ựa
|
||||||
|
uee uê
|
||||||
|
Uee Uê
|
||||||
|
uees uế
|
||||||
|
Uees Uế
|
||||||
|
ueef uề
|
||||||
|
Ueef Uề
|
||||||
|
ueer uể
|
||||||
|
Ueer Uể
|
||||||
|
ueex uễ
|
||||||
|
Ueex Uễ
|
||||||
|
ueej uệ
|
||||||
|
Ueej Uệ
|
||||||
iee iê
|
iee iê
|
||||||
Iee Iê
|
Iee Iê
|
||||||
iees iế
|
iees iế
|
||||||
@@ -274,18 +418,6 @@ oawx oẵ
|
|||||||
Oawx Oẵ
|
Oawx Oẵ
|
||||||
oawj oặ
|
oawj oặ
|
||||||
Oawj Oặ
|
Oawj Oặ
|
||||||
uwa ưa
|
|
||||||
Uwa Ưa
|
|
||||||
uwas ưá
|
|
||||||
Uwas Ưá
|
|
||||||
uwaf ưà
|
|
||||||
Uwaf Ưà
|
|
||||||
uwar ưả
|
|
||||||
Uwar Ưả
|
|
||||||
uwax ưã
|
|
||||||
Uwax Ưã
|
|
||||||
uwaj ưạ
|
|
||||||
Uwaj Ưạ
|
|
||||||
uwow ươ
|
uwow ươ
|
||||||
Uwow Ươ
|
Uwow Ươ
|
||||||
uwows ướ
|
uwows ướ
|
||||||
@@ -304,8 +436,6 @@ ye ye
|
|||||||
Ye Ye
|
Ye Ye
|
||||||
uo uo
|
uo uo
|
||||||
Uo Uo
|
Uo Uo
|
||||||
ua ua
|
|
||||||
Ua Ua
|
|
||||||
ieeu iêu
|
ieeu iêu
|
||||||
Ieeu Iêu
|
Ieeu Iêu
|
||||||
ieeus iếu
|
ieeus iếu
|
||||||
@@ -354,6 +484,54 @@ uowix ưỡi
|
|||||||
Uowix Ưỡi
|
Uowix Ưỡi
|
||||||
uowij ượi
|
uowij ượi
|
||||||
Uowij Ượi
|
Uowij Ượi
|
||||||
|
uowu ươu
|
||||||
|
Uowu Ươu
|
||||||
|
uowus ướu
|
||||||
|
Uowus Ướu
|
||||||
|
uowuf ườu
|
||||||
|
Uowuf Ườu
|
||||||
|
uowur ưởu
|
||||||
|
Uowur Ưởu
|
||||||
|
uowux ưỡu
|
||||||
|
Uowux Ưỡu
|
||||||
|
uowuj ượu
|
||||||
|
Uowuj Ượu
|
||||||
|
uwowi ươi
|
||||||
|
Uwowi Ươi
|
||||||
|
uwowis ưới
|
||||||
|
Uwowis Ưới
|
||||||
|
uwowif ười
|
||||||
|
Uwowif Ười
|
||||||
|
uwowir ưởi
|
||||||
|
Uwowir Ưởi
|
||||||
|
uwowix ưỡi
|
||||||
|
Uwowix Ưỡi
|
||||||
|
uwowij ượi
|
||||||
|
Uwowij Ượi
|
||||||
|
uwowu ươu
|
||||||
|
Uwowu Ươu
|
||||||
|
uwowus ướu
|
||||||
|
Uwowus Ướu
|
||||||
|
uwowuf ườu
|
||||||
|
Uwowuf Ườu
|
||||||
|
uwowur ưởu
|
||||||
|
Uwowur Ưởu
|
||||||
|
uwowux ưỡu
|
||||||
|
Uwowux Ưỡu
|
||||||
|
uwowuj ượu
|
||||||
|
Uwowuj Ượu
|
||||||
|
uaay uây
|
||||||
|
Uaay Uây
|
||||||
|
uaays uấy
|
||||||
|
Uaays Uấy
|
||||||
|
uaayf uầy
|
||||||
|
Uaayf Uầy
|
||||||
|
uaayr uẩy
|
||||||
|
Uaayr Uẩy
|
||||||
|
uaayx uẫy
|
||||||
|
Uaayx Uẫy
|
||||||
|
uaayj uậy
|
||||||
|
Uaayj Uậy
|
||||||
oai oai
|
oai oai
|
||||||
Oai Oai
|
Oai Oai
|
||||||
oais oái
|
oais oái
|
||||||
@@ -378,6 +556,42 @@ oayx oãy
|
|||||||
Oayx Oãy
|
Oayx Oãy
|
||||||
oayj oạy
|
oayj oạy
|
||||||
Oayj Oạy
|
Oayj Oạy
|
||||||
|
oeo oeo
|
||||||
|
Oeo Oeo
|
||||||
|
oeos oéo
|
||||||
|
Oeos Oéo
|
||||||
|
oeof oèo
|
||||||
|
Oeof Oèo
|
||||||
|
oeor oẻo
|
||||||
|
Oeor Oẻo
|
||||||
|
oeox oẽo
|
||||||
|
Oeox Oẽo
|
||||||
|
oeoj oẹo
|
||||||
|
Oeoj Oẹo
|
||||||
|
uya uya
|
||||||
|
Uya Uya
|
||||||
|
uyas uýa
|
||||||
|
Uyas Uýa
|
||||||
|
uyaf uỳa
|
||||||
|
Uyaf Uỳa
|
||||||
|
uyar uỷa
|
||||||
|
Uyar Uỷa
|
||||||
|
uyax uỹa
|
||||||
|
Uyax Uỹa
|
||||||
|
uyaj uỵa
|
||||||
|
Uyaj Uỵa
|
||||||
|
uyu uyu
|
||||||
|
Uyu Uyu
|
||||||
|
uyus uýu
|
||||||
|
Uyus Uýu
|
||||||
|
uyuf uỳu
|
||||||
|
Uyuf Uỳu
|
||||||
|
uyur uỷu
|
||||||
|
Uyur Uỷu
|
||||||
|
uyux uỹu
|
||||||
|
Uyux Uỹu
|
||||||
|
uyuj uỵu
|
||||||
|
Uyuj Uỵu
|
||||||
uyee uyê
|
uyee uyê
|
||||||
Uyee Uyê
|
Uyee Uyê
|
||||||
uyees uyế
|
uyees uyế
|
||||||
@@ -390,8 +604,6 @@ uyeex uyễ
|
|||||||
Uyeex Uyễ
|
Uyeex Uyễ
|
||||||
uyeej uyệ
|
uyeej uyệ
|
||||||
Uyeej Uyệ
|
Uyeej Uyệ
|
||||||
uya uya
|
|
||||||
Uya Uya
|
|
||||||
aw ă
|
aw ă
|
||||||
Aw Ă
|
Aw Ă
|
||||||
aa â
|
aa â
|
||||||
@@ -2146,6 +2358,198 @@ uownhx ưỡnh
|
|||||||
Uownhx Ưỡnh
|
Uownhx Ưỡnh
|
||||||
uownhj ượnh
|
uownhj ượnh
|
||||||
Uownhj Ượnh
|
Uownhj Ượnh
|
||||||
|
uwowc ươc
|
||||||
|
Uwowc Ươc
|
||||||
|
uwowcs ước
|
||||||
|
Uwowcs Ước
|
||||||
|
uwowcf ườc
|
||||||
|
Uwowcf Ườc
|
||||||
|
uwowcr ưởc
|
||||||
|
Uwowcr Ưởc
|
||||||
|
uwowcx ưỡc
|
||||||
|
Uwowcx Ưỡc
|
||||||
|
uwowcj ược
|
||||||
|
Uwowcj Ược
|
||||||
|
uwowm ươm
|
||||||
|
Uwowm Ươm
|
||||||
|
uwowms ướm
|
||||||
|
Uwowms Ướm
|
||||||
|
uwowmf ườm
|
||||||
|
Uwowmf Ườm
|
||||||
|
uwowmr ưởm
|
||||||
|
Uwowmr Ưởm
|
||||||
|
uwowmx ưỡm
|
||||||
|
Uwowmx Ưỡm
|
||||||
|
uwowmj ượm
|
||||||
|
Uwowmj Ượm
|
||||||
|
uwown ươn
|
||||||
|
Uwown Ươn
|
||||||
|
uwowns ướn
|
||||||
|
Uwowns Ướn
|
||||||
|
uwownf ườn
|
||||||
|
Uwownf Ườn
|
||||||
|
uwownr ưởn
|
||||||
|
Uwownr Ưởn
|
||||||
|
uwownx ưỡn
|
||||||
|
Uwownx Ưỡn
|
||||||
|
uwownj ượn
|
||||||
|
Uwownj Ượn
|
||||||
|
uwowp ươp
|
||||||
|
Uwowp Ươp
|
||||||
|
uwowps ướp
|
||||||
|
Uwowps Ướp
|
||||||
|
uwowpf ườp
|
||||||
|
Uwowpf Ườp
|
||||||
|
uwowpr ưởp
|
||||||
|
Uwowpr Ưởp
|
||||||
|
uwowpx ưỡp
|
||||||
|
Uwowpx Ưỡp
|
||||||
|
uwowpj ượp
|
||||||
|
Uwowpj Ượp
|
||||||
|
uwowt ươt
|
||||||
|
Uwowt Ươt
|
||||||
|
uwowts ướt
|
||||||
|
Uwowts Ướt
|
||||||
|
uwowtf ườt
|
||||||
|
Uwowtf Ườt
|
||||||
|
uwowtr ưởt
|
||||||
|
Uwowtr Ưởt
|
||||||
|
uwowtx ưỡt
|
||||||
|
Uwowtx Ưỡt
|
||||||
|
uwowtj ượt
|
||||||
|
Uwowtj Ượt
|
||||||
|
uwowch ươch
|
||||||
|
Uwowch Ươch
|
||||||
|
uwowchs ướch
|
||||||
|
Uwowchs Ướch
|
||||||
|
uwowchf ườch
|
||||||
|
Uwowchf Ườch
|
||||||
|
uwowchr ưởch
|
||||||
|
Uwowchr Ưởch
|
||||||
|
uwowchx ưỡch
|
||||||
|
Uwowchx Ưỡch
|
||||||
|
uwowchj ượch
|
||||||
|
Uwowchj Ượch
|
||||||
|
uwowng ương
|
||||||
|
Uwowng Ương
|
||||||
|
uwowngs ướng
|
||||||
|
Uwowngs Ướng
|
||||||
|
uwowngf ường
|
||||||
|
Uwowngf Ường
|
||||||
|
uwowngr ưởng
|
||||||
|
Uwowngr Ưởng
|
||||||
|
uwowngx ưỡng
|
||||||
|
Uwowngx Ưỡng
|
||||||
|
uwowngj ượng
|
||||||
|
Uwowngj Ượng
|
||||||
|
uwownh ươnh
|
||||||
|
Uwownh Ươnh
|
||||||
|
uwownhs ướnh
|
||||||
|
Uwownhs Ướnh
|
||||||
|
uwownhf ườnh
|
||||||
|
Uwownhf Ườnh
|
||||||
|
uwownhr ưởnh
|
||||||
|
Uwownhr Ưởnh
|
||||||
|
uwownhx ưỡnh
|
||||||
|
Uwownhx Ưỡnh
|
||||||
|
uwownhj ượnh
|
||||||
|
Uwownhj Ượnh
|
||||||
|
uyeec uyêc
|
||||||
|
Uyeec Uyêc
|
||||||
|
uyeecs uyếc
|
||||||
|
Uyeecs Uyếc
|
||||||
|
uyeecf uyềc
|
||||||
|
Uyeecf Uyềc
|
||||||
|
uyeecr uyểc
|
||||||
|
Uyeecr Uyểc
|
||||||
|
uyeecx uyễc
|
||||||
|
Uyeecx Uyễc
|
||||||
|
uyeecj uyệc
|
||||||
|
Uyeecj Uyệc
|
||||||
|
uyeem uyêm
|
||||||
|
Uyeem Uyêm
|
||||||
|
uyeems uyếm
|
||||||
|
Uyeems Uyếm
|
||||||
|
uyeemf uyềm
|
||||||
|
Uyeemf Uyềm
|
||||||
|
uyeemr uyểm
|
||||||
|
Uyeemr Uyểm
|
||||||
|
uyeemx uyễm
|
||||||
|
Uyeemx Uyễm
|
||||||
|
uyeemj uyệm
|
||||||
|
Uyeemj Uyệm
|
||||||
|
uyeen uyên
|
||||||
|
Uyeen Uyên
|
||||||
|
uyeens uyến
|
||||||
|
Uyeens Uyến
|
||||||
|
uyeenf uyền
|
||||||
|
Uyeenf Uyền
|
||||||
|
uyeenr uyển
|
||||||
|
Uyeenr Uyển
|
||||||
|
uyeenx uyễn
|
||||||
|
Uyeenx Uyễn
|
||||||
|
uyeenj uyện
|
||||||
|
Uyeenj Uyện
|
||||||
|
uyeep uyêp
|
||||||
|
Uyeep Uyêp
|
||||||
|
uyeeps uyếp
|
||||||
|
Uyeeps Uyếp
|
||||||
|
uyeepf uyềp
|
||||||
|
Uyeepf Uyềp
|
||||||
|
uyeepr uyểp
|
||||||
|
Uyeepr Uyểp
|
||||||
|
uyeepx uyễp
|
||||||
|
Uyeepx Uyễp
|
||||||
|
uyeepj uyệp
|
||||||
|
Uyeepj Uyệp
|
||||||
|
uyeet uyêt
|
||||||
|
Uyeet Uyêt
|
||||||
|
uyeets uyết
|
||||||
|
Uyeets Uyết
|
||||||
|
uyeetf uyềt
|
||||||
|
Uyeetf Uyềt
|
||||||
|
uyeetr uyểt
|
||||||
|
Uyeetr Uyểt
|
||||||
|
uyeetx uyễt
|
||||||
|
Uyeetx Uyễt
|
||||||
|
uyeetj uyệt
|
||||||
|
Uyeetj Uyệt
|
||||||
|
uyeech uyêch
|
||||||
|
Uyeech Uyêch
|
||||||
|
uyeechs uyếch
|
||||||
|
Uyeechs Uyếch
|
||||||
|
uyeechf uyềch
|
||||||
|
Uyeechf Uyềch
|
||||||
|
uyeechr uyểch
|
||||||
|
Uyeechr Uyểch
|
||||||
|
uyeechx uyễch
|
||||||
|
Uyeechx Uyễch
|
||||||
|
uyeechj uyệch
|
||||||
|
Uyeechj Uyệch
|
||||||
|
uyeeng uyêng
|
||||||
|
Uyeeng Uyêng
|
||||||
|
uyeengs uyếng
|
||||||
|
Uyeengs Uyếng
|
||||||
|
uyeengf uyềng
|
||||||
|
Uyeengf Uyềng
|
||||||
|
uyeengr uyểng
|
||||||
|
Uyeengr Uyểng
|
||||||
|
uyeengx uyễng
|
||||||
|
Uyeengx Uyễng
|
||||||
|
uyeengj uyệng
|
||||||
|
Uyeengj Uyệng
|
||||||
|
uyeenh uyênh
|
||||||
|
Uyeenh Uyênh
|
||||||
|
uyeenhs uyếnh
|
||||||
|
Uyeenhs Uyếnh
|
||||||
|
uyeenhf uyềnh
|
||||||
|
Uyeenhf Uyềnh
|
||||||
|
uyeenhr uyểnh
|
||||||
|
Uyeenhr Uyểnh
|
||||||
|
uyeenhx uyễnh
|
||||||
|
Uyeenhx Uyễnh
|
||||||
|
uyeenhj uyệnh
|
||||||
|
Uyeenhj Uyệnh
|
||||||
uaac uâc
|
uaac uâc
|
||||||
Uaac Uâc
|
Uaac Uâc
|
||||||
uaacs uấc
|
uaacs uấc
|
||||||
@@ -2242,6 +2646,102 @@ uaanhx uẫnh
|
|||||||
Uaanhx Uẫnh
|
Uaanhx Uẫnh
|
||||||
uaanhj uậnh
|
uaanhj uậnh
|
||||||
Uaanhj Uậnh
|
Uaanhj Uậnh
|
||||||
|
ueec uêc
|
||||||
|
Ueec Uêc
|
||||||
|
ueecs uếc
|
||||||
|
Ueecs Uếc
|
||||||
|
ueecf uềc
|
||||||
|
Ueecf Uềc
|
||||||
|
ueecr uểc
|
||||||
|
Ueecr Uểc
|
||||||
|
ueecx uễc
|
||||||
|
Ueecx Uễc
|
||||||
|
ueecj uệc
|
||||||
|
Ueecj Uệc
|
||||||
|
ueem uêm
|
||||||
|
Ueem Uêm
|
||||||
|
ueems uếm
|
||||||
|
Ueems Uếm
|
||||||
|
ueemf uềm
|
||||||
|
Ueemf Uềm
|
||||||
|
ueemr uểm
|
||||||
|
Ueemr Uểm
|
||||||
|
ueemx uễm
|
||||||
|
Ueemx Uễm
|
||||||
|
ueemj uệm
|
||||||
|
Ueemj Uệm
|
||||||
|
ueen uên
|
||||||
|
Ueen Uên
|
||||||
|
ueens uến
|
||||||
|
Ueens Uến
|
||||||
|
ueenf uền
|
||||||
|
Ueenf Uền
|
||||||
|
ueenr uển
|
||||||
|
Ueenr Uển
|
||||||
|
ueenx uễn
|
||||||
|
Ueenx Uễn
|
||||||
|
ueenj uện
|
||||||
|
Ueenj Uện
|
||||||
|
ueep uêp
|
||||||
|
Ueep Uêp
|
||||||
|
ueeps uếp
|
||||||
|
Ueeps Uếp
|
||||||
|
ueepf uềp
|
||||||
|
Ueepf Uềp
|
||||||
|
ueepr uểp
|
||||||
|
Ueepr Uểp
|
||||||
|
ueepx uễp
|
||||||
|
Ueepx Uễp
|
||||||
|
ueepj uệp
|
||||||
|
Ueepj Uệp
|
||||||
|
ueet uêt
|
||||||
|
Ueet Uêt
|
||||||
|
ueets uết
|
||||||
|
Ueets Uết
|
||||||
|
ueetf uềt
|
||||||
|
Ueetf Uềt
|
||||||
|
ueetr uểt
|
||||||
|
Ueetr Uểt
|
||||||
|
ueetx uễt
|
||||||
|
Ueetx Uễt
|
||||||
|
ueetj uệt
|
||||||
|
Ueetj Uệt
|
||||||
|
ueech uêch
|
||||||
|
Ueech Uêch
|
||||||
|
ueechs uếch
|
||||||
|
Ueechs Uếch
|
||||||
|
ueechf uềch
|
||||||
|
Ueechf Uềch
|
||||||
|
ueechr uểch
|
||||||
|
Ueechr Uểch
|
||||||
|
ueechx uễch
|
||||||
|
Ueechx Uễch
|
||||||
|
ueechj uệch
|
||||||
|
Ueechj Uệch
|
||||||
|
ueeng uêng
|
||||||
|
Ueeng Uêng
|
||||||
|
ueengs uếng
|
||||||
|
Ueengs Uếng
|
||||||
|
ueengf uềng
|
||||||
|
Ueengf Uềng
|
||||||
|
ueengr uểng
|
||||||
|
Ueengr Uểng
|
||||||
|
ueengx uễng
|
||||||
|
Ueengx Uễng
|
||||||
|
ueengj uệng
|
||||||
|
Ueengj Uệng
|
||||||
|
ueenh uênh
|
||||||
|
Ueenh Uênh
|
||||||
|
ueenhs uếnh
|
||||||
|
Ueenhs Uếnh
|
||||||
|
ueenhf uềnh
|
||||||
|
Ueenhf Uềnh
|
||||||
|
ueenhr uểnh
|
||||||
|
Ueenhr Uểnh
|
||||||
|
ueenhx uễnh
|
||||||
|
Ueenhx Uễnh
|
||||||
|
ueenhj uệnh
|
||||||
|
Ueenhj Uệnh
|
||||||
oawc oăc
|
oawc oăc
|
||||||
Oawc Oăc
|
Oawc Oăc
|
||||||
oawcs oắc
|
oawcs oắc
|
||||||
@@ -2818,6 +3318,102 @@ oanhx oãnh
|
|||||||
Oanhx Oãnh
|
Oanhx Oãnh
|
||||||
oanhj oạnh
|
oanhj oạnh
|
||||||
Oanhj Oạnh
|
Oanhj Oạnh
|
||||||
|
oec oec
|
||||||
|
Oec Oec
|
||||||
|
oecs oéc
|
||||||
|
Oecs Oéc
|
||||||
|
oecf oèc
|
||||||
|
Oecf Oèc
|
||||||
|
oecr oẻc
|
||||||
|
Oecr Oẻc
|
||||||
|
oecx oẽc
|
||||||
|
Oecx Oẽc
|
||||||
|
oecj oẹc
|
||||||
|
Oecj Oẹc
|
||||||
|
oem oem
|
||||||
|
Oem Oem
|
||||||
|
oems oém
|
||||||
|
Oems Oém
|
||||||
|
oemf oèm
|
||||||
|
Oemf Oèm
|
||||||
|
oemr oẻm
|
||||||
|
Oemr Oẻm
|
||||||
|
oemx oẽm
|
||||||
|
Oemx Oẽm
|
||||||
|
oemj oẹm
|
||||||
|
Oemj Oẹm
|
||||||
|
oen oen
|
||||||
|
Oen Oen
|
||||||
|
oens oén
|
||||||
|
Oens Oén
|
||||||
|
oenf oèn
|
||||||
|
Oenf Oèn
|
||||||
|
oenr oẻn
|
||||||
|
Oenr Oẻn
|
||||||
|
oenx oẽn
|
||||||
|
Oenx Oẽn
|
||||||
|
oenj oẹn
|
||||||
|
Oenj Oẹn
|
||||||
|
oep oep
|
||||||
|
Oep Oep
|
||||||
|
oeps oép
|
||||||
|
Oeps Oép
|
||||||
|
oepf oèp
|
||||||
|
Oepf Oèp
|
||||||
|
oepr oẻp
|
||||||
|
Oepr Oẻp
|
||||||
|
oepx oẽp
|
||||||
|
Oepx Oẽp
|
||||||
|
oepj oẹp
|
||||||
|
Oepj Oẹp
|
||||||
|
oet oet
|
||||||
|
Oet Oet
|
||||||
|
oets oét
|
||||||
|
Oets Oét
|
||||||
|
oetf oèt
|
||||||
|
Oetf Oèt
|
||||||
|
oetr oẻt
|
||||||
|
Oetr Oẻt
|
||||||
|
oetx oẽt
|
||||||
|
Oetx Oẽt
|
||||||
|
oetj oẹt
|
||||||
|
Oetj Oẹt
|
||||||
|
oech oech
|
||||||
|
Oech Oech
|
||||||
|
oechs oéch
|
||||||
|
Oechs Oéch
|
||||||
|
oechf oèch
|
||||||
|
Oechf Oèch
|
||||||
|
oechr oẻch
|
||||||
|
Oechr Oẻch
|
||||||
|
oechx oẽch
|
||||||
|
Oechx Oẽch
|
||||||
|
oechj oẹch
|
||||||
|
Oechj Oẹch
|
||||||
|
oeng oeng
|
||||||
|
Oeng Oeng
|
||||||
|
oengs oéng
|
||||||
|
Oengs Oéng
|
||||||
|
oengf oèng
|
||||||
|
Oengf Oèng
|
||||||
|
oengr oẻng
|
||||||
|
Oengr Oẻng
|
||||||
|
oengx oẽng
|
||||||
|
Oengx Oẽng
|
||||||
|
oengj oẹng
|
||||||
|
Oengj Oẹng
|
||||||
|
oenh oenh
|
||||||
|
Oenh Oenh
|
||||||
|
oenhs oénh
|
||||||
|
Oenhs Oénh
|
||||||
|
oenhf oènh
|
||||||
|
Oenhf Oènh
|
||||||
|
oenhr oẻnh
|
||||||
|
Oenhr Oẻnh
|
||||||
|
oenhx oẽnh
|
||||||
|
Oenhx Oẽnh
|
||||||
|
oenhj oẹnh
|
||||||
|
Oenhj Oẹnh
|
||||||
oic oic
|
oic oic
|
||||||
Oic Oic
|
Oic Oic
|
||||||
oics óic
|
oics óic
|
||||||
@@ -3153,10 +3749,18 @@ quay quay
|
|||||||
Quay Quay
|
Quay Quay
|
||||||
giay giay
|
giay giay
|
||||||
Giay Giay
|
Giay Giay
|
||||||
|
queo queo
|
||||||
|
Queo Queo
|
||||||
|
gieo gieo
|
||||||
|
Gieo Gieo
|
||||||
queu queu
|
queu queu
|
||||||
Queu Queu
|
Queu Queu
|
||||||
gieu gieu
|
gieu gieu
|
||||||
Gieu Gieu
|
Gieu Gieu
|
||||||
|
quia quia
|
||||||
|
Quia Quia
|
||||||
|
giia giia
|
||||||
|
Giia Giia
|
||||||
quiu quiu
|
quiu quiu
|
||||||
Quiu Quiu
|
Quiu Quiu
|
||||||
giiu giiu
|
giiu giiu
|
||||||
@@ -3165,10 +3769,40 @@ quoi quoi
|
|||||||
Quoi Quoi
|
Quoi Quoi
|
||||||
gioi gioi
|
gioi gioi
|
||||||
Gioi Gioi
|
Gioi Gioi
|
||||||
|
giua giua
|
||||||
|
Giua Giua
|
||||||
giui giui
|
giui giui
|
||||||
Giui Giui
|
Giui Giui
|
||||||
giuy giuy
|
giuy giuy
|
||||||
Giuy Giuy
|
Giuy Giuy
|
||||||
|
quaau quâu
|
||||||
|
Quaau Quâu
|
||||||
|
giaau giâu
|
||||||
|
Giaau Giâu
|
||||||
|
quaay quây
|
||||||
|
Quaay Quây
|
||||||
|
giaay giây
|
||||||
|
Giaay Giây
|
||||||
|
queeu quêu
|
||||||
|
Queeu Quêu
|
||||||
|
gieeu giêu
|
||||||
|
Gieeu Giêu
|
||||||
|
quooi quôi
|
||||||
|
Quooi Quôi
|
||||||
|
giooi giôi
|
||||||
|
Giooi Giôi
|
||||||
|
quowi quơi
|
||||||
|
Quowi Quơi
|
||||||
|
giowi giơi
|
||||||
|
Giowi Giơi
|
||||||
|
giuwi giưi
|
||||||
|
Giuwi Giưi
|
||||||
|
giuwu giưu
|
||||||
|
Giuwu Giưu
|
||||||
|
giuwa giưa
|
||||||
|
Giuwa Giưa
|
||||||
|
giuee giuê
|
||||||
|
Giuee Giuê
|
||||||
quiee quiê
|
quiee quiê
|
||||||
Quiee Quiê
|
Quiee Quiê
|
||||||
giiee giiê
|
giiee giiê
|
||||||
@@ -3187,8 +3821,6 @@ quoaw quoă
|
|||||||
Quoaw Quoă
|
Quoaw Quoă
|
||||||
gioaw gioă
|
gioaw gioă
|
||||||
Gioaw Gioă
|
Gioaw Gioă
|
||||||
giuwa giưa
|
|
||||||
Giuwa Giưa
|
|
||||||
giuwow giươ
|
giuwow giươ
|
||||||
Giuwow Giươ
|
Giuwow Giươ
|
||||||
quie quie
|
quie quie
|
||||||
@@ -3201,8 +3833,6 @@ giye giye
|
|||||||
Giye Giye
|
Giye Giye
|
||||||
giuo giuo
|
giuo giuo
|
||||||
Giuo Giuo
|
Giuo Giuo
|
||||||
giua giua
|
|
||||||
Giua Giua
|
|
||||||
quieeu quiêu
|
quieeu quiêu
|
||||||
Quieeu Quiêu
|
Quieeu Quiêu
|
||||||
giieeu giiêu
|
giieeu giiêu
|
||||||
@@ -3215,6 +3845,14 @@ giuooi giuôi
|
|||||||
Giuooi Giuôi
|
Giuooi Giuôi
|
||||||
giuowi giươi
|
giuowi giươi
|
||||||
Giuowi Giươi
|
Giuowi Giươi
|
||||||
|
giuowu giươu
|
||||||
|
Giuowu Giươu
|
||||||
|
giuwowi giươi
|
||||||
|
Giuwowi Giươi
|
||||||
|
giuwowu giươu
|
||||||
|
Giuwowu Giươu
|
||||||
|
giuaay giuây
|
||||||
|
Giuaay Giuây
|
||||||
quoai quoai
|
quoai quoai
|
||||||
Quoai Quoai
|
Quoai Quoai
|
||||||
gioai gioai
|
gioai gioai
|
||||||
@@ -3223,10 +3861,16 @@ quoay quoay
|
|||||||
Quoay Quoay
|
Quoay Quoay
|
||||||
gioay gioay
|
gioay gioay
|
||||||
Gioay Gioay
|
Gioay Gioay
|
||||||
giuyee giuyê
|
quoeo quoeo
|
||||||
Giuyee Giuyê
|
Quoeo Quoeo
|
||||||
|
gioeo gioeo
|
||||||
|
Gioeo Gioeo
|
||||||
giuya giuya
|
giuya giuya
|
||||||
Giuya Giuya
|
Giuya Giuya
|
||||||
|
giuyu giuyu
|
||||||
|
Giuyu Giuyu
|
||||||
|
giuyee giuyê
|
||||||
|
Giuyee Giuyê
|
||||||
quaw quă
|
quaw quă
|
||||||
Quaw Quă
|
Quaw Quă
|
||||||
giaw giă
|
giaw giă
|
||||||
@@ -3719,6 +4363,38 @@ giuowng giương
|
|||||||
Giuowng Giương
|
Giuowng Giương
|
||||||
giuownh giươnh
|
giuownh giươnh
|
||||||
Giuownh Giươnh
|
Giuownh Giươnh
|
||||||
|
giuwowc giươc
|
||||||
|
Giuwowc Giươc
|
||||||
|
giuwowm giươm
|
||||||
|
Giuwowm Giươm
|
||||||
|
giuwown giươn
|
||||||
|
Giuwown Giươn
|
||||||
|
giuwowp giươp
|
||||||
|
Giuwowp Giươp
|
||||||
|
giuwowt giươt
|
||||||
|
Giuwowt Giươt
|
||||||
|
giuwowch giươch
|
||||||
|
Giuwowch Giươch
|
||||||
|
giuwowng giương
|
||||||
|
Giuwowng Giương
|
||||||
|
giuwownh giươnh
|
||||||
|
Giuwownh Giươnh
|
||||||
|
giuyeec giuyêc
|
||||||
|
Giuyeec Giuyêc
|
||||||
|
giuyeem giuyêm
|
||||||
|
Giuyeem Giuyêm
|
||||||
|
giuyeen giuyên
|
||||||
|
Giuyeen Giuyên
|
||||||
|
giuyeep giuyêp
|
||||||
|
Giuyeep Giuyêp
|
||||||
|
giuyeet giuyêt
|
||||||
|
Giuyeet Giuyêt
|
||||||
|
giuyeech giuyêch
|
||||||
|
Giuyeech Giuyêch
|
||||||
|
giuyeeng giuyêng
|
||||||
|
Giuyeeng Giuyêng
|
||||||
|
giuyeenh giuyênh
|
||||||
|
Giuyeenh Giuyênh
|
||||||
giuaac giuâc
|
giuaac giuâc
|
||||||
Giuaac Giuâc
|
Giuaac Giuâc
|
||||||
giuaam giuâm
|
giuaam giuâm
|
||||||
@@ -3735,6 +4411,22 @@ giuaang giuâng
|
|||||||
Giuaang Giuâng
|
Giuaang Giuâng
|
||||||
giuaanh giuânh
|
giuaanh giuânh
|
||||||
Giuaanh Giuânh
|
Giuaanh Giuânh
|
||||||
|
giueec giuêc
|
||||||
|
Giueec Giuêc
|
||||||
|
giueem giuêm
|
||||||
|
Giueem Giuêm
|
||||||
|
giueen giuên
|
||||||
|
Giueen Giuên
|
||||||
|
giueep giuêp
|
||||||
|
Giueep Giuêp
|
||||||
|
giueet giuêt
|
||||||
|
Giueet Giuêt
|
||||||
|
giueech giuêch
|
||||||
|
Giueech Giuêch
|
||||||
|
giueeng giuêng
|
||||||
|
Giueeng Giuêng
|
||||||
|
giueenh giuênh
|
||||||
|
Giueenh Giuênh
|
||||||
quoawc quoăc
|
quoawc quoăc
|
||||||
Quoawc Quoăc
|
Quoawc Quoăc
|
||||||
gioawc gioăc
|
gioawc gioăc
|
||||||
@@ -3927,6 +4619,38 @@ quoanh quoanh
|
|||||||
Quoanh Quoanh
|
Quoanh Quoanh
|
||||||
gioanh gioanh
|
gioanh gioanh
|
||||||
Gioanh Gioanh
|
Gioanh Gioanh
|
||||||
|
quoec quoec
|
||||||
|
Quoec Quoec
|
||||||
|
gioec gioec
|
||||||
|
Gioec Gioec
|
||||||
|
quoem quoem
|
||||||
|
Quoem Quoem
|
||||||
|
gioem gioem
|
||||||
|
Gioem Gioem
|
||||||
|
quoen quoen
|
||||||
|
Quoen Quoen
|
||||||
|
gioen gioen
|
||||||
|
Gioen Gioen
|
||||||
|
quoep quoep
|
||||||
|
Quoep Quoep
|
||||||
|
gioep gioep
|
||||||
|
Gioep Gioep
|
||||||
|
quoet quoet
|
||||||
|
Quoet Quoet
|
||||||
|
gioet gioet
|
||||||
|
Gioet Gioet
|
||||||
|
quoech quoech
|
||||||
|
Quoech Quoech
|
||||||
|
gioech gioech
|
||||||
|
Gioech Gioech
|
||||||
|
quoeng quoeng
|
||||||
|
Quoeng Quoeng
|
||||||
|
gioeng gioeng
|
||||||
|
Gioeng Gioeng
|
||||||
|
quoenh quoenh
|
||||||
|
Quoenh Quoenh
|
||||||
|
gioenh gioenh
|
||||||
|
Gioenh Gioenh
|
||||||
quoic quoic
|
quoic quoic
|
||||||
Quoic Quoic
|
Quoic Quoic
|
||||||
gioic gioic
|
gioic gioic
|
||||||
|
|||||||
256
popup_layout.c
256
popup_layout.c
@@ -1,33 +1,257 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
int
|
enum {
|
||||||
popupcells(Rune *r, int n)
|
Asciitofull = 0xFEE0,
|
||||||
{
|
};
|
||||||
int i, w;
|
|
||||||
|
int popupscale = 1;
|
||||||
|
|
||||||
|
static void
|
||||||
|
fill(u32int *buf, int n, u32int color)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
w = 0;
|
|
||||||
for(i = 0; i < n; i++)
|
for(i = 0; i < n; i++)
|
||||||
if(r[i] != 0xfe0e && r[i] != 0xfe0f)
|
buf[i] = color;
|
||||||
w++;
|
}
|
||||||
return w;
|
|
||||||
|
static void
|
||||||
|
fillrect(u32int *buf, int w, int h, int x, int y, int rw, int rh,
|
||||||
|
u32int color)
|
||||||
|
{
|
||||||
|
int x0, x1, y0, y1;
|
||||||
|
|
||||||
|
x0 = min(max(x, 0), w);
|
||||||
|
x1 = min(max(x + max(rw, 0), 0), w);
|
||||||
|
y0 = min(max(y, 0), h);
|
||||||
|
y1 = min(max(y + max(rh, 0), 0), h);
|
||||||
|
for(; y0 < y1; y0++)
|
||||||
|
fill(buf + y0*w + x0, max(x1 - x0, 0), color);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
validarea(Area *a)
|
||||||
|
{
|
||||||
|
return a != nil && a->w > 0 && a->h > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static vlong
|
||||||
|
areadistance(Area *a, int x, int y)
|
||||||
|
{
|
||||||
|
vlong dx, dy, right, bottom;
|
||||||
|
|
||||||
|
right = (vlong)a->x + a->w;
|
||||||
|
bottom = (vlong)a->y + a->h;
|
||||||
|
dx = x < a->x ? (vlong)a->x - x : x >= right ? x - right : 0;
|
||||||
|
dy = y < a->y ? (vlong)a->y - y : y >= bottom ? y - bottom : 0;
|
||||||
|
return dx + dy;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
popupposition(Caret *caret, int pointerx, int pointery, int screenw,
|
popuparea(Area *mon, int nmon, Area *work, int x, int y, Area *out)
|
||||||
int screenh, int w, int h, int *x, int *y)
|
|
||||||
{
|
{
|
||||||
vlong px, py, xmax, ymax;
|
vlong best, bottom, d, right, x0, x1, y0, y1;
|
||||||
|
int i, pick;
|
||||||
|
|
||||||
|
memset(out, 0, sizeof *out);
|
||||||
|
pick = -1;
|
||||||
|
best = (vlong)1 << 62;
|
||||||
|
for(i = 0; i < nmon; i++){
|
||||||
|
if(!validarea(&mon[i]))
|
||||||
|
continue;
|
||||||
|
right = (vlong)mon[i].x + mon[i].w;
|
||||||
|
bottom = (vlong)mon[i].y + mon[i].h;
|
||||||
|
if(x >= mon[i].x && x < right &&
|
||||||
|
y >= mon[i].y && y < bottom){
|
||||||
|
pick = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
d = areadistance(&mon[i], x, y);
|
||||||
|
if(d < best){
|
||||||
|
best = d;
|
||||||
|
pick = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(pick < 0)
|
||||||
|
return;
|
||||||
|
*out = mon[pick];
|
||||||
|
if(!validarea(work))
|
||||||
|
return;
|
||||||
|
x0 = max((vlong)out->x, work->x);
|
||||||
|
y0 = max((vlong)out->y, work->y);
|
||||||
|
x1 = min((vlong)out->x + out->w, (vlong)work->x + work->w);
|
||||||
|
y1 = min((vlong)out->y + out->h, (vlong)work->y + work->h);
|
||||||
|
if(x1 <= x0 || y1 <= y0)
|
||||||
|
return;
|
||||||
|
out->x = x0;
|
||||||
|
out->y = y0;
|
||||||
|
out->w = x1 - x0;
|
||||||
|
out->h = y1 - y0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
pagemarker(char *buf, int nbuf, int first, int shown, int total)
|
||||||
|
{
|
||||||
|
if(nbuf <= 0)
|
||||||
|
return 0;
|
||||||
|
buf[0] = 0;
|
||||||
|
if(shown <= 0 || (first == 0 && shown >= total))
|
||||||
|
return 0;
|
||||||
|
return snprint(buf, nbuf, "%d-%d/%d", first + 1,
|
||||||
|
first + shown, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
fitrows(int h, int pre, int mark, int n)
|
||||||
|
{
|
||||||
|
int fixed;
|
||||||
|
|
||||||
|
fixed = 2*PopupPad + mark*Fontsz;
|
||||||
|
if(pre)
|
||||||
|
fixed += Fontsz + (n != 0 ? PopupSep : 0);
|
||||||
|
if(h <= fixed)
|
||||||
|
return 0;
|
||||||
|
return min(n, (h - fixed) / Fontsz);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
popuplayout(Drawcmd *dc, int areaw, int areah, Popup *p)
|
||||||
|
{
|
||||||
|
char buf[32];
|
||||||
|
int first, i, markrow, nall, nmark, npre, total, width, y;
|
||||||
|
|
||||||
|
memset(p, 0, sizeof *p);
|
||||||
|
p->prey = -1;
|
||||||
|
p->sepy = -1;
|
||||||
|
p->rowsy = -1;
|
||||||
|
p->marky = -1;
|
||||||
|
p->sely = -1;
|
||||||
|
nall = min(max(dc->nkouho, 0), Maxdisp);
|
||||||
|
npre = dc->pre.n != 0;
|
||||||
|
if((nall == 0 && !npre) || areaw <= 0 || areah <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* As many rows as fit, and a page marker when they do not all. */
|
||||||
|
total = max(dc->total, dc->first + nall);
|
||||||
|
markrow = dc->first > 0 || nall < total;
|
||||||
|
p->n = fitrows(areah, npre, markrow, nall);
|
||||||
|
if(p->n < nall && !markrow){
|
||||||
|
markrow = 1;
|
||||||
|
p->n = fitrows(areah, npre, markrow, nall);
|
||||||
|
}
|
||||||
|
if(p->n == 0 && !npre)
|
||||||
|
return;
|
||||||
|
if(p->n != 0 && dc->sel >= p->n)
|
||||||
|
p->row0 = min(dc->sel - p->n + 1, nall - p->n);
|
||||||
|
first = dc->first + p->row0;
|
||||||
|
nmark = markrow ? pagemarker(buf, sizeof buf, first, p->n, total) : 0;
|
||||||
|
if(nmark == 0)
|
||||||
|
markrow = 0;
|
||||||
|
|
||||||
|
/* Rows share one steady width; a preedit alone hugs its text. */
|
||||||
|
width = p->n != 0 ? PopupBasew : 2*PopupPad;
|
||||||
|
if(npre)
|
||||||
|
width = max(width, textwidth(&dc->pre) + 2*PopupPad);
|
||||||
|
for(i = 0; i < p->n; i++)
|
||||||
|
width = max(width, textwidth(&dc->kouho[p->row0+i]) +
|
||||||
|
PopupNumw + 2*PopupPad);
|
||||||
|
if(markrow){
|
||||||
|
sinit(&p->mark, buf, nmark);
|
||||||
|
p->markw = textwidth(&p->mark);
|
||||||
|
width = max(width, p->markw + 2*PopupPad);
|
||||||
|
}
|
||||||
|
p->w = min(width, areaw);
|
||||||
|
|
||||||
|
y = PopupPad;
|
||||||
|
if(npre){
|
||||||
|
p->prey = y;
|
||||||
|
y += Fontsz;
|
||||||
|
}
|
||||||
|
if(npre && p->n != 0){
|
||||||
|
p->sepy = y;
|
||||||
|
y += PopupSep;
|
||||||
|
}
|
||||||
|
if(p->n != 0){
|
||||||
|
p->rowsy = y;
|
||||||
|
y += p->n*Fontsz;
|
||||||
|
}
|
||||||
|
if(markrow){
|
||||||
|
p->marky = y;
|
||||||
|
y += Fontsz;
|
||||||
|
}
|
||||||
|
p->h = min(y + PopupPad, areah);
|
||||||
|
p->textw = max(p->w - 2*PopupPad - PopupNumw, 0);
|
||||||
|
if(markrow){
|
||||||
|
p->markw = min(p->markw, max(p->w - 2*PopupPad, 0));
|
||||||
|
p->markx = p->w - PopupPad - p->markw;
|
||||||
|
}
|
||||||
|
p->selw = max(p->w - 2*PopupPad, 0);
|
||||||
|
if(dc->sel >= p->row0 && dc->sel < p->row0 + p->n)
|
||||||
|
p->sely = p->rowsy + (dc->sel - p->row0)*Fontsz;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
popupdraw(u32int *img, Drawcmd *dc, Popup *p)
|
||||||
|
{
|
||||||
|
Str num;
|
||||||
|
u32int color;
|
||||||
|
int i, j, y;
|
||||||
|
|
||||||
|
fill(img, p->w*p->h, Colbg);
|
||||||
|
if(p->prey >= 0)
|
||||||
|
textdraw(img, p->w, p->h, PopupPad, p->prey,
|
||||||
|
max(p->w - 2*PopupPad, 0), Colfg, &dc->pre);
|
||||||
|
if(p->sepy >= 0)
|
||||||
|
fillrect(img, p->w, p->h, PopupPad, p->sepy,
|
||||||
|
p->w - 2*PopupPad, PopupSep, Colsep);
|
||||||
|
if(p->sely >= 0)
|
||||||
|
fillrect(img, p->w, p->h, PopupPad, p->sely,
|
||||||
|
p->selw, Fontsz, Colsel);
|
||||||
|
for(i = 0, y = p->rowsy; i < p->n; i++, y += Fontsz){
|
||||||
|
j = p->row0 + i;
|
||||||
|
color = j == dc->sel ? Colselfg : Colfg;
|
||||||
|
sclear(&num);
|
||||||
|
sputr(&num, '1' + j + Asciitofull);
|
||||||
|
textdraw(img, p->w, p->h, PopupPad, y,
|
||||||
|
min(PopupNumw, max(p->w - 2*PopupPad, 0)), color, &num);
|
||||||
|
textdraw(img, p->w, p->h, PopupPad + PopupNumw, y, p->textw,
|
||||||
|
color, &dc->kouho[j]);
|
||||||
|
}
|
||||||
|
if(p->marky >= 0)
|
||||||
|
textdraw(img, p->w, p->h, p->markx, p->marky, p->markw,
|
||||||
|
Colfg, &p->mark);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
popupposition(Caret *caret, int pointerx, int pointery, Area *area,
|
||||||
|
int w, int h, int *x, int *y)
|
||||||
|
{
|
||||||
|
vlong below, bottom, px, py, right, xmax, ymax;
|
||||||
|
|
||||||
|
if(!validarea(area)){
|
||||||
|
*x = 0;
|
||||||
|
*y = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
right = (vlong)area->x + area->w;
|
||||||
|
bottom = (vlong)area->y + area->h;
|
||||||
|
|
||||||
if(caret->valid){
|
if(caret->valid){
|
||||||
px = caret->x;
|
px = caret->x;
|
||||||
py = (vlong)caret->y + max(caret->h, 0);
|
below = (vlong)caret->y + max(caret->h, 0);
|
||||||
|
if(below >= area->y && below + h <= bottom)
|
||||||
|
py = below;
|
||||||
|
else
|
||||||
|
py = (vlong)caret->y - h;
|
||||||
}else{
|
}else{
|
||||||
px = (vlong)pointerx + 10;
|
px = (vlong)pointerx + 10;
|
||||||
py = (vlong)pointery + 10;
|
py = (vlong)pointery + 10;
|
||||||
|
if(py + h > bottom)
|
||||||
|
py = (vlong)pointery - 10 - h;
|
||||||
}
|
}
|
||||||
xmax = max((vlong)screenw - w, 0);
|
xmax = max(right - w, area->x);
|
||||||
ymax = max((vlong)screenh - h, 0);
|
ymax = max(bottom - h, area->y);
|
||||||
*x = max(0, min(px, xmax));
|
*x = max((vlong)area->x, min(px, xmax));
|
||||||
*y = max(0, min(py, ymax));
|
*y = max((vlong)area->y, min(py, ymax));
|
||||||
}
|
}
|
||||||
|
|||||||
494
proto/input-method-unstable-v2.xml
Normal file
494
proto/input-method-unstable-v2.xml
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="input_method_unstable_v2">
|
||||||
|
|
||||||
|
<copyright>
|
||||||
|
Copyright © 2008-2011 Kristian Høgsberg
|
||||||
|
Copyright © 2010-2011 Intel Corporation
|
||||||
|
Copyright © 2012-2013 Collabora, Ltd.
|
||||||
|
Copyright © 2012, 2013 Intel Corporation
|
||||||
|
Copyright © 2015, 2016 Jan Arne Petersen
|
||||||
|
Copyright © 2017, 2018 Red Hat, Inc.
|
||||||
|
Copyright © 2018 Purism SPC
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next
|
||||||
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<description summary="Protocol for creating input methods">
|
||||||
|
This protocol allows applications to act as input methods for compositors.
|
||||||
|
|
||||||
|
An input method context is used to manage the state of the input method.
|
||||||
|
|
||||||
|
Text strings are UTF-8 encoded, their indices and lengths are in bytes.
|
||||||
|
|
||||||
|
This document adheres to the RFC 2119 when using words like "must",
|
||||||
|
"should", "may", etc.
|
||||||
|
|
||||||
|
Warning! The protocol described in this file is experimental and
|
||||||
|
backward incompatible changes may be made. Backward compatible changes
|
||||||
|
may be added together with the corresponding interface version bump.
|
||||||
|
Backward incompatible changes are done by bumping the version number in
|
||||||
|
the protocol and interface names and resetting the interface version.
|
||||||
|
Once the protocol is to be declared stable, the 'z' prefix and the
|
||||||
|
version number in the protocol and interface names are removed and the
|
||||||
|
interface version number is reset.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<interface name="zwp_input_method_v2" version="1">
|
||||||
|
<description summary="input method">
|
||||||
|
An input method object allows for clients to compose text.
|
||||||
|
|
||||||
|
The objects connects the client to a text input in an application, and
|
||||||
|
lets the client to serve as an input method for a seat.
|
||||||
|
|
||||||
|
The zwp_input_method_v2 object can occupy two distinct states: active and
|
||||||
|
inactive. In the active state, the object is associated to and
|
||||||
|
communicates with a text input. In the inactive state, there is no
|
||||||
|
associated text input, and the only communication is with the compositor.
|
||||||
|
Initially, the input method is in the inactive state.
|
||||||
|
|
||||||
|
Requests issued in the inactive state must be accepted by the compositor.
|
||||||
|
Because of the serial mechanism, and the state reset on activate event,
|
||||||
|
they will not have any effect on the state of the next text input.
|
||||||
|
|
||||||
|
There must be no more than one input method object per seat.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="role" value="0" summary="wl_surface has another role"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<event name="activate">
|
||||||
|
<description summary="input method has been requested">
|
||||||
|
Notification that a text input focused on this seat requested the input
|
||||||
|
method to be activated.
|
||||||
|
|
||||||
|
This event serves the purpose of providing the compositor with an
|
||||||
|
active input method.
|
||||||
|
|
||||||
|
This event resets all state associated with previous
|
||||||
|
surrounding_text, text_change_cause, and content_type events, as well
|
||||||
|
as the state associated with set_preedit_string, commit_string, and
|
||||||
|
delete_surrounding_text requests. In addition, it marks the
|
||||||
|
zwp_input_method_v2 object as active, and makes any existing
|
||||||
|
zwp_input_popup_surface_v2 objects visible.
|
||||||
|
|
||||||
|
The surrounding_text, and content_type events must follow before the
|
||||||
|
next done event if the text input supports the respective
|
||||||
|
functionality.
|
||||||
|
|
||||||
|
State set with this event is double-buffered. It will get applied on
|
||||||
|
the next zwp_input_method_v2.done event, and stay valid until changed.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="deactivate">
|
||||||
|
<description summary="deactivate event">
|
||||||
|
Notification that no focused text input currently needs an active
|
||||||
|
input method on this seat.
|
||||||
|
|
||||||
|
This event marks the zwp_input_method_v2 object as inactive. The
|
||||||
|
compositor must make all existing zwp_input_popup_surface_v2 objects
|
||||||
|
invisible until the next activate event.
|
||||||
|
|
||||||
|
State set with this event is double-buffered. It will get applied on
|
||||||
|
the next zwp_input_method_v2.done event, and stay valid until changed.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="surrounding_text">
|
||||||
|
<description summary="surrounding text event">
|
||||||
|
Updates the surrounding plain text around the cursor, excluding the
|
||||||
|
preedit text.
|
||||||
|
|
||||||
|
If any preedit text is present, it is replaced with the cursor for the
|
||||||
|
purpose of this event.
|
||||||
|
|
||||||
|
The argument text is a buffer containing the surrounding text, and must
|
||||||
|
include the cursor position, and the complete selection. It should
|
||||||
|
contain additional characters before and after these. There is a
|
||||||
|
maximum length of wayland messages, so text can not be longer than 4000
|
||||||
|
bytes.
|
||||||
|
|
||||||
|
cursor is the byte offset of the cursor within the text buffer.
|
||||||
|
|
||||||
|
anchor is the byte offset of the selection anchor within the text
|
||||||
|
buffer. If there is no selected text, anchor must be the same as
|
||||||
|
cursor.
|
||||||
|
|
||||||
|
If this event does not arrive before the first done event, the input
|
||||||
|
method may assume that the text input does not support this
|
||||||
|
functionality and ignore following surrounding_text events.
|
||||||
|
|
||||||
|
Values set with this event are double-buffered. They will get applied
|
||||||
|
and set to initial values on the next zwp_input_method_v2.done
|
||||||
|
event.
|
||||||
|
|
||||||
|
The initial state for affected fields is empty, meaning that the text
|
||||||
|
input does not support sending surrounding text. If the empty values
|
||||||
|
get applied, subsequent attempts to change them may have no effect.
|
||||||
|
</description>
|
||||||
|
<arg name="text" type="string"/>
|
||||||
|
<arg name="cursor" type="uint"/>
|
||||||
|
<arg name="anchor" type="uint"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="text_change_cause">
|
||||||
|
<description summary="indicates the cause of surrounding text change">
|
||||||
|
Tells the input method why the text surrounding the cursor changed.
|
||||||
|
|
||||||
|
Whenever the client detects an external change in text, cursor, or
|
||||||
|
anchor position, it must issue this request to the compositor. This
|
||||||
|
request is intended to give the input method a chance to update the
|
||||||
|
preedit text in an appropriate way, e.g. by removing it when the user
|
||||||
|
starts typing with a keyboard.
|
||||||
|
|
||||||
|
cause describes the source of the change.
|
||||||
|
|
||||||
|
The value set with this event is double-buffered. It will get applied
|
||||||
|
and set to its initial value on the next zwp_input_method_v2.done
|
||||||
|
event.
|
||||||
|
|
||||||
|
The initial value of cause is input_method.
|
||||||
|
</description>
|
||||||
|
<arg name="cause" type="uint" enum="zwp_text_input_v3.change_cause"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="content_type">
|
||||||
|
<description summary="content purpose and hint">
|
||||||
|
Indicates the content type and hint for the current
|
||||||
|
zwp_input_method_v2 instance.
|
||||||
|
|
||||||
|
Values set with this event are double-buffered. They will get applied
|
||||||
|
on the next zwp_input_method_v2.done event.
|
||||||
|
|
||||||
|
The initial value for hint is none, and the initial value for purpose
|
||||||
|
is normal.
|
||||||
|
</description>
|
||||||
|
<arg name="hint" type="uint" enum="zwp_text_input_v3.content_hint"/>
|
||||||
|
<arg name="purpose" type="uint" enum="zwp_text_input_v3.content_purpose"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="done">
|
||||||
|
<description summary="apply state">
|
||||||
|
Atomically applies state changes recently sent to the client.
|
||||||
|
|
||||||
|
The done event establishes and updates the state of the client, and
|
||||||
|
must be issued after any changes to apply them.
|
||||||
|
|
||||||
|
Text input state (content purpose, content hint, surrounding text, and
|
||||||
|
change cause) is conceptually double-buffered within an input method
|
||||||
|
context.
|
||||||
|
|
||||||
|
Events modify the pending state, as opposed to the current state in use
|
||||||
|
by the input method. A done event atomically applies all pending state,
|
||||||
|
replacing the current state. After done, the new pending state is as
|
||||||
|
documented for each related request.
|
||||||
|
|
||||||
|
Events must be applied in the order of arrival.
|
||||||
|
|
||||||
|
Neither current nor pending state are modified unless noted otherwise.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="commit_string">
|
||||||
|
<description summary="commit string">
|
||||||
|
Send the commit string text for insertion to the application.
|
||||||
|
|
||||||
|
Inserts a string at current cursor position (see commit event
|
||||||
|
sequence). The string to commit could be either just a single character
|
||||||
|
after a key press or the result of some composing.
|
||||||
|
|
||||||
|
The argument text is a buffer containing the string to insert. There is
|
||||||
|
a maximum length of wayland messages, so text can not be longer than
|
||||||
|
4000 bytes.
|
||||||
|
|
||||||
|
Values set with this event are double-buffered. They must be applied
|
||||||
|
and reset to initial on the next zwp_text_input_v3.commit request.
|
||||||
|
|
||||||
|
The initial value of text is an empty string.
|
||||||
|
</description>
|
||||||
|
<arg name="text" type="string"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_preedit_string">
|
||||||
|
<description summary="pre-edit string">
|
||||||
|
Send the pre-edit string text to the application text input.
|
||||||
|
|
||||||
|
Place a new composing text (pre-edit) at the current cursor position.
|
||||||
|
Any previously set composing text must be removed. Any previously
|
||||||
|
existing selected text must be removed. The cursor is moved to a new
|
||||||
|
position within the preedit string.
|
||||||
|
|
||||||
|
The argument text is a buffer containing the preedit string. There is
|
||||||
|
a maximum length of wayland messages, so text can not be longer than
|
||||||
|
4000 bytes.
|
||||||
|
|
||||||
|
The arguments cursor_begin and cursor_end are counted in bytes relative
|
||||||
|
to the beginning of the submitted string buffer. Cursor should be
|
||||||
|
hidden by the text input when both are equal to -1.
|
||||||
|
|
||||||
|
cursor_begin indicates the beginning of the cursor. cursor_end
|
||||||
|
indicates the end of the cursor. It may be equal or different than
|
||||||
|
cursor_begin.
|
||||||
|
|
||||||
|
Values set with this event are double-buffered. They must be applied on
|
||||||
|
the next zwp_input_method_v2.commit event.
|
||||||
|
|
||||||
|
The initial value of text is an empty string. The initial value of
|
||||||
|
cursor_begin, and cursor_end are both 0.
|
||||||
|
</description>
|
||||||
|
<arg name="text" type="string"/>
|
||||||
|
<arg name="cursor_begin" type="int"/>
|
||||||
|
<arg name="cursor_end" type="int"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="delete_surrounding_text">
|
||||||
|
<description summary="delete text">
|
||||||
|
Remove the surrounding text.
|
||||||
|
|
||||||
|
before_length and after_length are the number of bytes before and after
|
||||||
|
the current cursor index (excluding the preedit text) to delete.
|
||||||
|
|
||||||
|
If any preedit text is present, it is replaced with the cursor for the
|
||||||
|
purpose of this event. In effect before_length is counted from the
|
||||||
|
beginning of preedit text, and after_length from its end (see commit
|
||||||
|
event sequence).
|
||||||
|
|
||||||
|
Values set with this event are double-buffered. They must be applied
|
||||||
|
and reset to initial on the next zwp_input_method_v2.commit request.
|
||||||
|
|
||||||
|
The initial values of both before_length and after_length are 0.
|
||||||
|
</description>
|
||||||
|
<arg name="before_length" type="uint"/>
|
||||||
|
<arg name="after_length" type="uint"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="commit">
|
||||||
|
<description summary="apply state">
|
||||||
|
Apply state changes from commit_string, set_preedit_string and
|
||||||
|
delete_surrounding_text requests.
|
||||||
|
|
||||||
|
The state relating to these events is double-buffered, and each one
|
||||||
|
modifies the pending state. This request replaces the current state
|
||||||
|
with the pending state.
|
||||||
|
|
||||||
|
The connected text input is expected to proceed by evaluating the
|
||||||
|
changes in the following order:
|
||||||
|
|
||||||
|
1. Replace existing preedit string with the cursor.
|
||||||
|
2. Delete requested surrounding text.
|
||||||
|
3. Insert commit string with the cursor at its end.
|
||||||
|
4. Calculate surrounding text to send.
|
||||||
|
5. Insert new preedit text in cursor position.
|
||||||
|
6. Place cursor inside preedit text.
|
||||||
|
|
||||||
|
The serial number reflects the last state of the zwp_input_method_v2
|
||||||
|
object known to the client. The value of the serial argument must be
|
||||||
|
equal to the number of done events already issued by that object. When
|
||||||
|
the compositor receives a commit request with a serial different than
|
||||||
|
the number of past done events, it must proceed as normal, except it
|
||||||
|
should not change the current state of the zwp_input_method_v2 object.
|
||||||
|
</description>
|
||||||
|
<arg name="serial" type="uint"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="get_input_popup_surface">
|
||||||
|
<description summary="create popup surface">
|
||||||
|
Creates a new zwp_input_popup_surface_v2 object wrapping a given
|
||||||
|
surface.
|
||||||
|
|
||||||
|
The surface gets assigned the "input_popup" role. If the surface
|
||||||
|
already has an assigned role, the compositor must issue a protocol
|
||||||
|
error.
|
||||||
|
</description>
|
||||||
|
<arg name="id" type="new_id" interface="zwp_input_popup_surface_v2"/>
|
||||||
|
<arg name="surface" type="object" interface="wl_surface"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="grab_keyboard">
|
||||||
|
<description summary="grab hardware keyboard">
|
||||||
|
Allow an input method to receive hardware keyboard input and process
|
||||||
|
key events to generate text events (with pre-edit) over the wire. This
|
||||||
|
allows input methods which compose multiple key events for inputting
|
||||||
|
text like it is done for CJK languages.
|
||||||
|
|
||||||
|
The compositor should send all keyboard events on the seat to the grab
|
||||||
|
holder via the returned wl_keyboard object. Nevertheless, the
|
||||||
|
compositor may decide not to forward any particular event. The
|
||||||
|
compositor must not further process any event after it has been
|
||||||
|
forwarded to the grab holder.
|
||||||
|
|
||||||
|
Releasing the resulting wl_keyboard object releases the grab.
|
||||||
|
</description>
|
||||||
|
<arg name="keyboard" type="new_id"
|
||||||
|
interface="zwp_input_method_keyboard_grab_v2"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="unavailable">
|
||||||
|
<description summary="input method unavailable">
|
||||||
|
The input method ceased to be available.
|
||||||
|
|
||||||
|
The compositor must issue this event as the only event on the object if
|
||||||
|
there was another input_method object associated with the same seat at
|
||||||
|
the time of its creation.
|
||||||
|
|
||||||
|
The compositor must issue this request when the object is no longer
|
||||||
|
usable, e.g. due to seat removal.
|
||||||
|
|
||||||
|
The input method context becomes inert and should be destroyed after
|
||||||
|
deactivation is handled. Any further requests and events except for the
|
||||||
|
destroy request must be ignored.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the text input">
|
||||||
|
Destroys the zwp_text_input_v2 object and any associated child
|
||||||
|
objects, i.e. zwp_input_popup_surface_v2 and
|
||||||
|
zwp_input_method_keyboard_grab_v2.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwp_input_popup_surface_v2" version="1">
|
||||||
|
<description summary="popup surface">
|
||||||
|
This interface marks a surface as a popup for interacting with an input
|
||||||
|
method.
|
||||||
|
|
||||||
|
The compositor should place it near the active text input area. It must
|
||||||
|
be visible if and only if the input method is in the active state.
|
||||||
|
|
||||||
|
The client must not destroy the underlying wl_surface while the
|
||||||
|
zwp_input_popup_surface_v2 object exists.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<event name="text_input_rectangle">
|
||||||
|
<description summary="set text input area position">
|
||||||
|
Notify about the position of the area of the text input expressed as a
|
||||||
|
rectangle in surface local coordinates.
|
||||||
|
|
||||||
|
This is a hint to the input method telling it the relative position of
|
||||||
|
the text being entered.
|
||||||
|
</description>
|
||||||
|
<arg name="x" type="int"/>
|
||||||
|
<arg name="y" type="int"/>
|
||||||
|
<arg name="width" type="int"/>
|
||||||
|
<arg name="height" type="int"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor"/>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwp_input_method_keyboard_grab_v2" version="1">
|
||||||
|
<!-- Closely follows wl_keyboard version 6 -->
|
||||||
|
<description summary="keyboard grab">
|
||||||
|
The zwp_input_method_keyboard_grab_v2 interface represents an exclusive
|
||||||
|
grab of the wl_keyboard interface associated with the seat.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<event name="keymap">
|
||||||
|
<description summary="keyboard mapping">
|
||||||
|
This event provides a file descriptor to the client which can be
|
||||||
|
memory-mapped to provide a keyboard mapping description.
|
||||||
|
</description>
|
||||||
|
<arg name="format" type="uint" enum="wl_keyboard.keymap_format"
|
||||||
|
summary="keymap format"/>
|
||||||
|
<arg name="fd" type="fd" summary="keymap file descriptor"/>
|
||||||
|
<arg name="size" type="uint" summary="keymap size, in bytes"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="key">
|
||||||
|
<description summary="key event">
|
||||||
|
A key was pressed or released.
|
||||||
|
The time argument is a timestamp with millisecond granularity, with an
|
||||||
|
undefined base.
|
||||||
|
</description>
|
||||||
|
<arg name="serial" type="uint" summary="serial number of the key event"/>
|
||||||
|
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||||
|
<arg name="key" type="uint" summary="key that produced the event"/>
|
||||||
|
<arg name="state" type="uint" enum="wl_keyboard.key_state"
|
||||||
|
summary="physical state of the key"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="modifiers">
|
||||||
|
<description summary="modifier and group state">
|
||||||
|
Notifies clients that the modifier and/or group state has changed, and
|
||||||
|
it should update its local state.
|
||||||
|
</description>
|
||||||
|
<arg name="serial" type="uint" summary="serial number of the modifiers event"/>
|
||||||
|
<arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
|
||||||
|
<arg name="mods_latched" type="uint" summary="latched modifiers"/>
|
||||||
|
<arg name="mods_locked" type="uint" summary="locked modifiers"/>
|
||||||
|
<arg name="group" type="uint" summary="keyboard layout"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="release" type="destructor">
|
||||||
|
<description summary="release the grab object"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="repeat_info">
|
||||||
|
<description summary="repeat rate and delay">
|
||||||
|
Informs the client about the keyboard's repeat rate and delay.
|
||||||
|
|
||||||
|
This event is sent as soon as the zwp_input_method_keyboard_grab_v2
|
||||||
|
object has been created, and is guaranteed to be received by the
|
||||||
|
client before any key press event.
|
||||||
|
|
||||||
|
Negative values for either rate or delay are illegal. A rate of zero
|
||||||
|
will disable any repeating (regardless of the value of delay).
|
||||||
|
|
||||||
|
This event can be sent later on as well with a new value if necessary,
|
||||||
|
so clients should continue listening for the event past the creation
|
||||||
|
of zwp_input_method_keyboard_grab_v2.
|
||||||
|
</description>
|
||||||
|
<arg name="rate" type="int"
|
||||||
|
summary="the rate of repeating keys in characters per second"/>
|
||||||
|
<arg name="delay" type="int"
|
||||||
|
summary="delay in milliseconds since key down until repeating starts"/>
|
||||||
|
</event>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwp_input_method_manager_v2" version="1">
|
||||||
|
<description summary="input method manager">
|
||||||
|
The input method manager allows the client to become the input method on
|
||||||
|
a chosen seat.
|
||||||
|
|
||||||
|
No more than one input method must be associated with any seat at any
|
||||||
|
given time.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="get_input_method">
|
||||||
|
<description summary="request an input method object">
|
||||||
|
Request a new input zwp_input_method_v2 object associated with a given
|
||||||
|
seat.
|
||||||
|
</description>
|
||||||
|
<arg name="seat" type="object" interface="wl_seat"/>
|
||||||
|
<arg name="input_method" type="new_id" interface="zwp_input_method_v2"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the input method manager">
|
||||||
|
Destroys the zwp_input_method_manager_v2 object.
|
||||||
|
|
||||||
|
The zwp_input_method_v2 objects originating from it remain valid.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
||||||
112
proto/virtual-keyboard-unstable-v1.xml
Normal file
112
proto/virtual-keyboard-unstable-v1.xml
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="virtual_keyboard_unstable_v1">
|
||||||
|
<copyright>
|
||||||
|
Copyright © 2008-2011 Kristian Høgsberg
|
||||||
|
Copyright © 2010-2013 Intel Corporation
|
||||||
|
Copyright © 2012-2013 Collabora, Ltd.
|
||||||
|
Copyright © 2018 Purism SPC
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next
|
||||||
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<interface name="zwp_virtual_keyboard_v1" version="1">
|
||||||
|
<description summary="virtual keyboard">
|
||||||
|
The virtual keyboard provides an application with requests which emulate
|
||||||
|
the behaviour of a physical keyboard.
|
||||||
|
|
||||||
|
This interface can be used by clients on its own to provide raw input
|
||||||
|
events, or it can accompany the input method protocol.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="keymap">
|
||||||
|
<description summary="keyboard mapping">
|
||||||
|
Provide a file descriptor to the compositor which can be
|
||||||
|
memory-mapped to provide a keyboard mapping description.
|
||||||
|
</description>
|
||||||
|
<arg name="format" type="uint" enum="wl_keyboard.keymap_format" summary="keymap format"/>
|
||||||
|
<arg name="fd" type="fd" summary="keymap file descriptor"/>
|
||||||
|
<arg name="size" type="uint" summary="keymap size, in bytes"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="no_keymap" value="0" summary="No keymap was set"/>
|
||||||
|
<entry name="invalid_keymap_format" value="1" summary="Invalid keymap format"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<request name="key">
|
||||||
|
<description summary="key event">
|
||||||
|
A key was pressed or released.
|
||||||
|
The time argument is a timestamp with millisecond granularity, with an
|
||||||
|
undefined base. All requests regarding a single object must share the
|
||||||
|
same clock.
|
||||||
|
|
||||||
|
Keymap must be set before issuing this request.
|
||||||
|
|
||||||
|
State carries a value from the key_state enumeration.
|
||||||
|
</description>
|
||||||
|
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||||
|
<arg name="key" type="uint" summary="key that produced the event"/>
|
||||||
|
<arg name="state" type="uint" summary="physical state of the key"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="modifiers">
|
||||||
|
<description summary="modifier and group state">
|
||||||
|
Notifies the compositor that the modifier and/or group state has
|
||||||
|
changed, and it should update state.
|
||||||
|
|
||||||
|
The client should use wl_keyboard.modifiers event to synchronize its
|
||||||
|
internal state with seat state.
|
||||||
|
|
||||||
|
Keymap must be set before issuing this request.
|
||||||
|
</description>
|
||||||
|
<arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
|
||||||
|
<arg name="mods_latched" type="uint" summary="latched modifiers"/>
|
||||||
|
<arg name="mods_locked" type="uint" summary="locked modifiers"/>
|
||||||
|
<arg name="group" type="uint" summary="keyboard layout"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor" since="1">
|
||||||
|
<description summary="destroy the virtual keyboard keyboard object"/>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwp_virtual_keyboard_manager_v1" version="1">
|
||||||
|
<description summary="virtual keyboard manager">
|
||||||
|
A virtual keyboard manager allows an application to provide keyboard
|
||||||
|
input events as if they came from a physical keyboard.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="unauthorized" value="0" summary="client not authorized to use the interface"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<request name="create_virtual_keyboard">
|
||||||
|
<description summary="Create a new virtual keyboard">
|
||||||
|
Creates a new virtual keyboard associated to a seat.
|
||||||
|
|
||||||
|
If the compositor enables a keyboard to perform arbitrary actions, it
|
||||||
|
should present an error when an untrusted client requests a new
|
||||||
|
keyboard.
|
||||||
|
</description>
|
||||||
|
<arg name="seat" type="object" interface="wl_seat"/>
|
||||||
|
<arg name="id" type="new_id" interface="zwp_virtual_keyboard_v1"/>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
||||||
322
run.sh
322
run.sh
@@ -1,326 +1,24 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
cd "$(dirname "$0")" || exit 1
|
cd "$(dirname "$0")" || exit 1
|
||||||
root=$(pwd -P) || exit 1
|
|
||||||
uid=$(id -u) || exit 1
|
|
||||||
|
|
||||||
if test "$#" -eq 0; then
|
if test "$#" -ne 0; then
|
||||||
set --
|
echo "usage: run.sh" >&2
|
||||||
for font in \
|
exit 1
|
||||||
/usr/share/fonts/TTF/DejaVuSans.ttf \
|
|
||||||
/usr/share/fonts/TTF/Jigmo.ttf \
|
|
||||||
/usr/share/fonts/TTF/Jigmo2.ttf \
|
|
||||||
/usr/share/fonts/noto/NotoSansCJK-Regular.ttc
|
|
||||||
do
|
|
||||||
if test -f "$font"; then
|
|
||||||
set -- "$@" "$font"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if test "$#" -eq 0; then
|
|
||||||
echo "run.sh: no default font found; pass a font file" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! test -x ./strans; then
|
if ! test -x ./strans; then
|
||||||
echo "run.sh: ./strans is not executable; run make first" >&2
|
echo "run.sh: ./strans is not executable; run make first" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if test -n "${DISPLAY-}" && ! test -x xim/strans-xim; then
|
if ! command -v pkill >/dev/null 2>&1; then
|
||||||
echo "run.sh: xim/strans-xim is not executable; run make first" >&2
|
echo "run.sh: pkill is required" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case ${XDG_RUNTIME_DIR-} in
|
uid=$(id -u) || exit 1
|
||||||
/) ipc=/strans.sock ;;
|
pkill -TERM -u "$uid" -x strans 2>/dev/null || :
|
||||||
/*/) ipc=${XDG_RUNTIME_DIR}strans.sock ;;
|
sleep 0.1
|
||||||
/*) ipc=${XDG_RUNTIME_DIR}/strans.sock ;;
|
pkill -KILL -u "$uid" -x strans 2>/dev/null || :
|
||||||
*) ipc=/tmp/strans.$(id -u) ;;
|
|
||||||
esac
|
|
||||||
if test -n "${XDG_CONFIG_HOME-}"; then
|
|
||||||
busdir=$XDG_CONFIG_HOME/ibus/bus
|
|
||||||
elif test "${HOME+x}" = x; then
|
|
||||||
busdir=$HOME/.config/ibus/bus
|
|
||||||
else
|
|
||||||
echo "run.sh: HOME or XDG_CONFIG_HOME is required for IBus discovery" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
strans_pid=
|
./strans map </dev/null &
|
||||||
xim_pid=
|
|
||||||
old_daemon_pid=
|
|
||||||
old_daemon_start=
|
|
||||||
old_launcher_pid=
|
|
||||||
old_launcher_start=
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
{
|
|
||||||
status=$?
|
|
||||||
trap - 0 1 2 15
|
|
||||||
if test -n "$xim_pid"; then
|
|
||||||
kill "$xim_pid" 2>/dev/null || :
|
|
||||||
wait "$xim_pid" 2>/dev/null || :
|
|
||||||
xim_pid=
|
|
||||||
fi
|
|
||||||
if test -n "$strans_pid"; then
|
|
||||||
kill "$strans_pid" 2>/dev/null || :
|
|
||||||
wait "$strans_pid" 2>/dev/null || :
|
|
||||||
strans_pid=
|
|
||||||
fi
|
|
||||||
exit "$status"
|
|
||||||
}
|
|
||||||
|
|
||||||
ibusready()
|
|
||||||
{
|
|
||||||
for address in "$busdir"/*; do
|
|
||||||
test -f "$address" || continue
|
|
||||||
foundpid=
|
|
||||||
foundaddress=
|
|
||||||
while IFS= read -r line; do
|
|
||||||
case $line in
|
|
||||||
IBUS_DAEMON_PID="$strans_pid") foundpid=1 ;;
|
|
||||||
IBUS_ADDRESS=unix:abstract=strans-"$strans_pid",*) foundaddress=1 ;;
|
|
||||||
esac
|
|
||||||
done <"$address"
|
|
||||||
if test -n "$foundpid" && test -n "$foundaddress"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
procuid()
|
|
||||||
{
|
|
||||||
{
|
|
||||||
while IFS=' ' read -r key real effective saved filesystem rest; do
|
|
||||||
case $key in
|
|
||||||
Uid:)
|
|
||||||
test "$real" = "$effective" &&
|
|
||||||
test "$real" = "$saved" &&
|
|
||||||
test "$real" = "$filesystem" || return 1
|
|
||||||
printf '%s\n' "$real"
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done <"/proc/$1/status"
|
|
||||||
} 2>/dev/null
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
procparent()
|
|
||||||
{
|
|
||||||
{
|
|
||||||
while IFS=' ' read -r key value rest; do
|
|
||||||
case $key in
|
|
||||||
PPid:) printf '%s\n' "$value"; return 0 ;;
|
|
||||||
esac
|
|
||||||
done <"/proc/$1/status"
|
|
||||||
} 2>/dev/null
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
procstart()
|
|
||||||
{
|
|
||||||
proc_stat=
|
|
||||||
{ IFS= read -r proc_stat <"/proc/$1/stat"; } 2>/dev/null || return 1
|
|
||||||
proc_stat=${proc_stat##*) }
|
|
||||||
set -- $proc_stat
|
|
||||||
test "$#" -ge 20 || return 1
|
|
||||||
test "$1" != Z || return 1
|
|
||||||
printf '%s\n' "${20}"
|
|
||||||
}
|
|
||||||
|
|
||||||
sameproc()
|
|
||||||
{
|
|
||||||
test -n "$2" || return 1
|
|
||||||
proc_now=$(procstart "$1") || return 1
|
|
||||||
test "$proc_now" = "$2"
|
|
||||||
}
|
|
||||||
|
|
||||||
listeninginode()
|
|
||||||
{
|
|
||||||
listener_inode=
|
|
||||||
listener_count=0
|
|
||||||
{
|
|
||||||
while IFS=' ' read -r num ref proto flags type state inode path; do
|
|
||||||
if test "$flags" = 00010000 && test "$type" = 0001 &&
|
|
||||||
test "$state" = 01 && test "$path" = "$1"; then
|
|
||||||
listener_inode=$inode
|
|
||||||
listener_count=$((listener_count + 1))
|
|
||||||
fi
|
|
||||||
done </proc/net/unix
|
|
||||||
} 2>/dev/null
|
|
||||||
test "$listener_count" -eq 1 || return 1
|
|
||||||
printf '%s\n' "$listener_inode"
|
|
||||||
}
|
|
||||||
|
|
||||||
socketowned()
|
|
||||||
{
|
|
||||||
socket_inode=$(listeninginode "$2") || return 1
|
|
||||||
for socket_fd in "/proc/$1/fd"/*; do
|
|
||||||
test "$(readlink "$socket_fd" 2>/dev/null)" = \
|
|
||||||
"socket:[$socket_inode]" && return 0
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
validdaemon()
|
|
||||||
{
|
|
||||||
test "$(procuid "$1")" = "$uid" || return 1
|
|
||||||
test "$(readlink "/proc/$1/cwd" 2>/dev/null)" = "$root" || return 1
|
|
||||||
daemon_exe=$(readlink "/proc/$1/exe" 2>/dev/null) || return 1
|
|
||||||
case $daemon_exe in
|
|
||||||
"$root/strans"|"$root/strans (deleted)") ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
test -S "$ipc" || return 1
|
|
||||||
socketowned "$1" "$ipc" || return 1
|
|
||||||
socketowned "$1" "@strans-$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
finddaemon()
|
|
||||||
{
|
|
||||||
old_daemon_pid=
|
|
||||||
old_daemon_start=
|
|
||||||
for address in "$busdir"/*; do
|
|
||||||
test -f "$address" || continue
|
|
||||||
address_pid=
|
|
||||||
address_ok=
|
|
||||||
while IFS= read -r line; do
|
|
||||||
case $line in
|
|
||||||
IBUS_DAEMON_PID=*) address_pid=${line#IBUS_DAEMON_PID=} ;;
|
|
||||||
esac
|
|
||||||
done <"$address"
|
|
||||||
case $address_pid in
|
|
||||||
''|*[!0-9]*) continue ;;
|
|
||||||
esac
|
|
||||||
while IFS= read -r line; do
|
|
||||||
case $line in
|
|
||||||
IBUS_ADDRESS=unix:abstract=strans-"$address_pid",*)
|
|
||||||
address_ok=1 ;;
|
|
||||||
esac
|
|
||||||
done <"$address"
|
|
||||||
test -n "$address_ok" || continue
|
|
||||||
validdaemon "$address_pid" || continue
|
|
||||||
address_start=$(procstart "$address_pid") || continue
|
|
||||||
validdaemon "$address_pid" || continue
|
|
||||||
old_daemon_pid=$address_pid
|
|
||||||
old_daemon_start=$address_start
|
|
||||||
return 0
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
findlauncher()
|
|
||||||
{
|
|
||||||
launcher_env=$({ tr '\000' '\n' <"/proc/$old_daemon_pid/environ"; } \
|
|
||||||
2>/dev/null) || return 1
|
|
||||||
old_launcher_pid=$(printf '%s\n' "$launcher_env" |
|
|
||||||
sed -n 's/^STRANS_RUN_OWNER_PID=//p')
|
|
||||||
old_launcher_start=$(printf '%s\n' "$launcher_env" |
|
|
||||||
sed -n 's/^STRANS_RUN_OWNER_START=//p')
|
|
||||||
case $old_launcher_pid in
|
|
||||||
''|*[!0-9]*|0|1|"$$") return 1 ;;
|
|
||||||
esac
|
|
||||||
case $old_launcher_start in
|
|
||||||
''|*[!0-9]*) return 1 ;;
|
|
||||||
esac
|
|
||||||
test "$(procuid "$old_launcher_pid")" = "$uid" || return 1
|
|
||||||
test "$(readlink "/proc/$old_launcher_pid/cwd" 2>/dev/null)" = "$root" ||
|
|
||||||
return 1
|
|
||||||
sameproc "$old_launcher_pid" "$old_launcher_start" || return 1
|
|
||||||
sameproc "$old_daemon_pid" "$old_daemon_start" || return 1
|
|
||||||
test "$(procparent "$old_daemon_pid")" = "$old_launcher_pid" || return 1
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
stoplauncher()
|
|
||||||
{
|
|
||||||
sameproc "$old_daemon_pid" "$old_daemon_start" || return 1
|
|
||||||
test "$(procparent "$old_daemon_pid")" = "$old_launcher_pid" || return 1
|
|
||||||
sameproc "$old_launcher_pid" "$old_launcher_start" || return 1
|
|
||||||
kill "$old_launcher_pid" 2>/dev/null || return 1
|
|
||||||
echo "run.sh: stopping previous launcher $old_launcher_pid (daemon $old_daemon_pid)"
|
|
||||||
|
|
||||||
stop_attempt=0
|
|
||||||
while test "$stop_attempt" -lt 10; do
|
|
||||||
if ! sameproc "$old_launcher_pid" "$old_launcher_start" &&
|
|
||||||
! sameproc "$old_daemon_pid" "$old_daemon_start"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
stop_attempt=$((stop_attempt + 1))
|
|
||||||
if test "$stop_attempt" -lt 10; then
|
|
||||||
sleep 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "run.sh: previous launcher did not stop within 10 seconds" >&2
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup 0
|
|
||||||
trap 'exit 129' 1
|
|
||||||
trap 'exit 130' 2
|
|
||||||
trap 'exit 143' 15
|
|
||||||
|
|
||||||
launcher_start=$(procstart "$$") || {
|
|
||||||
echo "run.sh: cannot read launcher process identity from /proc" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if finddaemon; then
|
|
||||||
if ! findlauncher; then
|
|
||||||
echo "run.sh: daemon $old_daemon_pid owns the current endpoints but has no" >&2
|
|
||||||
echo "run.sh: live run.sh owner; leaving it untouched (stop it once, then retry)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if ! stoplauncher; then
|
|
||||||
echo "run.sh: previous launcher did not stop cleanly; refusing to start another daemon" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
STRANS_RUN_OWNER_PID=$$ STRANS_RUN_OWNER_START=$launcher_start \
|
|
||||||
./strans map "$@" &
|
|
||||||
strans_pid=$!
|
|
||||||
|
|
||||||
attempt=0
|
|
||||||
while test "$attempt" -lt 10; do
|
|
||||||
if test -S "$ipc" && ibusready; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
if ! kill -0 "$strans_pid" 2>/dev/null; then
|
|
||||||
wait "$strans_pid"
|
|
||||||
status=$?
|
|
||||||
strans_pid=
|
|
||||||
if test "$status" -eq 0; then
|
|
||||||
status=1
|
|
||||||
fi
|
|
||||||
echo "run.sh: daemon exited before publishing IPC and IBus endpoints" >&2
|
|
||||||
exit "$status"
|
|
||||||
fi
|
|
||||||
attempt=$((attempt + 1))
|
|
||||||
if test "$attempt" -lt 10; then
|
|
||||||
sleep 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if test "$attempt" -eq 10; then
|
|
||||||
echo "run.sh: daemon did not publish IPC and IBus endpoints within 10 seconds" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "run.sh: daemon $strans_pid is ready"
|
|
||||||
if test -z "${DISPLAY-}"; then
|
|
||||||
echo "run.sh: DISPLAY is unset; XIM will not be started"
|
|
||||||
wait "$strans_pid"
|
|
||||||
status=$?
|
|
||||||
strans_pid=
|
|
||||||
exit "$status"
|
|
||||||
fi
|
|
||||||
|
|
||||||
xim/strans-xim &
|
|
||||||
xim_pid=$!
|
|
||||||
echo "run.sh: started XIM $xim_pid; press Ctrl-C to stop both processes"
|
|
||||||
wait "$xim_pid"
|
|
||||||
status=$?
|
|
||||||
xim_pid=
|
|
||||||
exit "$status"
|
|
||||||
|
|||||||
138
srv.c
138
srv.c
@@ -5,56 +5,122 @@
|
|||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
|
|
||||||
static char adir[256];
|
static char adir[256];
|
||||||
|
static char sockpath[sizeof(((struct sockaddr_un*)0)->sun_path)];
|
||||||
|
static dev_t sockdev;
|
||||||
|
static ino_t sockino;
|
||||||
static Channel *clientc;
|
static Channel *clientc;
|
||||||
|
|
||||||
static int
|
/* Removes the socket only while it is still the one we announced. */
|
||||||
srvreadkey(int fd, Keyreq *kr)
|
static void
|
||||||
|
srvunlink(void)
|
||||||
{
|
{
|
||||||
uchar req[Ipcreqsz];
|
struct stat st;
|
||||||
u32int ks, mod;
|
|
||||||
int want;
|
|
||||||
|
|
||||||
if(ipcreadn(fd, req, sizeof req) < 0)
|
if(sockpath[0] != '\0' && lstat(sockpath, &st) == 0 &&
|
||||||
return -1;
|
st.st_dev == sockdev && st.st_ino == sockino)
|
||||||
ipcunpackreq(req, &want, &mod, &ks);
|
unlink(sockpath);
|
||||||
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
|
sockpath[0] = '\0';
|
||||||
kr->ks = ks;
|
|
||||||
kr->mod = mod;
|
|
||||||
memset(&kr->caret, 0, sizeof kr->caret);
|
|
||||||
return want;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
srvnote(void *v, char *note)
|
||||||
|
{
|
||||||
|
USED(v);
|
||||||
|
if(notefatal(note))
|
||||||
|
srvunlink();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 0 for a request to make, 1 for a frame that only changes what rides
|
||||||
|
* along with the requests after it, -1 for anything else.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
srvreadreq(int fd, Keyreq *kr, int *want)
|
||||||
|
{
|
||||||
|
uchar req[Ipccaretsz];
|
||||||
|
char text[Ipcfieldmax];
|
||||||
|
int valid;
|
||||||
|
size_t n;
|
||||||
|
int32_t x, y, h;
|
||||||
|
|
||||||
|
if(ipcreadn(fd, req, Ipcreqsz) < 0)
|
||||||
|
return -1;
|
||||||
|
*want = 0;
|
||||||
|
kr->ks = 0;
|
||||||
|
kr->mod = 0;
|
||||||
|
switch(ipcreqtype(req)){
|
||||||
|
case Ipckey:
|
||||||
|
ipcunpackreq(req, want, &kr->mod, &kr->ks);
|
||||||
|
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
|
||||||
|
return 0;
|
||||||
|
case Ipccap:
|
||||||
|
*want = (req[0] & Ipcreqwant) != 0;
|
||||||
|
kr->op = Keycap;
|
||||||
|
return 0;
|
||||||
|
case Ipccaret:
|
||||||
|
if(ipcreadn(fd, req + Ipcreqsz, Ipccaretsz - Ipcreqsz) < 0 ||
|
||||||
|
ipcunpackcaret(req, &valid, &x, &y, &h) < 0)
|
||||||
|
return -1;
|
||||||
|
kr->op = Keycaret;
|
||||||
|
kr->caret.valid = valid;
|
||||||
|
kr->caret.x = x;
|
||||||
|
kr->caret.y = y;
|
||||||
|
kr->caret.h = h;
|
||||||
|
return 0;
|
||||||
|
case Ipcsurround:
|
||||||
|
n = ipcsurroundlen(req);
|
||||||
|
if(n > 0 && ipcreadn(fd, text, n) < 0)
|
||||||
|
return -1;
|
||||||
|
stail(&kr->surround, text, n);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One Keyreq persists for the connection: the negotiated preedit and
|
||||||
|
* the last caret ride along with every request, and the socket closing
|
||||||
|
* releases the engine.
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
clientthread(void *arg)
|
clientthread(void *arg)
|
||||||
{
|
{
|
||||||
Channel *reply;
|
|
||||||
int fd;
|
|
||||||
Keyreq kr;
|
Keyreq kr;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
uchar out[Ipcmaxresp];
|
uchar out[Ipcmaxresp], token;
|
||||||
char commit[Maxutf], preedit[Maxutf];
|
char commit[Maxutf], preedit[Maxutf];
|
||||||
int n, ncommit, npreedit, want;
|
int fd, n, ncommit, npreedit, rv, want;
|
||||||
uchar token;
|
|
||||||
|
|
||||||
fd = (int)(uintptr)arg;
|
fd = (int)(uintptr)arg;
|
||||||
threadsetname("client %d", fd);
|
threadsetname("client %d", fd);
|
||||||
reply = chancreate(sizeof(Keyres), 0);
|
memset(&kr, 0, sizeof kr);
|
||||||
kr.reply = reply;
|
kr.reply = chancreate(sizeof(Keyres), 0);
|
||||||
kr.owner = &fd;
|
kr.owner = &fd;
|
||||||
while((want = srvreadkey(fd, &kr)) >= 0){
|
kr.clientpre = 1;
|
||||||
|
while((rv = srvreadreq(fd, &kr, &want)) >= 0){
|
||||||
|
if(rv != 0)
|
||||||
|
continue;
|
||||||
|
if(kr.op != Keycaret)
|
||||||
|
kr.clientpre = want;
|
||||||
chansend(keyc, &kr);
|
chansend(keyc, &kr);
|
||||||
chanrecv(reply, &res);
|
chanrecv(kr.reply, &res);
|
||||||
|
if(kr.op == Keycaret)
|
||||||
|
continue;
|
||||||
ncommit = stoutf(&res.commit, commit, sizeof commit);
|
ncommit = stoutf(&res.commit, commit, sizeof commit);
|
||||||
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
|
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
|
||||||
n = ipcpackresp(out, sizeof out, res.eaten,
|
/* A capability reply is always eaten: it marks the extension. */
|
||||||
commit, ncommit, preedit, npreedit, want);
|
n = ipcpackresp(out, sizeof out, kr.op == Keycap || res.eaten,
|
||||||
|
res.del, commit, ncommit, preedit, npreedit, want);
|
||||||
if(n < 0 || ipcsend(fd, out, n) < 0)
|
if(n < 0 || ipcsend(fd, out, n) < 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
kr.op = Keyrelease;
|
kr.op = Keyrelease;
|
||||||
|
kr.ks = 0;
|
||||||
|
kr.mod = 0;
|
||||||
chansend(keyc, &kr);
|
chansend(keyc, &kr);
|
||||||
chanrecv(reply, &res);
|
chanrecv(kr.reply, &res);
|
||||||
chanfree(reply);
|
chanfree(kr.reply);
|
||||||
close(fd);
|
close(fd);
|
||||||
chanrecv(clientc, &token);
|
chanrecv(clientc, &token);
|
||||||
}
|
}
|
||||||
@@ -62,18 +128,23 @@ clientthread(void *arg)
|
|||||||
void
|
void
|
||||||
srvinit(void)
|
srvinit(void)
|
||||||
{
|
{
|
||||||
char *addr, path[sizeof(((struct sockaddr_un*)0)->sun_path)];
|
struct stat st;
|
||||||
|
char *addr;
|
||||||
|
|
||||||
if(ipcpath(path, sizeof path) < 0)
|
if(ipcpath(sockpath, sizeof sockpath) < 0)
|
||||||
die("IPC path is too long");
|
die("IPC path is too long");
|
||||||
addr = smprint("unix!%s", path);
|
addr = smprint("unix!%s", sockpath);
|
||||||
if(addr == nil)
|
if(addr == nil)
|
||||||
die("out of memory");
|
die("out of memory");
|
||||||
if(announce(addr, adir) < 0)
|
if(announce(addr, adir) < 0)
|
||||||
die("IPC endpoint is already in use: %r");
|
die("IPC endpoint is already in use: %r");
|
||||||
free(addr);
|
free(addr);
|
||||||
if(chmod(path, 0600) < 0)
|
if(chmod(sockpath, 0600) < 0 || lstat(sockpath, &st) < 0)
|
||||||
die("can't protect IPC endpoint: %s", path);
|
die("can't protect IPC endpoint: %s", sockpath);
|
||||||
|
sockdev = st.st_dev;
|
||||||
|
sockino = st.st_ino;
|
||||||
|
atexit(srvunlink);
|
||||||
|
threadnotify(srvnote, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -94,9 +165,6 @@ srvthread(void*)
|
|||||||
close(fd);
|
close(fd);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(proccreate(clientthread, (void*)(uintptr)fd, 8192) < 0){
|
proccreate(clientthread, (void*)(uintptr)fd, 8192);
|
||||||
chanrecv(clientc, &token);
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
str.c
41
str.c
@@ -1,24 +1,42 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
void
|
/* Fills s from n bytes of UTF-8; whole, valid, and at most Maxrunes.
|
||||||
|
* Anything else leaves s empty. */
|
||||||
|
int
|
||||||
sinit(Str *s, char *src, int n)
|
sinit(Str *s, char *src, int n)
|
||||||
{
|
{
|
||||||
|
Str tmp = {0};
|
||||||
|
Rune r;
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
s->n = 0;
|
s->n = 0;
|
||||||
if(n < 0 || (n > 0 && src == nil))
|
while(n > 0){
|
||||||
return;
|
if(tmp.n >= Maxrunes || !fullrune(src, n))
|
||||||
while(n > 0 && s->n < Maxrunes){
|
return 0;
|
||||||
if(!fullrune(src, n))
|
len = chartorune(&r, src);
|
||||||
break;
|
if((r == Runeerror && len == 1) || (r >= 0xd800 && r <= 0xdfff))
|
||||||
len = chartorune(&s->r[s->n], src);
|
return 0;
|
||||||
if(len > n)
|
tmp.r[tmp.n++] = r;
|
||||||
break;
|
|
||||||
s->n++;
|
|
||||||
src += len;
|
src += len;
|
||||||
n -= len;
|
n -= len;
|
||||||
}
|
}
|
||||||
|
*s = tmp;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The last runes of n bytes of UTF-8, as many as a Str holds. */
|
||||||
|
void
|
||||||
|
stail(Str *s, char *src, int n)
|
||||||
|
{
|
||||||
|
Rune r;
|
||||||
|
char *p;
|
||||||
|
int nr;
|
||||||
|
|
||||||
|
nr = utfnlen(src, n);
|
||||||
|
for(p = src; nr > Maxrunes; nr--)
|
||||||
|
p += chartorune(&r, p);
|
||||||
|
sinit(s, p, n - (p - src));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -66,14 +84,13 @@ scmp(Str *a, Str *b)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* UTF-8 of s into buf[sz], NUL-terminated, whole runes only. */
|
||||||
int
|
int
|
||||||
stoutf(Str *s, char *buf, int sz)
|
stoutf(Str *s, char *buf, int sz)
|
||||||
{
|
{
|
||||||
char tmp[UTFmax];
|
char tmp[UTFmax];
|
||||||
int i, n, len;
|
int i, n, len;
|
||||||
|
|
||||||
if(sz <= 0)
|
|
||||||
return 0;
|
|
||||||
n = 0;
|
n = 0;
|
||||||
for(i = 0; i < s->n; i++){
|
for(i = 0; i < s->n; i++){
|
||||||
len = runetochar(tmp, &s->r[i]);
|
len = runetochar(tmp, &s->r[i]);
|
||||||
|
|||||||
159
tests/Makefile
159
tests/Makefile
@@ -1,71 +1,150 @@
|
|||||||
CC = 9c
|
CC = 9c
|
||||||
LD = 9l
|
LD = 9l
|
||||||
HOSTCC = cc
|
HOSTCC = cc
|
||||||
CFLAGS = -std=c99 -Wall -Wextra -O2 -g -I.. -I../cutest
|
PKG_CONFIG ?= pkg-config
|
||||||
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
|
CFLAGS ?= -O2 -g
|
||||||
FT_LIBS = $(shell pkg-config --libs freetype2)
|
UNIT_CPPFLAGS = -I..
|
||||||
DBUS_CFLAGS = $(shell pkg-config --cflags dbus-1)
|
UNIT_CFLAGS = -Wall -Wextra
|
||||||
DBUS_LIBS = $(shell pkg-config --libs dbus-1)
|
HOST_CFLAGS = -std=c99 -Wall -Wextra
|
||||||
IBUS_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
|
TEXT_CFLAGS = $(shell $(PKG_CONFIG) --cflags pangocairo cairo fontconfig)
|
||||||
IBUS_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
|
TEXT_LIBS = $(shell $(PKG_CONFIG) --libs pangocairo cairo fontconfig)
|
||||||
LIBS = -lthread -lbio $(FT_LIBS) $(IBUS_LIBS)
|
DBUS_CFLAGS = $(shell $(PKG_CONFIG) --cflags dbus-1)
|
||||||
|
DBUS_LIBS = $(shell $(PKG_CONFIG) --libs dbus-1)
|
||||||
|
GTK_CFLAGS = $(shell $(PKG_CONFIG) --cflags gtk+-3.0)
|
||||||
|
GTK_LIBS = $(shell $(PKG_CONFIG) --libs gtk+-3.0)
|
||||||
|
IBUS_CFLAGS = $(shell $(PKG_CONFIG) --cflags dbus-1 xkbcommon)
|
||||||
|
IBUS_LIBS = $(shell $(PKG_CONFIG) --libs dbus-1 xkbcommon)
|
||||||
|
IBUS_CLIENT_CFLAGS = $(shell $(PKG_CONFIG) --cflags ibus-1.0)
|
||||||
|
IBUS_CLIENT_LIBS = $(shell $(PKG_CONFIG) --libs ibus-1.0)
|
||||||
|
X11_CFLAGS = $(shell $(PKG_CONFIG) --cflags x11)
|
||||||
|
X11_LIBS = $(shell $(PKG_CONFIG) --libs x11)
|
||||||
|
XIM_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
||||||
|
XIM_LIBS = $(shell $(PKG_CONFIG) --libs xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
||||||
|
WL_CFLAGS = $(shell $(PKG_CONFIG) --cflags wayland-client xkbcommon)
|
||||||
|
WL_LIBS = $(shell $(PKG_CONFIG) --libs wayland-client xkbcommon)
|
||||||
|
UNIT_LDLIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS) $(WL_LIBS)
|
||||||
|
|
||||||
PROG = unit_test
|
PROG = unit_test
|
||||||
LIVE = ibus_live_test ipc_live_test daemon_collision_test daemon_failure_test \
|
STRESS = stress_test
|
||||||
daemon_restart_test
|
LIVESRC = live.c live.h
|
||||||
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
LIVEBUSSRC = livebus.c livebus.h
|
||||||
|
SMOKE = ibus_live_test ibus_client_smoke gtk_live_test xim_live_test \
|
||||||
|
ipc_live_test
|
||||||
|
FAULT = daemon_collision_test daemon_failure_test daemon_restart_test
|
||||||
|
LIVE = $(SMOKE) $(FAULT)
|
||||||
|
TESTSRC = test_util.c str_test.c trie_test.c \
|
||||||
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
||||||
popup_test.c font_test.c ibus_test.c server_test.c
|
popup_test.c font_test.c ibus_test.c server_test.c compose_test.c \
|
||||||
|
xim_adapter_test.c wl_adapter_test.c
|
||||||
TESTOBJ = $(TESTSRC:.c=.o)
|
TESTOBJ = $(TESTSRC:.c=.o)
|
||||||
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
|
# imv2.c and vkv1.c are wayland-scanner's; the parent Makefile writes them.
|
||||||
font.c
|
PARENTSRC = str.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
|
||||||
|
font.c compose.c imv2.c vkv1.c
|
||||||
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
||||||
OBJS = $(TESTOBJ) $(PARENTOBJ)
|
COMMONOBJ = $(TESTOBJ) $(PARENTOBJ)
|
||||||
|
OBJS = unit_test.o stress_test.o $(COMMONOBJ)
|
||||||
|
|
||||||
all: $(PROG) $(LIVE)
|
all: $(PROG) $(STRESS) $(LIVE)
|
||||||
|
|
||||||
check test: $(PROG) $(LIVE)
|
check test: $(PROG)
|
||||||
./$(PROG) $(TESTARGS)
|
./$(PROG) $(UNITARGS)
|
||||||
./ibus_live_test ../strans ../map
|
|
||||||
|
check-live: $(SMOKE) ../strans ../gtk/im-strans.so
|
||||||
|
./ibus_live_test ../strans ../map ./ibus_client_smoke
|
||||||
|
./gtk_live_test ../gtk/im-strans.so
|
||||||
|
./xim_live_test ../strans ../map
|
||||||
./ipc_live_test ../strans ../map
|
./ipc_live_test ../strans ../map
|
||||||
|
|
||||||
|
check-stress: $(STRESS) ibus_live_test ipc_live_test $(FAULT) ../strans
|
||||||
|
./$(STRESS)
|
||||||
|
./ibus_live_test --capacity ../strans ../map
|
||||||
|
./ipc_live_test --capacity ../strans ../map
|
||||||
./daemon_collision_test ../strans ../map
|
./daemon_collision_test ../strans ../map
|
||||||
./daemon_failure_test ../strans
|
./daemon_failure_test ../strans
|
||||||
./daemon_restart_test ../strans ../map
|
./daemon_restart_test ../strans ../map
|
||||||
|
|
||||||
$(PROG): $(OBJS)
|
$(PROG): unit_test.o $(COMMONOBJ)
|
||||||
$(LD) -o $@ $(OBJS) $(LIBS)
|
$(LD) $(LDFLAGS) -o $@ unit_test.o $(COMMONOBJ) $(UNIT_LDLIBS) \
|
||||||
|
$(LDLIBS)
|
||||||
|
|
||||||
ibus_live_test: ibus_live_test.c
|
$(STRESS): stress_test.o $(COMMONOBJ)
|
||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g $(DBUS_CFLAGS) -o $@ $< $(DBUS_LIBS)
|
$(LD) $(LDFLAGS) -o $@ stress_test.o $(COMMONOBJ) $(UNIT_LDLIBS) \
|
||||||
|
$(LDLIBS)
|
||||||
|
|
||||||
ipc_live_test: ipc_live_test.c ../ipc.c ../ipc.h
|
stress_test.o: unit_test.c
|
||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. -o $@ ipc_live_test.c ../ipc.c
|
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -DSTRESS \
|
||||||
|
-c -o $@ $<
|
||||||
|
|
||||||
daemon_collision_test: daemon_collision_test.c ../ipc.c ../ipc.h
|
ibus_live_test: ibus_live_test.c $(LIVESRC) $(LIVEBUSSRC) ../ipc.c ../ipc.h
|
||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(DBUS_CFLAGS) -o $@ \
|
$(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
||||||
daemon_collision_test.c ../ipc.c $(DBUS_LIBS)
|
$(LDFLAGS) -o $@ ibus_live_test.c live.c livebus.c ../ipc.c \
|
||||||
|
$(DBUS_LIBS) $(LDLIBS)
|
||||||
|
|
||||||
daemon_failure_test: daemon_failure_test.c
|
ibus_client_smoke: ibus_client_smoke.c
|
||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -o $@ $<
|
$(HOSTCC) $(CPPFLAGS) $(IBUS_CLIENT_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
||||||
|
$(LDFLAGS) -o $@ $< $(IBUS_CLIENT_LIBS) $(LDLIBS)
|
||||||
|
|
||||||
daemon_restart_test: daemon_restart_test.c ../ipc.c ../ipc.h
|
gtk_live_test: gtk_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(DBUS_CFLAGS) -o $@ \
|
$(HOSTCC) $(CPPFLAGS) -I.. $(GTK_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
||||||
daemon_restart_test.c ../ipc.c $(DBUS_LIBS)
|
$(LDFLAGS) -o $@ gtk_live_test.c live.c ../ipc.c $(GTK_LIBS) \
|
||||||
|
-pthread $(LDLIBS)
|
||||||
|
|
||||||
$(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h ../cutest/cutest.h
|
# The parent make knows what these are built from; always ask it.
|
||||||
|
../gtk/im-strans.so:
|
||||||
|
$(MAKE) -C ../gtk
|
||||||
|
|
||||||
|
../strans:
|
||||||
|
$(MAKE) -C .. strans
|
||||||
|
|
||||||
|
../imv2.c ../imv2.h ../vkv1.c ../vkv1.h:
|
||||||
|
$(MAKE) -C .. $(@F)
|
||||||
|
|
||||||
|
xim_live_test: xim_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
||||||
|
$(HOSTCC) $(CPPFLAGS) -I.. $(X11_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
||||||
|
$(LDFLAGS) -o $@ xim_live_test.c live.c ../ipc.c $(X11_LIBS) \
|
||||||
|
$(LDLIBS)
|
||||||
|
|
||||||
|
ipc_live_test: ipc_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
||||||
|
$(HOSTCC) $(CPPFLAGS) -I.. $(HOST_CFLAGS) $(CFLAGS) $(LDFLAGS) \
|
||||||
|
-o $@ ipc_live_test.c live.c ../ipc.c $(LDLIBS)
|
||||||
|
|
||||||
|
daemon_collision_test: daemon_collision_test.c $(LIVESRC) $(LIVEBUSSRC) \
|
||||||
|
../ipc.c ../ipc.h
|
||||||
|
$(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
||||||
|
$(LDFLAGS) -o $@ daemon_collision_test.c live.c livebus.c \
|
||||||
|
../ipc.c $(DBUS_LIBS) $(LDLIBS)
|
||||||
|
|
||||||
|
daemon_failure_test: daemon_failure_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
||||||
|
$(HOSTCC) $(CPPFLAGS) -I.. $(HOST_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ \
|
||||||
|
daemon_failure_test.c live.c ../ipc.c $(LDLIBS)
|
||||||
|
|
||||||
|
daemon_restart_test: daemon_restart_test.c $(LIVESRC) $(LIVEBUSSRC) \
|
||||||
|
../ipc.c ../ipc.h
|
||||||
|
$(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
||||||
|
$(LDFLAGS) -o $@ daemon_restart_test.c live.c livebus.c ../ipc.c \
|
||||||
|
$(DBUS_LIBS) $(LDLIBS)
|
||||||
|
|
||||||
|
unit_test.o stress_test.o $(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h \
|
||||||
|
../cutest/cutest.h
|
||||||
engine_test.o: ../strans.c
|
engine_test.o: ../strans.c
|
||||||
ibus_test.o: CFLAGS += $(IBUS_CFLAGS)
|
ibus_test.o: UNIT_CPPFLAGS += $(IBUS_CFLAGS)
|
||||||
ibus_test.o: ../ibus.c
|
ibus_test.o: ../ibus.c
|
||||||
|
xim_adapter_test.o: UNIT_CPPFLAGS += $(XIM_CFLAGS)
|
||||||
|
xim_adapter_test.o: ../xim.c
|
||||||
|
wl_adapter_test.o unit_imv2.o unit_vkv1.o: UNIT_CPPFLAGS += $(WL_CFLAGS)
|
||||||
|
wl_adapter_test.o: ../wl.c ../imv2.h ../vkv1.h
|
||||||
server_test.o: ../srv.c
|
server_test.o: ../srv.c
|
||||||
unit_font.o: CFLAGS += $(FT_CFLAGS)
|
unit_font.o: UNIT_CPPFLAGS += $(TEXT_CFLAGS)
|
||||||
|
unit_compose.o compose_test.o: UNIT_CPPFLAGS += $(IBUS_CFLAGS)
|
||||||
|
|
||||||
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJS) $(PROG) $(LIVE)
|
rm -f $(OBJS) $(PROG) $(STRESS) $(LIVE)
|
||||||
|
|
||||||
.PHONY: all check test clean
|
.PHONY: all check check-live check-stress test clean \
|
||||||
|
../strans ../gtk/im-strans.so
|
||||||
|
|||||||
56
tests/compose_test.c
Normal file
56
tests/compose_test.c
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
|
#include "test.h"
|
||||||
|
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A sequence swallows its keys and hands its text back at the end, and
|
||||||
|
* belongs to the one context, in the one frontend, that started it.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
compose_sequences(struct ct *t)
|
||||||
|
{
|
||||||
|
static int a, b; /* two input contexts, told apart by address */
|
||||||
|
static const struct {
|
||||||
|
int who;
|
||||||
|
void *owner;
|
||||||
|
u32int sym;
|
||||||
|
int composing;
|
||||||
|
char *text;
|
||||||
|
} keys[] = {
|
||||||
|
{ Composexim, &a, XKB_KEY_a, 0, "" },
|
||||||
|
{ Composexim, &a, XKB_KEY_dead_acute, 1, "" },
|
||||||
|
{ Composexim, &a, XKB_KEY_e, 0, "é" },
|
||||||
|
{ Composexim, &a, XKB_KEY_Multi_key, 1, "" },
|
||||||
|
{ Composexim, &a, XKB_KEY_o, 1, "" },
|
||||||
|
{ Composexim, &a, XKB_KEY_c, 0, "©" },
|
||||||
|
{ Composexim, &a, XKB_KEY_dead_acute, 1, "" },
|
||||||
|
{ Composexim, &a, XKB_KEY_Escape, 1, "" },
|
||||||
|
{ Composexim, &a, XKB_KEY_a, 0, "" },
|
||||||
|
/* another context of the same frontend starts over */
|
||||||
|
{ Composexim, &a, XKB_KEY_dead_acute, 1, "" },
|
||||||
|
{ Composexim, &b, XKB_KEY_e, 0, "" },
|
||||||
|
/* another frontend does not touch this one's sequence */
|
||||||
|
{ Composexim, &a, XKB_KEY_dead_acute, 1, "" },
|
||||||
|
{ Composewl, &a, XKB_KEY_o, 0, "" },
|
||||||
|
{ Composexim, &a, XKB_KEY_e, 0, "é" },
|
||||||
|
};
|
||||||
|
char buf[Maxutf];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if(!CT_CHECK(t, setenv("XCOMPOSEFILE", "data/compose", 1) == 0))
|
||||||
|
return;
|
||||||
|
composeinit();
|
||||||
|
for(i = 0; i < nelem(keys); i++){
|
||||||
|
CT_EQ_INT(t, keys[i].composing, composekey(keys[i].who,
|
||||||
|
keys[i].owner, keys[i].sym, buf, sizeof buf));
|
||||||
|
CT_EQ_STR(t, keys[i].text, buf);
|
||||||
|
}
|
||||||
|
/* a context that goes takes its half-typed sequence with it */
|
||||||
|
CT_EQ_INT(t, 1,
|
||||||
|
composekey(Composewl, &a, XKB_KEY_dead_acute, buf, sizeof buf));
|
||||||
|
composedrop(Composewl, &a);
|
||||||
|
CT_EQ_INT(t, 0, composekey(Composewl, &a, XKB_KEY_e, buf, sizeof buf));
|
||||||
|
CT_EQ_STR(t, "", buf);
|
||||||
|
unsetenv("XCOMPOSEFILE");
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,635 +1,138 @@
|
|||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <signal.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/inotify.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "live.h"
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Failtimeout = 8000,
|
Failtimeout = 4000,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Test Test;
|
|
||||||
|
|
||||||
struct Test
|
|
||||||
{
|
|
||||||
pid_t child;
|
|
||||||
int errfd;
|
|
||||||
int notifyfd;
|
|
||||||
int runtimewd;
|
|
||||||
int buswd;
|
|
||||||
int endpointclean;
|
|
||||||
char root[256];
|
|
||||||
char runtime[320];
|
|
||||||
char config[320];
|
|
||||||
char ibus[384];
|
|
||||||
char bus[448];
|
|
||||||
char home[320];
|
|
||||||
char socket[384];
|
|
||||||
char badmap[320];
|
|
||||||
char missingfont[384];
|
|
||||||
char expected[768];
|
|
||||||
char err[4096];
|
|
||||||
size_t nerr;
|
|
||||||
};
|
|
||||||
|
|
||||||
static uint32_t endpointmask = IN_CREATE|IN_MOVED_TO|IN_MOVED_FROM|IN_ATTRIB|
|
|
||||||
IN_DELETE|IN_MODIFY|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF;
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
fail(char *fmt, ...)
|
waitfailed(Daemon *d, char *badmap)
|
||||||
{
|
{
|
||||||
va_list ap;
|
struct pollfd pfd;
|
||||||
|
int n, status;
|
||||||
fprintf(stderr, "daemon_failure_test: ");
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
fputc('\n', stderr);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t
|
|
||||||
nowms(void)
|
|
||||||
{
|
|
||||||
struct timespec ts;
|
|
||||||
|
|
||||||
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
|
||||||
return -1;
|
|
||||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
leftms(int64_t deadline)
|
|
||||||
{
|
|
||||||
int64_t n;
|
|
||||||
|
|
||||||
n = nowms();
|
|
||||||
if(n < 0)
|
|
||||||
return -1;
|
|
||||||
n = deadline - n;
|
|
||||||
if(n <= 0)
|
|
||||||
return 0;
|
|
||||||
if(n > 0x7fffffff)
|
|
||||||
return 0x7fffffff;
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
makedir(char *path)
|
|
||||||
{
|
|
||||||
if(mkdir(path, 0700) == 0)
|
|
||||||
return 1;
|
|
||||||
return fail("mkdir %s: %s", path, strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
readerrors(Test *t)
|
|
||||||
{
|
|
||||||
ssize_t n;
|
|
||||||
|
|
||||||
if(t->errfd < 0)
|
|
||||||
return;
|
|
||||||
while(t->nerr + 1 < sizeof t->err){
|
|
||||||
n = read(t->errfd, t->err + t->nerr,
|
|
||||||
sizeof t->err - t->nerr - 1);
|
|
||||||
if(n > 0){
|
|
||||||
t->nerr += n;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
t->err[t->nerr] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
showerrors(Test *t)
|
|
||||||
{
|
|
||||||
readerrors(t);
|
|
||||||
if(t->nerr != 0)
|
|
||||||
fprintf(stderr, "daemon_failure_test: daemon stderr:\n%s", t->err);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
setup(Test *t)
|
|
||||||
{
|
|
||||||
memset(t, 0, sizeof *t);
|
|
||||||
t->child = -1;
|
|
||||||
t->errfd = -1;
|
|
||||||
t->notifyfd = -1;
|
|
||||||
t->runtimewd = -1;
|
|
||||||
t->buswd = -1;
|
|
||||||
t->endpointclean = 1;
|
|
||||||
if(nowms() < 0)
|
|
||||||
return fail("read monotonic clock: %s", strerror(errno));
|
|
||||||
snprintf(t->root, sizeof t->root, "/tmp/strans-failure.XXXXXX");
|
|
||||||
if(mkdtemp(t->root) == NULL){
|
|
||||||
t->root[0] = '\0';
|
|
||||||
return fail("mkdtemp: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
if(snprintf(t->runtime, sizeof t->runtime, "%s/runtime", t->root)
|
|
||||||
>= (int)sizeof t->runtime ||
|
|
||||||
snprintf(t->config, sizeof t->config, "%s/config", t->root)
|
|
||||||
>= (int)sizeof t->config ||
|
|
||||||
snprintf(t->ibus, sizeof t->ibus, "%s/ibus", t->config)
|
|
||||||
>= (int)sizeof t->ibus ||
|
|
||||||
snprintf(t->bus, sizeof t->bus, "%s/bus", t->ibus)
|
|
||||||
>= (int)sizeof t->bus ||
|
|
||||||
snprintf(t->home, sizeof t->home, "%s/home", t->root)
|
|
||||||
>= (int)sizeof t->home ||
|
|
||||||
snprintf(t->socket, sizeof t->socket, "%s/strans.sock", t->runtime)
|
|
||||||
>= (int)sizeof t->socket ||
|
|
||||||
snprintf(t->badmap, sizeof t->badmap, "%s/missing-map", t->root)
|
|
||||||
>= (int)sizeof t->badmap ||
|
|
||||||
snprintf(t->missingfont, sizeof t->missingfont,
|
|
||||||
"%s/missing-font.ttf", t->root) >= (int)sizeof t->missingfont ||
|
|
||||||
snprintf(t->expected, sizeof t->expected,
|
|
||||||
"strans: can't open: %s/hira.map\n", t->badmap)
|
|
||||||
>= (int)sizeof t->expected)
|
|
||||||
return fail("temporary path is too long");
|
|
||||||
if(!makedir(t->runtime) || !makedir(t->config) || !makedir(t->ibus) ||
|
|
||||||
!makedir(t->bus) || !makedir(t->home))
|
|
||||||
return 0;
|
|
||||||
t->notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
|
|
||||||
if(t->notifyfd < 0)
|
|
||||||
return fail("inotify_init1: %s", strerror(errno));
|
|
||||||
t->runtimewd = inotify_add_watch(t->notifyfd, t->runtime, endpointmask);
|
|
||||||
if(t->runtimewd < 0)
|
|
||||||
return fail("watch runtime directory: %s", strerror(errno));
|
|
||||||
t->buswd = inotify_add_watch(t->notifyfd, t->bus, endpointmask);
|
|
||||||
if(t->buswd < 0)
|
|
||||||
return fail("watch IBus directory: %s", strerror(errno));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
startchild(Test *t, char *program)
|
|
||||||
{
|
|
||||||
int errpipe[2], fd;
|
|
||||||
long maxfd;
|
|
||||||
pid_t pid;
|
|
||||||
|
|
||||||
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0)
|
|
||||||
return fail("pipe2: %s", strerror(errno));
|
|
||||||
pid = fork();
|
|
||||||
if(pid < 0){
|
|
||||||
close(errpipe[0]);
|
|
||||||
close(errpipe[1]);
|
|
||||||
return fail("fork: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
if(pid == 0){
|
|
||||||
close(errpipe[0]);
|
|
||||||
if(dup2(errpipe[1], STDERR_FILENO) < 0)
|
|
||||||
_exit(126);
|
|
||||||
close(errpipe[1]);
|
|
||||||
if(close_range(3, UINT_MAX, 0) < 0){
|
|
||||||
maxfd = sysconf(_SC_OPEN_MAX);
|
|
||||||
if(maxfd < 0)
|
|
||||||
maxfd = 1024;
|
|
||||||
for(fd = 3; fd < maxfd; fd++)
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
if(setenv("XDG_RUNTIME_DIR", t->runtime, 1) < 0 ||
|
|
||||||
setenv("XDG_CONFIG_HOME", t->config, 1) < 0 ||
|
|
||||||
setenv("HOME", t->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
|
||||||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
|
||||||
unsetenv("IBUS_ADDRESS") < 0){
|
|
||||||
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
|
|
||||||
strerror(errno));
|
|
||||||
_exit(126);
|
|
||||||
}
|
|
||||||
execl(program, program, t->badmap, t->missingfont, (char*)0);
|
|
||||||
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
|
||||||
_exit(127);
|
|
||||||
}
|
|
||||||
close(errpipe[1]);
|
|
||||||
t->child = pid;
|
|
||||||
t->errfd = errpipe[0];
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
drainnotify(Test *t)
|
|
||||||
{
|
|
||||||
char buf[4096];
|
|
||||||
char *where;
|
|
||||||
struct inotify_event *ev;
|
|
||||||
ssize_t n;
|
|
||||||
size_t off;
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
ok = 1;
|
|
||||||
for(;;){
|
|
||||||
n = read(t->notifyfd, buf, sizeof buf);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n < 0 && errno == EAGAIN)
|
|
||||||
return ok;
|
|
||||||
if(n < 0)
|
|
||||||
return fail("read endpoint watches: %s", strerror(errno));
|
|
||||||
if(n == 0)
|
|
||||||
return ok;
|
|
||||||
for(off = 0; off + sizeof *ev <= (size_t)n;
|
|
||||||
off += sizeof *ev + ev->len){
|
|
||||||
ev = (struct inotify_event*)(buf + off);
|
|
||||||
if(off + sizeof *ev + ev->len > (size_t)n)
|
|
||||||
return fail("truncated inotify event");
|
|
||||||
if(ev->mask & IN_Q_OVERFLOW){
|
|
||||||
t->endpointclean = 0;
|
|
||||||
fail("endpoint watch queue overflowed");
|
|
||||||
ok = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(ev->mask & (IN_IGNORED|IN_UNMOUNT)){
|
|
||||||
t->endpointclean = 0;
|
|
||||||
fail("endpoint watch became invalid: %#x", ev->mask);
|
|
||||||
ok = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(ev->wd != t->runtimewd && ev->wd != t->buswd)
|
|
||||||
continue;
|
|
||||||
if(!(ev->mask & endpointmask))
|
|
||||||
continue;
|
|
||||||
where = ev->wd == t->runtimewd ? "runtime" : "IBus";
|
|
||||||
t->endpointclean = 0;
|
|
||||||
fail("unexpected %s endpoint event %#x for %s", where,
|
|
||||||
ev->mask, ev->len != 0 ? ev->name : "directory");
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(off != (size_t)n)
|
|
||||||
return fail("truncated inotify event buffer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
killowned(Test *t, int *status)
|
|
||||||
{
|
|
||||||
pid_t pid;
|
|
||||||
int n, ok;
|
|
||||||
|
|
||||||
if(t->child <= 0)
|
|
||||||
return 1;
|
|
||||||
pid = t->child;
|
|
||||||
ok = 1;
|
|
||||||
if(kill(pid, SIGKILL) < 0 && errno != ESRCH){
|
|
||||||
fail("kill -9 %ld: %s", (long)pid, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
do
|
|
||||||
n = waitpid(pid, status, 0);
|
|
||||||
while(n < 0 && errno == EINTR);
|
|
||||||
if(n != pid){
|
|
||||||
fail("reap %ld after SIGKILL: %s", (long)pid,
|
|
||||||
n < 0 ? strerror(errno) : "wrong child");
|
|
||||||
ok = 0;
|
|
||||||
}else
|
|
||||||
t->child = -1;
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
waitfailed(Test *t)
|
|
||||||
{
|
|
||||||
struct pollfd pfd[2];
|
|
||||||
pid_t pid;
|
|
||||||
int n, ok, status, timeout;
|
|
||||||
int64_t deadline;
|
int64_t deadline;
|
||||||
|
|
||||||
pid = t->child;
|
status = 0;
|
||||||
ok = 1;
|
deadline = nowms() + Failtimeout;
|
||||||
deadline = nowms();
|
|
||||||
if(deadline < 0){
|
|
||||||
fail("read monotonic clock before waiting for daemon: %s",
|
|
||||||
strerror(errno));
|
|
||||||
killowned(t, &status);
|
|
||||||
readerrors(t);
|
|
||||||
drainnotify(t);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
deadline += Failtimeout;
|
|
||||||
for(;;){
|
for(;;){
|
||||||
do
|
n = waitpid(d->pid, &status, WNOHANG);
|
||||||
n = waitpid(pid, &status, WNOHANG);
|
if(n == d->pid){
|
||||||
while(n < 0 && errno == EINTR);
|
d->pid = -1;
|
||||||
if(n == pid){
|
readerrors(d);
|
||||||
t->child = -1;
|
|
||||||
readerrors(t);
|
|
||||||
if(!drainnotify(t))
|
|
||||||
ok = 0;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(n < 0){
|
|
||||||
fail("waitpid failed daemon %ld: %s", (long)pid,
|
|
||||||
strerror(errno));
|
|
||||||
if(errno == ECHILD){
|
|
||||||
t->child = -1;
|
|
||||||
readerrors(t);
|
|
||||||
drainnotify(t);
|
|
||||||
}else{
|
|
||||||
killowned(t, &status);
|
|
||||||
readerrors(t);
|
|
||||||
drainnotify(t);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
timeout = leftms(deadline);
|
|
||||||
if(timeout < 0){
|
|
||||||
fail("read monotonic clock while waiting for daemon: %s",
|
|
||||||
strerror(errno));
|
|
||||||
killowned(t, &status);
|
|
||||||
readerrors(t);
|
|
||||||
drainnotify(t);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if(timeout == 0){
|
|
||||||
fail("daemon %ld did not exit after map initialization failure",
|
|
||||||
(long)pid);
|
|
||||||
killowned(t, &status);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
pfd[0].fd = t->notifyfd;
|
|
||||||
pfd[0].events = POLLIN;
|
|
||||||
pfd[0].revents = 0;
|
|
||||||
pfd[1].fd = t->errfd;
|
|
||||||
pfd[1].events = POLLIN|POLLHUP;
|
|
||||||
pfd[1].revents = 0;
|
|
||||||
n = poll(pfd, 2, timeout);
|
|
||||||
if(n < 0 && errno == EINTR)
|
if(n < 0 && errno == EINTR)
|
||||||
continue;
|
continue;
|
||||||
if(n < 0){
|
if(n < 0){
|
||||||
fail("poll failed daemon: %s", strerror(errno));
|
if(errno == ECHILD)
|
||||||
killowned(t, &status);
|
d->pid = -1;
|
||||||
readerrors(t);
|
return fail("waitpid: %s", strerror(errno));
|
||||||
drainnotify(t);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
if(pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL)){
|
n = leftms(deadline);
|
||||||
fail("endpoint watch became unusable: %#x", pfd[0].revents);
|
if(n == 0)
|
||||||
killowned(t, &status);
|
return fail("daemon did not exit after map initialization failure");
|
||||||
readerrors(t);
|
pfd.fd = d->errfd;
|
||||||
drainnotify(t);
|
pfd.events = POLLIN|POLLHUP;
|
||||||
return 0;
|
pfd.revents = 0;
|
||||||
}
|
n = poll(&pfd, 1, n);
|
||||||
if(pfd[1].revents & (POLLERR|POLLNVAL)){
|
if(n < 0 && errno == EINTR)
|
||||||
fail("daemon stderr pipe became unusable: %#x",
|
continue;
|
||||||
pfd[1].revents);
|
if(n < 0)
|
||||||
killowned(t, &status);
|
return fail("poll daemon: %s", strerror(errno));
|
||||||
readerrors(t);
|
if(n > 0)
|
||||||
drainnotify(t);
|
readerrors(d);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if((pfd[0].revents & POLLIN) && !drainnotify(t))
|
|
||||||
ok = 0;
|
|
||||||
if(pfd[1].revents & (POLLIN|POLLHUP))
|
|
||||||
readerrors(t);
|
|
||||||
}
|
}
|
||||||
if(!WIFEXITED(status) || WEXITSTATUS(status) != 1)
|
if(!WIFEXITED(status) || WEXITSTATUS(status) == 0)
|
||||||
return fail("daemon map failure had wait status %#x", status);
|
return fail("daemon map failure had wait status %#x", status);
|
||||||
if(strcmp(t->err, t->expected) != 0)
|
if(strstr(d->err, "can't open") == NULL || strstr(d->err, badmap) == NULL)
|
||||||
return fail("daemon did not report the expected map initialization failure");
|
return fail("daemon did not report the missing map directory");
|
||||||
return ok && t->endpointclean;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
dirempty(char *path, char *which)
|
emptydir(char *path)
|
||||||
{
|
{
|
||||||
DIR *dir;
|
DIR *dir;
|
||||||
struct dirent *de;
|
struct dirent *de;
|
||||||
int count, ok;
|
int empty;
|
||||||
|
|
||||||
dir = opendir(path);
|
dir = opendir(path);
|
||||||
if(dir == NULL)
|
if(dir == NULL)
|
||||||
return fail("open %s directory %s: %s", which, path, strerror(errno));
|
return fail("open %s: %s", path, strerror(errno));
|
||||||
count = 0;
|
empty = 1;
|
||||||
ok = 1;
|
|
||||||
errno = 0;
|
errno = 0;
|
||||||
while((de = readdir(dir)) != NULL){
|
while((de = readdir(dir)) != NULL)
|
||||||
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
if(strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0){
|
||||||
continue;
|
fail("unexpected endpoint %s/%s", path, de->d_name);
|
||||||
count++;
|
empty = 0;
|
||||||
fail("unexpected %s directory entry %s", which, de->d_name);
|
}
|
||||||
ok = 0;
|
|
||||||
errno = 0;
|
|
||||||
}
|
|
||||||
if(errno != 0){
|
if(errno != 0){
|
||||||
fail("read %s directory %s: %s", which, path, strerror(errno));
|
fail("read %s: %s", path, strerror(errno));
|
||||||
ok = 0;
|
empty = 0;
|
||||||
}
|
}
|
||||||
if(closedir(dir) < 0){
|
if(closedir(dir) < 0){
|
||||||
fail("close %s directory %s: %s", which, path, strerror(errno));
|
fail("close %s: %s", path, strerror(errno));
|
||||||
ok = 0;
|
empty = 0;
|
||||||
}
|
}
|
||||||
if(count != 0)
|
return empty;
|
||||||
ok = 0;
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
checkendpoints(Test *t)
|
checkendpoints(Live *l)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
int ok;
|
|
||||||
|
|
||||||
ok = 1;
|
if(lstat(l->socket, &st) == 0)
|
||||||
errno = 0;
|
return fail("IPC endpoint exists after failed startup: %s", l->socket);
|
||||||
if(lstat(t->socket, &st) == 0){
|
if(errno != ENOENT)
|
||||||
fail("IPC endpoint exists after failed startup: %s", t->socket);
|
return fail("lstat IPC endpoint: %s", strerror(errno));
|
||||||
ok = 0;
|
return emptydir(l->runtime) && emptydir(l->bus);
|
||||||
}else if(errno != ENOENT){
|
|
||||||
fail("lstat failed IPC endpoint: %s", strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(!dirempty(t->runtime, "runtime"))
|
|
||||||
ok = 0;
|
|
||||||
if(!dirempty(t->bus, "IBus"))
|
|
||||||
ok = 0;
|
|
||||||
return ok && t->endpointclean;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int clearfd(int);
|
|
||||||
|
|
||||||
static int
|
|
||||||
removeentry(int dirfd, char *name)
|
|
||||||
{
|
|
||||||
struct stat st;
|
|
||||||
int fd, ok;
|
|
||||||
|
|
||||||
if(fstatat(dirfd, name, &st, AT_SYMLINK_NOFOLLOW) < 0)
|
|
||||||
return fail("stat cleanup entry %s: %s", name, strerror(errno));
|
|
||||||
if(!S_ISDIR(st.st_mode)){
|
|
||||||
if(unlinkat(dirfd, name, 0) == 0 || errno == ENOENT)
|
|
||||||
return 1;
|
|
||||||
return fail("remove cleanup entry %s: %s", name, strerror(errno));
|
|
||||||
}
|
|
||||||
fd = openat(dirfd, name, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
|
|
||||||
if(fd < 0)
|
|
||||||
return fail("open cleanup directory %s: %s", name, strerror(errno));
|
|
||||||
ok = clearfd(fd);
|
|
||||||
if(close(fd) < 0){
|
|
||||||
fail("close cleanup directory %s: %s", name, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(unlinkat(dirfd, name, AT_REMOVEDIR) < 0 && errno != ENOENT){
|
|
||||||
fail("remove cleanup directory %s: %s", name, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
clearfd(int fd)
|
|
||||||
{
|
|
||||||
DIR *dir;
|
|
||||||
struct dirent *de;
|
|
||||||
int copy, ok;
|
|
||||||
|
|
||||||
copy = dup(fd);
|
|
||||||
if(copy < 0)
|
|
||||||
return fail("duplicate cleanup directory: %s", strerror(errno));
|
|
||||||
dir = fdopendir(copy);
|
|
||||||
if(dir == NULL){
|
|
||||||
close(copy);
|
|
||||||
return fail("open cleanup directory stream: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
ok = 1;
|
|
||||||
errno = 0;
|
|
||||||
while((de = readdir(dir)) != NULL){
|
|
||||||
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
|
||||||
continue;
|
|
||||||
if(!removeentry(fd, de->d_name))
|
|
||||||
ok = 0;
|
|
||||||
errno = 0;
|
|
||||||
}
|
|
||||||
if(errno != 0){
|
|
||||||
fail("read cleanup directory: %s", strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(closedir(dir) < 0){
|
|
||||||
fail("close cleanup directory stream: %s", strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
removeroot(Test *t)
|
|
||||||
{
|
|
||||||
int fd, ok;
|
|
||||||
|
|
||||||
if(t->root[0] == '\0')
|
|
||||||
return 1;
|
|
||||||
fd = open(t->root, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
|
|
||||||
if(fd < 0)
|
|
||||||
return errno == ENOENT || fail("open cleanup root %s: %s", t->root,
|
|
||||||
strerror(errno));
|
|
||||||
ok = clearfd(fd);
|
|
||||||
if(close(fd) < 0){
|
|
||||||
fail("close cleanup root %s: %s", t->root, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(rmdir(t->root) < 0 && errno != ENOENT){
|
|
||||||
fail("rmdir %s: %s", t->root, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
cleanup(Test *t)
|
|
||||||
{
|
|
||||||
int n, ok, status;
|
|
||||||
|
|
||||||
ok = 1;
|
|
||||||
if(t->child > 0){
|
|
||||||
do
|
|
||||||
n = waitpid(t->child, &status, WNOHANG);
|
|
||||||
while(n < 0 && errno == EINTR);
|
|
||||||
if(n == t->child)
|
|
||||||
t->child = -1;
|
|
||||||
else if(n == 0){
|
|
||||||
fail("failed-start daemon still running during cleanup");
|
|
||||||
ok = 0;
|
|
||||||
if(!killowned(t, &status))
|
|
||||||
ok = 0;
|
|
||||||
}else{
|
|
||||||
fail("check failed-start daemon during cleanup: %s",
|
|
||||||
strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
if(errno == ECHILD)
|
|
||||||
t->child = -1;
|
|
||||||
else if(!killowned(t, &status))
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
readerrors(t);
|
|
||||||
if(t->errfd >= 0){
|
|
||||||
if(close(t->errfd) < 0){
|
|
||||||
fail("close daemon stderr: %s", strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
t->errfd = -1;
|
|
||||||
}
|
|
||||||
if(t->notifyfd >= 0){
|
|
||||||
if(t->runtimewd >= 0 &&
|
|
||||||
inotify_rm_watch(t->notifyfd, t->runtimewd) < 0 && errno != EINVAL){
|
|
||||||
fail("remove runtime watch: %s", strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(t->buswd >= 0 &&
|
|
||||||
inotify_rm_watch(t->notifyfd, t->buswd) < 0 && errno != EINVAL){
|
|
||||||
fail("remove IBus watch: %s", strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(close(t->notifyfd) < 0){
|
|
||||||
fail("close endpoint watches: %s", strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
t->notifyfd = -1;
|
|
||||||
}
|
|
||||||
if(!removeroot(t))
|
|
||||||
ok = 0;
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
Test test;
|
Daemon daemon;
|
||||||
|
Live live;
|
||||||
|
char badmap[320];
|
||||||
int ok;
|
int ok;
|
||||||
|
|
||||||
|
testname = "daemon_failure_test";
|
||||||
if(argc != 2){
|
if(argc != 2){
|
||||||
fprintf(stderr, "usage: daemon_failure_test strans\n");
|
fprintf(stderr, "usage: daemon_failure_test strans\n");
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
ok = setup(&test);
|
daemoninit(&daemon, "daemon");
|
||||||
|
ok = livesetup(&live, "failure");
|
||||||
|
if(ok && snprintf(badmap, sizeof badmap, "%s/missing-map", live.root)
|
||||||
|
>= (int)sizeof badmap)
|
||||||
|
ok = fail("temporary path is too long");
|
||||||
if(ok)
|
if(ok)
|
||||||
ok = startchild(&test, argv[1]);
|
ok = startdaemon(&live, &daemon, argv[1], badmap);
|
||||||
if(ok){
|
if(ok)
|
||||||
ok = waitfailed(&test);
|
ok = waitfailed(&daemon, badmap) && checkendpoints(&live);
|
||||||
if(test.child <= 0 && !checkendpoints(&test))
|
if(!killdaemon(&daemon, NULL))
|
||||||
ok = 0;
|
ok = 0;
|
||||||
}
|
if(!closeerrors(&daemon))
|
||||||
if(!cleanup(&test))
|
ok = 0;
|
||||||
|
if(!liveclean(&live))
|
||||||
ok = 0;
|
ok = 0;
|
||||||
if(!ok){
|
if(!ok){
|
||||||
showerrors(&test);
|
showerrors(&daemon);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
printf("failed daemon startup published no endpoints: ok\n");
|
printf("failed daemon startup leaves no endpoints: ok\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
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
|
||||||
かんじ 漢字 幹事 感じ
|
かんじ 漢字 幹事 感じ
|
||||||
えがお 笑顔
|
えがお 笑顔
|
||||||
きごう 記号 普通
|
きごう 記号 普通
|
||||||
|
|||||||
|
@@ -1,35 +1,28 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
dictionary_candidates(struct ct *t)
|
dictionary_candidates(struct ct *t)
|
||||||
{
|
{
|
||||||
char many[512], item[8];
|
char many[8*(Maxkouho+1)], item[8];
|
||||||
char *p;
|
char *p;
|
||||||
Dictreq req;
|
Str kouho[Maxkouho], key;
|
||||||
Dictres res;
|
Trie *saved;
|
||||||
Hmap *saved;
|
|
||||||
Lang *lang;
|
Lang *lang;
|
||||||
Str key;
|
int i, n;
|
||||||
int i;
|
|
||||||
|
|
||||||
lang = getlang(LangJP);
|
lang = getlang(LangJP);
|
||||||
saved = lang->dict;
|
saved = lang->dict;
|
||||||
lang->dict = hmapalloc(1);
|
lang->dict = trienew();
|
||||||
|
trieput(lang->dict, "かな", strlen("かな"), " 候補1 候補2 ",
|
||||||
|
strlen(" 候補1 候補2 "));
|
||||||
key = mkstr("かな");
|
key = mkstr("かな");
|
||||||
hmapset(&lang->dict, &key, " 候補1 かな 候補2 ",
|
n = dictlookup(lang->dict, &key, kouho, Maxkouho);
|
||||||
strlen(" 候補1 かな 候補2 "));
|
if(CT_EQ_INT(t, 2, n)){
|
||||||
memset(&req, 0, sizeof req);
|
checkstr(t, "candidate 1", "候補1", &kouho[0]);
|
||||||
req.key = key;
|
checkstr(t, "candidate 2", "候補2", &kouho[1]);
|
||||||
req.pre = mkstr("preedit-one");
|
}
|
||||||
req.lang = LangJP;
|
|
||||||
dictlookup(&req, &res);
|
|
||||||
if(!CT_EQ_INT(t, 2, res.nkouho))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_INT(t, LangJP, res.lang);
|
|
||||||
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
|
|
||||||
checkstr(t, "candidate 1", "候補1", &res.kouho[0]);
|
|
||||||
checkstr(t, "candidate 2", "候補2", &res.kouho[1]);
|
|
||||||
key = mkstr("key");
|
|
||||||
p = many;
|
p = many;
|
||||||
for(i = 0; i < Maxkouho+1; i++){
|
for(i = 0; i < Maxkouho+1; i++){
|
||||||
snprint(item, sizeof item, "c%02d", i);
|
snprint(item, sizeof item, "c%02d", i);
|
||||||
@@ -39,78 +32,57 @@ dictionary_candidates(struct ct *t)
|
|||||||
p += strlen(item);
|
p += strlen(item);
|
||||||
}
|
}
|
||||||
*p = '\0';
|
*p = '\0';
|
||||||
hmapset(&lang->dict, &key, many, strlen(many));
|
trieput(lang->dict, "key", 3, many, strlen(many));
|
||||||
req.key = key;
|
key = mkstr("key");
|
||||||
req.pre = mkstr("preedit-two");
|
n = dictlookup(lang->dict, &key, kouho, Maxkouho);
|
||||||
dictlookup(&req, &res);
|
if(CT_EQ_INT(t, Maxkouho, n)){
|
||||||
if(!CT_EQ_INT(t, Maxkouho, res.nkouho))
|
checkstr(t, "first capped candidate", "c00", &kouho[0]);
|
||||||
goto cleanup;
|
snprint(item, sizeof item, "c%02d", Maxkouho-1);
|
||||||
CT_EQ_INT(t, LangJP, res.lang);
|
checkstr(t, "last capped candidate", item, &kouho[Maxkouho-1]);
|
||||||
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
|
}
|
||||||
checkstr(t, "first capped candidate", "c00", &res.kouho[0]);
|
CT_EQ_INT(t, 3, dictlookup(lang->dict, &key, kouho, 3));
|
||||||
checkstr(t, "last capped candidate", "c31", &res.kouho[31]);
|
trieclose(lang->dict);
|
||||||
cleanup:
|
|
||||||
hmapfree(lang->dict);
|
|
||||||
lang->dict = saved;
|
lang->dict = saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
dictionary_misses_clear_result(struct ct *t)
|
dictionary_misses(struct ct *t)
|
||||||
{
|
{
|
||||||
static const struct {
|
Str kouho[Maxkouho], key;
|
||||||
char *name;
|
Trie *saved;
|
||||||
char *key;
|
|
||||||
char *pre;
|
|
||||||
} cases[] = {
|
|
||||||
{ "empty key", "", "empty-preedit" },
|
|
||||||
{ "missing key", "missing", "missing-preedit" },
|
|
||||||
};
|
|
||||||
Dictreq req;
|
|
||||||
Dictres res;
|
|
||||||
Hmap *saved;
|
|
||||||
Lang *lang;
|
Lang *lang;
|
||||||
int i;
|
|
||||||
|
|
||||||
lang = getlang(LangJP);
|
lang = getlang(LangJP);
|
||||||
saved = lang->dict;
|
saved = lang->dict;
|
||||||
lang->dict = hmapalloc(1);
|
lang->dict = trienew();
|
||||||
for(i = 0; i < nelem(cases); i++){
|
key = mkstr("");
|
||||||
memset(&res, 0xa5, sizeof res);
|
CT_EQ_INT(t, 0, dictlookup(lang->dict, &key, kouho, Maxkouho));
|
||||||
req.key = mkstr(cases[i].key);
|
key = mkstr("missing");
|
||||||
req.pre = mkstr(cases[i].pre);
|
CT_EQ_INT(t, 0, dictlookup(lang->dict, &key, kouho, Maxkouho));
|
||||||
req.lang = LangJP;
|
CT_EQ_INT(t, 0, dictlookup(getlang(LangKO)->dict, &key, kouho, Maxkouho));
|
||||||
dictlookup(&req, &res);
|
trieclose(lang->dict);
|
||||||
if(res.nkouho != 0)
|
|
||||||
CT_ERRORF(t, "%s: want 0 candidates, got %d",
|
|
||||||
cases[i].name, res.nkouho);
|
|
||||||
CT_EQ_INT(t, LangJP, res.lang);
|
|
||||||
checkstr(t, cases[i].name, cases[i].pre, &res.key);
|
|
||||||
}
|
|
||||||
hmapfree(lang->dict);
|
|
||||||
lang->dict = saved;
|
lang->dict = saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
dictionary_emoji_identity(struct ct *t)
|
dictionary_prefix(struct ct *t)
|
||||||
{
|
{
|
||||||
Dictreq req;
|
Str kouho[Maxkouho], key;
|
||||||
Dictres res;
|
Trie *dict;
|
||||||
Hmap *saved;
|
|
||||||
Lang *lang;
|
|
||||||
Str key;
|
|
||||||
|
|
||||||
lang = getlang(LangEMOJI);
|
dict = trienew();
|
||||||
saved = lang->dict;
|
trieput(dict, "smile", 5, "A B", 3);
|
||||||
lang->dict = hmapalloc(2);
|
trieput(dict, "smiley", 6, "B C", 3);
|
||||||
key = mkstr("é");
|
trieput(dict, "sad", 3, "D", 1);
|
||||||
hmapset(&lang->dict, &key, "é", strlen("é"));
|
key = mkstr("smile");
|
||||||
memset(&req, 0, sizeof req);
|
if(CT_EQ_INT(t, 4, dictprefix(dict, &key, kouho, Maxkouho))){
|
||||||
req.key = key;
|
checkstr(t, "own entry first", "A", &kouho[0]);
|
||||||
req.pre = key;
|
checkstr(t, "entry below", "C", &kouho[3]);
|
||||||
req.lang = LangEMOJI;
|
}
|
||||||
dictlookup(&req, &res);
|
key = mkstr("s");
|
||||||
if(CT_EQ_INT(t, 1, res.nkouho))
|
CT_EQ_INT(t, 5, dictprefix(dict, &key, kouho, Maxkouho));
|
||||||
checkstr(t, "emoji identity candidate", "é", &res.kouho[0]);
|
CT_EQ_INT(t, 2, dictprefix(dict, &key, kouho, 2));
|
||||||
hmapfree(lang->dict);
|
key = mkstr("x");
|
||||||
lang->dict = saved;
|
CT_EQ_INT(t, 0, dictprefix(dict, &key, kouho, Maxkouho));
|
||||||
|
trieclose(dict);
|
||||||
}
|
}
|
||||||
|
|||||||
1487
tests/engine_test.c
1487
tests/engine_test.c
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,18 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Guard = 8,
|
Guard = 8,
|
||||||
Testw = 3 * Fontsz,
|
Rgbmask = 0xffffff,
|
||||||
Testh = 3 * Fontsz,
|
|
||||||
Testn = Testw * Testh,
|
|
||||||
Missingn = Fontsz * Fontsz,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define Testw (6 * Fontsz)
|
||||||
|
#define Testh (3 * Fontsz)
|
||||||
|
#define Testn (Testw * Testh)
|
||||||
|
|
||||||
static u32int guardcolor = 0x5a5a5a5a;
|
static u32int guardcolor = 0x5a5a5a5a;
|
||||||
static char *testfonts[] = {
|
|
||||||
"/usr/share/fonts/TTF/DejaVuSans.ttf",
|
|
||||||
"/usr/share/fonts/TTF/Jigmo.ttf",
|
|
||||||
"/usr/share/fonts/TTF/Jigmo2.ttf",
|
|
||||||
};
|
|
||||||
static char *missingfonts[] = {
|
|
||||||
"/strans-test-no-such-font",
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
fillpixels(u32int *p, int n, u32int color)
|
fillpixels(u32int *p, int n, u32int color)
|
||||||
@@ -29,38 +24,84 @@ fillpixels(u32int *p, int n, u32int color)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
hasink(u32int *p, int n)
|
hasink(u32int *p, int n, u32int bg)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
bg &= Rgbmask;
|
||||||
for(i = 0; i < n; i++)
|
for(i = 0; i < n; i++)
|
||||||
if(p[i] != Colbg)
|
if((p[i] & Rgbmask) != bg)
|
||||||
return 1;
|
return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
renders(Rune r)
|
hascolors(u32int *p, int n, u32int bg)
|
||||||
{
|
{
|
||||||
u32int buf[Fontsz * Fontsz];
|
u32int c, first;
|
||||||
|
int b, found, g, i, r;
|
||||||
|
|
||||||
fillpixels(buf, nelem(buf), Colbg);
|
bg &= Rgbmask;
|
||||||
putfont(buf, Fontsz, Fontsz, 0, 0, r);
|
first = 0;
|
||||||
return hasink(buf, nelem(buf));
|
found = 0;
|
||||||
|
for(i = 0; i < n; i++){
|
||||||
|
c = p[i] & Rgbmask;
|
||||||
|
if(c == bg)
|
||||||
|
continue;
|
||||||
|
r = c >> 16 & 0xff;
|
||||||
|
g = c >> 8 & 0xff;
|
||||||
|
b = c & 0xff;
|
||||||
|
if(r == g && g == b)
|
||||||
|
continue;
|
||||||
|
if(!found){
|
||||||
|
first = c;
|
||||||
|
found = 1;
|
||||||
|
}else if(c != first)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
checkguards(struct ct *t, u32int *mem)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < Guard; i++){
|
||||||
|
CT_EQ_UINT(t, guardcolor, mem[i]);
|
||||||
|
CT_EQ_UINT(t, guardcolor, mem[Guard + Testn + i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
checkoutside(struct ct *t, u32int *buf, u32int bg, int row)
|
||||||
|
{
|
||||||
|
int x, y, y0, y1;
|
||||||
|
|
||||||
|
bg &= Rgbmask;
|
||||||
|
y0 = max(row, 0);
|
||||||
|
y1 = min(row + Fontsz, Testh);
|
||||||
|
for(y = 0; y < Testh; y++){
|
||||||
|
if(y >= y0 && y < y1)
|
||||||
|
continue;
|
||||||
|
for(x = 0; x < Testw; x++)
|
||||||
|
CT_EQ_UINT(t, bg, buf[y * Testw + x] & Rgbmask);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
inkbounds(u32int *p, int *minx, int *miny, int *maxx, int *maxy)
|
inkbounds(u32int *p, u32int bg, int *minx, int *miny, int *maxx, int *maxy)
|
||||||
{
|
{
|
||||||
int x, y;
|
int x, y;
|
||||||
|
|
||||||
|
bg &= Rgbmask;
|
||||||
*minx = Testw;
|
*minx = Testw;
|
||||||
*miny = Testh;
|
*miny = Testh;
|
||||||
*maxx = -1;
|
*maxx = -1;
|
||||||
*maxy = -1;
|
*maxy = -1;
|
||||||
for(y = 0; y < Testh; y++){
|
for(y = 0; y < Testh; y++){
|
||||||
for(x = 0; x < Testw; x++){
|
for(x = 0; x < Testw; x++){
|
||||||
if(p[y * Testw + x] == Colbg)
|
if((p[y * Testw + x] & Rgbmask) == bg)
|
||||||
continue;
|
continue;
|
||||||
*minx = min(*minx, x);
|
*minx = min(*minx, x);
|
||||||
*miny = min(*miny, y);
|
*miny = min(*miny, y);
|
||||||
@@ -72,86 +113,145 @@ inkbounds(u32int *p, int *minx, int *miny, int *maxx, int *maxy)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
checkclip(struct ct *t, u32int *ref, int dx, int dy)
|
checktext(struct ct *t, u32int *buf, char *utf)
|
||||||
{
|
{
|
||||||
u32int *buf, *mem, *want;
|
Str s;
|
||||||
int i, sx, sy, x, y;
|
int maxx, maxy, minx, miny, w;
|
||||||
|
|
||||||
|
s = mkstr(utf);
|
||||||
|
w = textwidth(&s);
|
||||||
|
CT_CHECK(t, w > 0);
|
||||||
|
CT_CHECK(t, w < Testw - Fontsz);
|
||||||
|
fillpixels(buf, Testn, Colbg);
|
||||||
|
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &s);
|
||||||
|
if(!CT_CHECK(t, inkbounds(buf, Colbg, &minx, &miny, &maxx, &maxy)))
|
||||||
|
return;
|
||||||
|
CT_CHECK(t, minx >= 0 && miny >= 0);
|
||||||
|
CT_CHECK(t, maxx < Testw && maxy < Testh);
|
||||||
|
CT_CHECK(t, maxx - minx + 1 <= w);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
checkcolor(struct ct *t, u32int *buf, char *utf)
|
||||||
|
{
|
||||||
|
Str s;
|
||||||
|
int w;
|
||||||
|
|
||||||
|
s = mkstr(utf);
|
||||||
|
w = textwidth(&s);
|
||||||
|
CT_CHECK(t, w > 0);
|
||||||
|
if(s.n > 1)
|
||||||
|
CT_CHECK(t, w < s.n * Fontsz);
|
||||||
|
fillpixels(buf, Testn, Colbg);
|
||||||
|
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &s);
|
||||||
|
CT_CHECK(t, hasink(buf, Testn, Colbg));
|
||||||
|
CT_CHECK(t, hascolors(buf, Testn, Colbg));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
checkshape(struct ct *t, u32int *buf, char *utf)
|
||||||
|
{
|
||||||
|
Str s;
|
||||||
|
int w;
|
||||||
|
|
||||||
|
s = mkstr(utf);
|
||||||
|
w = textwidth(&s);
|
||||||
|
CT_CHECK(t, w > 0);
|
||||||
|
CT_CHECK(t, w < s.n * Fontsz);
|
||||||
|
fillpixels(buf, Testn, Colbg);
|
||||||
|
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &s);
|
||||||
|
CT_CHECK(t, hasink(buf, Testn, Colbg));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
checkclip(struct ct *t, u32int *mem, int x, int y, Str *s)
|
||||||
|
{
|
||||||
|
u32int *buf;
|
||||||
|
|
||||||
mem = emalloc((Testn + 2 * Guard) * sizeof mem[0]);
|
|
||||||
want = emalloc(Testn * sizeof want[0]);
|
|
||||||
fillpixels(mem, Testn + 2 * Guard, guardcolor);
|
|
||||||
buf = mem + Guard;
|
buf = mem + Guard;
|
||||||
fillpixels(buf, Testn, Colbg);
|
fillpixels(buf, Testn, Colbg);
|
||||||
fillpixels(want, Testn, Colbg);
|
textdraw(buf, Testw, Testh, x, y, Testw, Colfg, s);
|
||||||
for(y = 0; y < Testh; y++){
|
CT_CHECK(t, hasink(buf, Testn, Colbg));
|
||||||
for(x = 0; x < Testw; x++){
|
checkoutside(t, buf, Colbg, y);
|
||||||
sx = x - dx;
|
checkguards(t, mem);
|
||||||
sy = y - dy;
|
|
||||||
if(sx >= 0 && sx < Testw && sy >= 0 && sy < Testh)
|
|
||||||
want[y * Testw + x] = ref[sy * Testw + sx];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
putfont(buf, Testw, Testh, Fontsz + dx, Fontsz + dy, 'A');
|
|
||||||
CT_CHECK(t, hasink(buf, Testn));
|
|
||||||
CT_EQ_MEM(t, want, buf, Testn * sizeof buf[0]);
|
|
||||||
for(i = 0; i < Guard; i++){
|
|
||||||
CT_EQ_UINT(t, guardcolor, mem[i]);
|
|
||||||
CT_EQ_UINT(t, guardcolor, mem[Guard + Testn + i]);
|
|
||||||
}
|
|
||||||
free(want);
|
|
||||||
free(mem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
font_render(struct ct *t)
|
font_render(struct ct *t)
|
||||||
{
|
{
|
||||||
u32int *buf, *missing, *ref;
|
static char *plain[] = { "A", "가", "あ", "漢", "☆", "𠀋" };
|
||||||
int i, minx, miny, maxx, maxy;
|
u32int *buf, *mem;
|
||||||
|
Str a, heart, longrow, missing;
|
||||||
|
int i, maxx, maxy, minx, miny, natural, w, x, y;
|
||||||
|
|
||||||
missing = emalloc((Missingn + 2 * Guard) * sizeof missing[0]);
|
mem = emalloc((Testn + 2 * Guard) * sizeof mem[0]);
|
||||||
fillpixels(missing, Missingn + 2 * Guard, guardcolor);
|
fillpixels(mem, Testn + 2 * Guard, guardcolor);
|
||||||
buf = missing + Guard;
|
buf = mem + Guard;
|
||||||
fillpixels(buf, Missingn, Colbg);
|
fillpixels(buf, Testn, Colbg);
|
||||||
CT_CHECK(t, !fontinit(missingfonts, nelem(missingfonts)));
|
a = mkstr("A");
|
||||||
putfont(buf, Fontsz, Fontsz, 0, 0, 'A');
|
textinit();
|
||||||
for(i = 0; i < Missingn; i++)
|
for(i = 0; i < nelem(plain); i++)
|
||||||
CT_EQ_UINT(t, Colbg, buf[i]);
|
checktext(t, buf, plain[i]);
|
||||||
for(i = 0; i < Guard; i++){
|
|
||||||
CT_EQ_UINT(t, guardcolor, missing[i]);
|
|
||||||
CT_EQ_UINT(t, guardcolor, missing[Guard + Missingn + i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!CT_CHECK(t, fontinit(testfonts, nelem(testfonts)))){
|
checkcolor(t, buf, "😀");
|
||||||
free(missing);
|
checkcolor(t, buf, "❤️");
|
||||||
return;
|
checkcolor(t, buf, "☕️");
|
||||||
}
|
checkshape(t, buf, "👩💻");
|
||||||
CT_CHECK(t, renders('A'));
|
checkshape(t, buf, "👍🏽");
|
||||||
CT_CHECK(t, renders(0xac00));
|
checkshape(t, buf, "🇰🇷");
|
||||||
CT_CHECK(t, renders(0x4e00));
|
checkshape(t, buf, "☕︎");
|
||||||
CT_CHECK(t, renders(0x1f600));
|
|
||||||
CT_CHECK(t, renders(0x2000b));
|
|
||||||
fillpixels(missing, Missingn + 2 * Guard, guardcolor);
|
|
||||||
buf = missing + Guard;
|
|
||||||
fillpixels(buf, Missingn, Colbg);
|
|
||||||
putfont(buf, Fontsz, Fontsz, 0, 0, 0x10ffff);
|
|
||||||
for(i = 0; i < Missingn; i++)
|
|
||||||
CT_EQ_UINT(t, Colbg, buf[i]);
|
|
||||||
for(i = 0; i < Guard; i++){
|
|
||||||
CT_EQ_UINT(t, guardcolor, missing[i]);
|
|
||||||
CT_EQ_UINT(t, guardcolor, missing[Guard + Missingn + i]);
|
|
||||||
}
|
|
||||||
free(missing);
|
|
||||||
|
|
||||||
ref = emalloc(Testn * sizeof ref[0]);
|
heart = mkstr("❤️");
|
||||||
fillpixels(ref, Testn, Colbg);
|
fillpixels(buf, Testn, Colsel);
|
||||||
putfont(ref, Testw, Testh, Fontsz, Fontsz, 'A');
|
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &heart);
|
||||||
if(!CT_CHECK(t, inkbounds(ref, &minx, &miny, &maxx, &maxy))){
|
CT_CHECK(t, hascolors(buf, Testn, Colsel));
|
||||||
free(ref);
|
CT_EQ_UINT(t, Colsel,
|
||||||
return;
|
buf[(Fontsz + Fontsz/2) * Testw + Testw - 1] & Rgbmask);
|
||||||
|
checkoutside(t, buf, Colsel, Fontsz);
|
||||||
|
checkguards(t, mem);
|
||||||
|
|
||||||
|
w = textwidth(&a);
|
||||||
|
checkclip(t, mem, -w / 2, Fontsz, &a);
|
||||||
|
checkclip(t, mem, Testw - w / 2, Fontsz, &a);
|
||||||
|
checkclip(t, mem, Fontsz, -Fontsz / 2, &a);
|
||||||
|
checkclip(t, mem, Fontsz, Testh - Fontsz / 2, &a);
|
||||||
|
|
||||||
|
longrow = mkstr("abcdefghijklmnopqrstuvwxyz");
|
||||||
|
natural = textwidth(&longrow);
|
||||||
|
CT_CHECK(t, natural > 2*Fontsz);
|
||||||
|
fillpixels(buf, Testn, Colbg);
|
||||||
|
textdraw(buf, Testw, Testh, 2*Fontsz, Fontsz, 2*Fontsz, Colfg,
|
||||||
|
&longrow);
|
||||||
|
CT_CHECK(t, hasink(buf, Testn, Colbg));
|
||||||
|
for(y = Fontsz; y < 2*Fontsz; y++){
|
||||||
|
for(x = 0; x < 2*Fontsz; x++)
|
||||||
|
CT_EQ_UINT(t, Colbg, buf[y * Testw + x] & Rgbmask);
|
||||||
|
for(x = 4*Fontsz; x < Testw; x++)
|
||||||
|
CT_EQ_UINT(t, Colbg, buf[y * Testw + x] & Rgbmask);
|
||||||
}
|
}
|
||||||
checkclip(t, ref, -minx - 1, 0);
|
CT_EQ_INT(t, natural, textwidth(&longrow));
|
||||||
checkclip(t, ref, Testw - maxx, 0);
|
|
||||||
checkclip(t, ref, 0, -miny - 1);
|
fillpixels(buf, Testn, Colsel);
|
||||||
checkclip(t, ref, 0, Testh - maxy);
|
textdraw(buf, Testw, Testh, 2*Fontsz, Fontsz, 2*Fontsz, Colselfg,
|
||||||
free(ref);
|
&longrow);
|
||||||
|
CT_CHECK(t, (Colselfg & Rgbmask) != (Colsel & Rgbmask));
|
||||||
|
if(CT_CHECK(t, inkbounds(buf, Colsel,
|
||||||
|
&minx, &miny, &maxx, &maxy))){
|
||||||
|
CT_CHECK(t, minx >= 2*Fontsz);
|
||||||
|
CT_CHECK(t, maxx < 4*Fontsz);
|
||||||
|
CT_CHECK(t, miny >= Fontsz);
|
||||||
|
CT_CHECK(t, maxy < 2*Fontsz);
|
||||||
|
}
|
||||||
|
checkoutside(t, buf, Colsel, Fontsz);
|
||||||
|
checkguards(t, mem);
|
||||||
|
CT_EQ_INT(t, natural, textwidth(&longrow));
|
||||||
|
|
||||||
|
missing = mkstr("\xF4\x8F\xBF\xBF");
|
||||||
|
for(i = 0; i < 32; i++){
|
||||||
|
textdraw(buf, Testw, Testh, Fontsz, Fontsz, Testw, Colfg, &heart);
|
||||||
|
textdraw(buf, Testw, Testh, 0, 0, Testw, Colfg, &missing);
|
||||||
|
}
|
||||||
|
checkguards(t, mem);
|
||||||
|
free(mem);
|
||||||
|
textclose();
|
||||||
}
|
}
|
||||||
|
|||||||
1015
tests/gtk_live_test.c
Normal file
1015
tests/gtk_live_test.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,112 +0,0 @@
|
|||||||
#include "test.h"
|
|
||||||
|
|
||||||
void
|
|
||||||
hmap_set_replace_and_grow(struct ct *t)
|
|
||||||
{
|
|
||||||
char keybuf[16], valbuf[16], source[] = "copied";
|
|
||||||
Hmap *h;
|
|
||||||
Hnode *n;
|
|
||||||
Str key;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
h = hmapalloc(1);
|
|
||||||
for(i = 0; i < 4; i++){
|
|
||||||
snprint(keybuf, sizeof keybuf, "key%d", i);
|
|
||||||
snprint(valbuf, sizeof valbuf, "value%d", i);
|
|
||||||
key = mkstr(keybuf);
|
|
||||||
hmapset(&h, &key, valbuf, strlen(valbuf));
|
|
||||||
}
|
|
||||||
for(i = 0; i < 4; i++){
|
|
||||||
snprint(keybuf, sizeof keybuf, "key%d", i);
|
|
||||||
snprint(valbuf, sizeof valbuf, "value%d", i);
|
|
||||||
key = mkstr(keybuf);
|
|
||||||
n = hmapget(h, &key);
|
|
||||||
if(n == nil || strcmp(n->val, valbuf) != 0)
|
|
||||||
CT_ERRORF(t, "%s: collision chain lost value", keybuf);
|
|
||||||
}
|
|
||||||
key = mkstr("key1");
|
|
||||||
hmapset(&h, &key, source, strlen(source));
|
|
||||||
source[0] = 'X';
|
|
||||||
n = hmapget(h, &key);
|
|
||||||
if(!CT_CHECK(t, n != nil))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_STR(t, "copied", n->val);
|
|
||||||
key = mkstr("key2");
|
|
||||||
n = hmapget(h, &key);
|
|
||||||
if(!CT_CHECK(t, n != nil))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_STR(t, "value2", n->val);
|
|
||||||
key = mkstr("key1");
|
|
||||||
hmapset(&h, &key, nil, 0);
|
|
||||||
n = hmapget(h, &key);
|
|
||||||
if(!CT_CHECK(t, n != nil))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_INT(t, 0, n->vlen);
|
|
||||||
CT_EQ_PTR(t, nil, n->val);
|
|
||||||
key = mkstr("missing");
|
|
||||||
CT_EQ_PTR(t, nil, hmapget(h, &key));
|
|
||||||
cleanup:
|
|
||||||
hmapfree(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
hmap_long_utf8_keys(struct ct *t)
|
|
||||||
{
|
|
||||||
Hmap *h;
|
|
||||||
Hnode *n;
|
|
||||||
Str a, b;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for(i = 0; i < Maxrunes-1; i++)
|
|
||||||
a.r[i] = b.r[i] = 0x1f600;
|
|
||||||
a.r[Maxrunes-1] = 0x1f601;
|
|
||||||
b.r[Maxrunes-1] = 0x1f602;
|
|
||||||
a.n = b.n = Maxrunes;
|
|
||||||
h = hmapalloc(1);
|
|
||||||
hmapset(&h, &a, "first", 5);
|
|
||||||
hmapset(&h, &b, "second", 6);
|
|
||||||
n = hmapget(h, &a);
|
|
||||||
if(!CT_CHECK(t, n != nil))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_STR(t, "first", n != nil ? n->val : nil);
|
|
||||||
n = hmapget(h, &b);
|
|
||||||
if(!CT_CHECK(t, n != nil))
|
|
||||||
goto cleanup;
|
|
||||||
CT_EQ_STR(t, "second", n != nil ? n->val : nil);
|
|
||||||
cleanup:
|
|
||||||
hmapfree(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
hmap_binary_keys_and_invalid_lengths(struct ct *t)
|
|
||||||
{
|
|
||||||
Hmap *h;
|
|
||||||
Hnode *n;
|
|
||||||
Str key, other;
|
|
||||||
|
|
||||||
h = hmapalloc(1);
|
|
||||||
CT_CHECK(t, h != nil);
|
|
||||||
key.n = 3;
|
|
||||||
key.r[0] = 'a';
|
|
||||||
key.r[1] = 0;
|
|
||||||
key.r[2] = 'b';
|
|
||||||
other = key;
|
|
||||||
other.r[2] = 'c';
|
|
||||||
hmapset(&h, &key, "one", 3);
|
|
||||||
hmapset(&h, &other, "two", 3);
|
|
||||||
n = hmapget(h, &key);
|
|
||||||
if(CT_CHECK(t, n != nil)){
|
|
||||||
CT_EQ_INT(t, 3, n->klen);
|
|
||||||
CT_EQ_MEM(t, "one", n->val, n->vlen);
|
|
||||||
}
|
|
||||||
n = hmapget(h, &other);
|
|
||||||
if(CT_CHECK(t, n != nil))
|
|
||||||
CT_EQ_MEM(t, "two", n->val, n->vlen);
|
|
||||||
hmapset(&h, &key, "bad", -1);
|
|
||||||
hmapset(&h, &key, nil, 1);
|
|
||||||
n = hmapget(h, &key);
|
|
||||||
if(CT_CHECK(t, n != nil))
|
|
||||||
CT_EQ_MEM(t, "one", n->val, n->vlen);
|
|
||||||
CT_EQ_PTR(t, nil, hmapalloc(0));
|
|
||||||
hmapfree(h);
|
|
||||||
}
|
|
||||||
386
tests/ibus_client_smoke.c
Normal file
386
tests/ibus_client_smoke.c
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include <ibus.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
typedef struct Log Log;
|
||||||
|
struct Log
|
||||||
|
{
|
||||||
|
GMainLoop *loop;
|
||||||
|
int *waiting;
|
||||||
|
IBusInputContext *a;
|
||||||
|
IBusInputContext *clearctx;
|
||||||
|
int legacy;
|
||||||
|
int modern;
|
||||||
|
int commit;
|
||||||
|
int invalid;
|
||||||
|
int sawpreedit;
|
||||||
|
int sawcommit;
|
||||||
|
int done;
|
||||||
|
int transfer;
|
||||||
|
int atext;
|
||||||
|
int aclear;
|
||||||
|
int retake;
|
||||||
|
int retook;
|
||||||
|
char revent[16];
|
||||||
|
int rn;
|
||||||
|
int repeat;
|
||||||
|
int rfirst;
|
||||||
|
int rcommit;
|
||||||
|
int rdone;
|
||||||
|
int flushed;
|
||||||
|
int hanja;
|
||||||
|
int required;
|
||||||
|
int deleted;
|
||||||
|
int delbefore;
|
||||||
|
int delcount;
|
||||||
|
int converted;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
revent(Log *log, char c)
|
||||||
|
{
|
||||||
|
if(log->rn + 1 < (int)sizeof log->revent){
|
||||||
|
log->revent[log->rn++] = c;
|
||||||
|
log->revent[log->rn] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
legacy(IBusInputContext *ctx, IBusText *text, guint cursor,
|
||||||
|
gboolean visible, void *arg)
|
||||||
|
{
|
||||||
|
IBusAttrList *attrs;
|
||||||
|
IBusAttribute *a;
|
||||||
|
Log *log;
|
||||||
|
const char *s;
|
||||||
|
|
||||||
|
(void)ctx;
|
||||||
|
log = arg;
|
||||||
|
log->legacy++;
|
||||||
|
s = ibus_text_get_text(text);
|
||||||
|
if(log->repeat == 3){
|
||||||
|
if(s[0] != '\0' || cursor != 0 || visible)
|
||||||
|
log->invalid = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(log->repeat != 0){
|
||||||
|
revent(log, 'P');
|
||||||
|
if(log->repeat == 1){
|
||||||
|
if(s[0] == '\0'){
|
||||||
|
if(cursor != 0 || visible)
|
||||||
|
log->invalid = 1;
|
||||||
|
}else if(strcmp(s, "ㅋ") != 0 || cursor != 1 || !visible)
|
||||||
|
log->invalid = 1;
|
||||||
|
else
|
||||||
|
log->rfirst = 1;
|
||||||
|
}else if(s[0] == '\0'){
|
||||||
|
if(cursor != 0 || visible || log->rcommit)
|
||||||
|
log->invalid = 1;
|
||||||
|
}else if(strcmp(s, "ㅋ") != 0 || cursor != 1 || !visible ||
|
||||||
|
!log->rcommit)
|
||||||
|
log->invalid = 1;
|
||||||
|
else
|
||||||
|
log->rdone = 1;
|
||||||
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(strcmp(s, "k") == 0){
|
||||||
|
attrs = ibus_text_get_attributes(text);
|
||||||
|
a = attrs == NULL ? NULL : ibus_attr_list_get(attrs, 0);
|
||||||
|
if(cursor != 1 || !visible || attrs == NULL || a == NULL ||
|
||||||
|
a->type != IBUS_ATTR_TYPE_UNDERLINE ||
|
||||||
|
a->value != IBUS_ATTR_UNDERLINE_SINGLE ||
|
||||||
|
a->start_index != 0 || a->end_index != 1 ||
|
||||||
|
ibus_attr_list_get(attrs, 1) != NULL)
|
||||||
|
log->invalid = 1;
|
||||||
|
else{
|
||||||
|
log->sawpreedit = 1;
|
||||||
|
if(log->transfer && ctx == log->a)
|
||||||
|
log->atext = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(log->transfer && log->atext && !log->aclear && s[0] == '\0'){
|
||||||
|
if(cursor != 0 || visible)
|
||||||
|
log->invalid = 1;
|
||||||
|
else{
|
||||||
|
log->clearctx = ctx;
|
||||||
|
if(ctx == log->a)
|
||||||
|
log->aclear = 1;
|
||||||
|
else
|
||||||
|
log->invalid = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log->done = log->sawpreedit && log->sawcommit;
|
||||||
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
modern(IBusInputContext *ctx, IBusText *text, guint cursor,
|
||||||
|
gboolean visible, guint mode, void *arg)
|
||||||
|
{
|
||||||
|
Log *log;
|
||||||
|
|
||||||
|
(void)ctx;
|
||||||
|
(void)text;
|
||||||
|
(void)cursor;
|
||||||
|
(void)visible;
|
||||||
|
(void)mode;
|
||||||
|
log = arg;
|
||||||
|
log->modern++;
|
||||||
|
log->invalid = 1;
|
||||||
|
if(log->loop != NULL)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
commit(IBusInputContext *ctx, IBusText *text, void *arg)
|
||||||
|
{
|
||||||
|
IBusAttrList *attrs;
|
||||||
|
Log *log;
|
||||||
|
const char *s;
|
||||||
|
|
||||||
|
(void)ctx;
|
||||||
|
log = arg;
|
||||||
|
log->commit++;
|
||||||
|
s = ibus_text_get_text(text);
|
||||||
|
attrs = ibus_text_get_attributes(text);
|
||||||
|
if(log->hanja){
|
||||||
|
/* The word converted, with the client\'s own syllable taken back. */
|
||||||
|
if(strcmp(s, "漢字") == 0 && log->deleted && log->delbefore == -1 &&
|
||||||
|
log->delcount == 1)
|
||||||
|
log->converted = 1;
|
||||||
|
else
|
||||||
|
log->invalid = 1;
|
||||||
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(log->repeat == 3){
|
||||||
|
/* Focus loss hands the pending syllable back as a commit. */
|
||||||
|
if(strcmp(s, "ㅋ") == 0)
|
||||||
|
log->flushed = 1;
|
||||||
|
else
|
||||||
|
log->invalid = 1;
|
||||||
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(log->repeat != 0){
|
||||||
|
revent(log, 'K');
|
||||||
|
if(log->repeat != 2 || strcmp(s, "ㅋ") != 0 || attrs == NULL ||
|
||||||
|
ibus_attr_list_get(attrs, 0) != NULL)
|
||||||
|
log->invalid = 1;
|
||||||
|
else
|
||||||
|
log->rcommit = 1;
|
||||||
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(log->retake){
|
||||||
|
/* Typing again takes back the reading the other context took. */
|
||||||
|
if(strcmp(s, "k") == 0)
|
||||||
|
log->retook = 1;
|
||||||
|
else
|
||||||
|
log->invalid = 1;
|
||||||
|
log->retake = 0;
|
||||||
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(strcmp(s, "か") != 0 || attrs == NULL ||
|
||||||
|
ibus_attr_list_get(attrs, 0) != NULL)
|
||||||
|
log->invalid = 1;
|
||||||
|
else
|
||||||
|
log->sawcommit = 1;
|
||||||
|
log->done = log->sawpreedit && log->sawcommit;
|
||||||
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
required(IBusInputContext *ctx, void *arg)
|
||||||
|
{
|
||||||
|
Log *log;
|
||||||
|
|
||||||
|
(void)ctx;
|
||||||
|
log = arg;
|
||||||
|
log->required = 1;
|
||||||
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
deleted(IBusInputContext *ctx, gint offset, guint n, void *arg)
|
||||||
|
{
|
||||||
|
Log *log;
|
||||||
|
|
||||||
|
(void)ctx;
|
||||||
|
log = arg;
|
||||||
|
log->deleted = 1;
|
||||||
|
log->delbefore = offset;
|
||||||
|
log->delcount = n;
|
||||||
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
timeout(void *arg)
|
||||||
|
{
|
||||||
|
g_main_loop_quit(arg);
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
waitflag(Log *log, int *flag)
|
||||||
|
{
|
||||||
|
guint timer;
|
||||||
|
|
||||||
|
if(*flag)
|
||||||
|
return 1;
|
||||||
|
log->waiting = flag;
|
||||||
|
log->loop = g_main_loop_new(NULL, FALSE);
|
||||||
|
timer = g_timeout_add_seconds(4, timeout, log->loop);
|
||||||
|
g_main_loop_run(log->loop);
|
||||||
|
if(g_main_context_find_source_by_id(NULL, timer) != NULL)
|
||||||
|
g_source_remove(timer);
|
||||||
|
g_main_loop_unref(log->loop);
|
||||||
|
log->loop = NULL;
|
||||||
|
log->waiting = NULL;
|
||||||
|
return *flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
IBusBus *bus;
|
||||||
|
IBusInputContext *a, *b, *c;
|
||||||
|
IBusText *around;
|
||||||
|
Log log;
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
if(argc != 2){
|
||||||
|
fprintf(stderr, "usage: ibus_client_smoke address\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if(setenv("IBUS_ADDRESS", argv[1], 1) < 0 ||
|
||||||
|
unsetenv("IBUS_ADDRESS_FILE") < 0 ||
|
||||||
|
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 || unsetenv("DISPLAY") < 0){
|
||||||
|
perror("ibus_client_smoke: environment");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
memset(&log, 0, sizeof log);
|
||||||
|
b = c = NULL;
|
||||||
|
ibus_init();
|
||||||
|
bus = ibus_bus_new();
|
||||||
|
if(bus == NULL || !ibus_bus_is_connected(bus)){
|
||||||
|
fprintf(stderr, "ibus_client_smoke: cannot connect to private bus\n");
|
||||||
|
if(bus != NULL) g_object_unref(bus);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
a = ibus_bus_create_input_context(bus, "strans-libibus-smoke-a");
|
||||||
|
if(a == NULL){
|
||||||
|
fprintf(stderr, "ibus_client_smoke: cannot create input context\n");
|
||||||
|
g_object_unref(bus);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
log.a = a;
|
||||||
|
g_signal_connect(a, "update-preedit-text", G_CALLBACK(legacy), &log);
|
||||||
|
g_signal_connect(a, "update-preedit-text-with-mode", G_CALLBACK(modern),
|
||||||
|
&log);
|
||||||
|
g_signal_connect(a, "commit-text", G_CALLBACK(commit), &log);
|
||||||
|
ibus_input_context_set_capabilities(a, IBUS_CAP_PREEDIT_TEXT);
|
||||||
|
ibus_input_context_focus_in(a);
|
||||||
|
ok = ibus_input_context_process_key_event(a, 'n', 0, IBUS_CONTROL_MASK) &&
|
||||||
|
ibus_input_context_process_key_event(a, 'k', 0, 0) &&
|
||||||
|
ibus_input_context_process_key_event(a, 'a', 0, 0) &&
|
||||||
|
ibus_input_context_process_key_event(a, '0', 0, 0) &&
|
||||||
|
waitflag(&log, &log.done);
|
||||||
|
b = ibus_bus_create_input_context(bus, "strans-libibus-smoke-b");
|
||||||
|
if(b == NULL)
|
||||||
|
ok = 0;
|
||||||
|
else{
|
||||||
|
g_signal_connect(b, "update-preedit-text", G_CALLBACK(legacy), &log);
|
||||||
|
g_signal_connect(b, "update-preedit-text-with-mode",
|
||||||
|
G_CALLBACK(modern), &log);
|
||||||
|
ibus_input_context_set_capabilities(b, IBUS_CAP_PREEDIT_TEXT);
|
||||||
|
log.transfer = 1;
|
||||||
|
ok = ok && ibus_input_context_process_key_event(a, 'k', 0, 0) &&
|
||||||
|
waitflag(&log, &log.atext);
|
||||||
|
ibus_input_context_focus_in(b);
|
||||||
|
ok = ok && ibus_input_context_process_key_event(b, 'n', 0, 0) &&
|
||||||
|
waitflag(&log, &log.aclear);
|
||||||
|
ibus_input_context_focus_out(b);
|
||||||
|
}
|
||||||
|
ibus_input_context_focus_in(a);
|
||||||
|
log.retake = 1;
|
||||||
|
ok = ok && ibus_input_context_process_key_event(a, 's', 0,
|
||||||
|
IBUS_CONTROL_MASK) && waitflag(&log, &log.retook);
|
||||||
|
log.repeat = 1;
|
||||||
|
ok = ok && ibus_input_context_process_key_event(a, 'z', 0, 0) &&
|
||||||
|
waitflag(&log, &log.rfirst);
|
||||||
|
log.repeat = 2;
|
||||||
|
log.revent[0] = '\0';
|
||||||
|
log.rn = 0;
|
||||||
|
ok = ok && ibus_input_context_process_key_event(a, 'z', 0, 0) &&
|
||||||
|
waitflag(&log, &log.rdone) && strcmp(log.revent, "PKP") == 0;
|
||||||
|
log.repeat = 3;
|
||||||
|
ibus_input_context_focus_out(a);
|
||||||
|
ok = ok && waitflag(&log, &log.flushed);
|
||||||
|
/*
|
||||||
|
* A word is committed a syllable at a time, so the Hanja key must
|
||||||
|
* reach into what the client already holds and ask for it back.
|
||||||
|
*/
|
||||||
|
c = ibus_bus_create_input_context(bus, "strans-libibus-smoke-c");
|
||||||
|
if(c == NULL)
|
||||||
|
ok = 0;
|
||||||
|
else{
|
||||||
|
g_signal_connect(c, "commit-text", G_CALLBACK(commit), &log);
|
||||||
|
g_signal_connect(c, "require-surrounding-text",
|
||||||
|
G_CALLBACK(required), &log);
|
||||||
|
g_signal_connect(c, "delete-surrounding-text",
|
||||||
|
G_CALLBACK(deleted), &log);
|
||||||
|
log.repeat = 0;
|
||||||
|
ibus_input_context_set_capabilities(c,
|
||||||
|
IBUS_CAP_PREEDIT_TEXT|IBUS_CAP_SURROUNDING_TEXT);
|
||||||
|
ibus_input_context_focus_in(c);
|
||||||
|
ok = ok && waitflag(&log, &log.required);
|
||||||
|
around = ibus_text_new_from_string("저는 한");
|
||||||
|
ibus_input_context_set_surrounding_text(c, around, 4, 4);
|
||||||
|
log.hanja = 1;
|
||||||
|
ok = ok && ibus_input_context_process_key_event(c, 's', 0,
|
||||||
|
IBUS_CONTROL_MASK) &&
|
||||||
|
ibus_input_context_process_key_event(c, 'w', 0, 0) &&
|
||||||
|
ibus_input_context_process_key_event(c, 'k', 0, 0) &&
|
||||||
|
ibus_input_context_process_key_event(c, 'h', 0,
|
||||||
|
IBUS_CONTROL_MASK) &&
|
||||||
|
ibus_input_context_process_key_event(c, IBUS_KEY_Return,
|
||||||
|
0, 0) && waitflag(&log, &log.converted);
|
||||||
|
log.hanja = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(log.legacy == 0 || log.modern != 0 || log.commit != 5 ||
|
||||||
|
log.invalid || !log.done || !log.atext || !log.aclear ||
|
||||||
|
!log.retook || log.clearctx != a || !log.rdone || !log.converted)
|
||||||
|
ok = 0;
|
||||||
|
if(c != NULL)
|
||||||
|
g_object_unref(c);
|
||||||
|
if(b != NULL)
|
||||||
|
g_object_unref(b);
|
||||||
|
g_object_unref(a);
|
||||||
|
g_object_unref(bus);
|
||||||
|
if(!ok){
|
||||||
|
fprintf(stderr,
|
||||||
|
"ibus_client_smoke: legacy=%d modern=%d commit=%d invalid=%d preedit=%d committed=%d transfer-text=%d a-clear=%d retook=%d repeat=%s flushed=%d required=%d deleted=%d,%d converted=%d\n",
|
||||||
|
log.legacy, log.modern, log.commit, log.invalid,
|
||||||
|
log.sawpreedit, log.sawcommit, log.atext, log.aclear,
|
||||||
|
log.retook, log.revent, log.flushed, log.required,
|
||||||
|
log.delbefore, log.delcount, log.converted);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("official libibus client preedit, commit, owner clear, take-back and focus-out hand-back: ok\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,8 @@
|
|||||||
#include "../ibus.c"
|
#include "ibus.c"
|
||||||
#include "../cutest/cutest.h"
|
#include "test.h"
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
void testengineinit(int);
|
|
||||||
void testenginehandle(Keyreq*);
|
|
||||||
void* testengineowner(void);
|
|
||||||
void testenginepreedit(Str*);
|
|
||||||
void testenginecaret(Caret*);
|
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Testctrlmask = 1<<2,
|
Testctrlmask = 1<<2,
|
||||||
@@ -18,20 +12,19 @@ typedef struct Ibusfix Ibusfix;
|
|||||||
struct Ibusfix
|
struct Ibusfix
|
||||||
{
|
{
|
||||||
Channel *oldreply;
|
Channel *oldreply;
|
||||||
Channel *trace;
|
Pump pump;
|
||||||
Channel *stop;
|
/* A fake connection is nothing but an address to tell two apart. */
|
||||||
Channel *done;
|
char fake[2];
|
||||||
DBusConnection *c1;
|
DBusConnection *c1;
|
||||||
DBusConnection *c2;
|
DBusConnection *c2;
|
||||||
int pumpactive;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
ibus_machine_id_fallback(struct ct *t)
|
ibus_machine_id_fallback(struct ct *t)
|
||||||
{
|
{
|
||||||
char root[] = "/tmp/strans-machine-id.XXXXXX";
|
char root[] = "/tmp/strans-machine-id.XXXXXX";
|
||||||
char primary[128], fallback[128], got[64];
|
char primary[128], fallback[128], override[128], got[512];
|
||||||
char *path[2];
|
char *path[2], *old, *saved;
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
int madeprimary, madefallback;
|
int madeprimary, madefallback;
|
||||||
|
|
||||||
@@ -61,6 +54,27 @@ 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");
|
||||||
|
saved = old == nil ? nil : strdup(old);
|
||||||
|
if(old == nil || CT_CHECK(t, saved != nil)){
|
||||||
|
if(CT_CHECK(t, snprintf(override, sizeof override, "%s/address", root)
|
||||||
|
< (int)sizeof override) &&
|
||||||
|
CT_CHECK(t, setenv("IBUS_ADDRESS_FILE", override, 1) == 0)){
|
||||||
|
CT_EQ_INT(t, 0, buildaddrpath(got, sizeof got));
|
||||||
|
CT_EQ_STR(t, override, got);
|
||||||
|
CT_CHECK(t, setenv("IBUS_ADDRESS_FILE", "", 1) == 0);
|
||||||
|
CT_EQ_INT(t, -1, buildaddrpath(got, sizeof got));
|
||||||
|
}
|
||||||
|
if(saved != nil){
|
||||||
|
setenv("IBUS_ADDRESS_FILE", saved, 1);
|
||||||
|
free(saved);
|
||||||
|
}else
|
||||||
|
unsetenv("IBUS_ADDRESS_FILE");
|
||||||
|
}
|
||||||
cleanup:
|
cleanup:
|
||||||
if(madefallback)
|
if(madefallback)
|
||||||
CT_CHECK(t, unlink(fallback) == 0);
|
CT_CHECK(t, unlink(fallback) == 0);
|
||||||
@@ -69,111 +83,8 @@ 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;
|
|
||||||
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 void
|
static void
|
||||||
enginepump(void *arg)
|
ibusbegin(Ibusfix *f)
|
||||||
{
|
|
||||||
Ibusfix *f;
|
|
||||||
Keyreq req;
|
|
||||||
uchar token;
|
|
||||||
Alt alts[] = {
|
|
||||||
{keyc, &req, CHANRCV, nil},
|
|
||||||
{nil, &token, CHANRCV, nil},
|
|
||||||
{nil, nil, CHANEND, nil},
|
|
||||||
};
|
|
||||||
|
|
||||||
f = arg;
|
|
||||||
alts[1].c = f->stop;
|
|
||||||
for(;;)
|
|
||||||
switch(alt(alts)){
|
|
||||||
case 0:
|
|
||||||
chansend(f->trace, &req);
|
|
||||||
testenginehandle(&req);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
chansend(f->done, &token);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
ibusbegin(struct ct *t, Ibusfix *f)
|
|
||||||
{
|
{
|
||||||
Drawcmd dc;
|
Drawcmd dc;
|
||||||
|
|
||||||
@@ -181,51 +92,36 @@ ibusbegin(struct ct *t, Ibusfix *f)
|
|||||||
memset(contexts, 0, sizeof contexts);
|
memset(contexts, 0, sizeof contexts);
|
||||||
memset(conns, 0, sizeof conns);
|
memset(conns, 0, sizeof conns);
|
||||||
nconns = 0;
|
nconns = 0;
|
||||||
|
preowner = nil;
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
while(channbrecv(drawc, &dc) > 0)
|
||||||
;
|
;
|
||||||
testengineinit(LangEN);
|
testengineinit(LangEN);
|
||||||
f->oldreply = replyc;
|
f->oldreply = replyc;
|
||||||
replyc = chancreate(sizeof(Keyres), 0);
|
replyc = chancreate(sizeof(Keyres), 0);
|
||||||
f->trace = chancreate(sizeof(Keyreq), Maxcontexts);
|
pumpstart(&f->pump, Maxcontexts);
|
||||||
f->stop = chancreate(sizeof(uchar), 0);
|
f->c1 = (DBusConnection*)&f->fake[0];
|
||||||
f->done = chancreate(sizeof(uchar), 0);
|
f->c2 = (DBusConnection*)&f->fake[1];
|
||||||
f->pumpactive = threadcreate(enginepump, f, 8192) >= 0;
|
|
||||||
f->c1 = malloc(1);
|
|
||||||
f->c2 = malloc(1);
|
|
||||||
return CT_CHECK(t, f->pumpactive && f->c1 != nil && f->c2 != nil);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
ibusend(Ibusfix *f)
|
ibusend(Ibusfix *f)
|
||||||
{
|
{
|
||||||
Drawcmd dc;
|
Drawcmd dc;
|
||||||
Keyreq req;
|
|
||||||
uchar token;
|
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if(f->pumpactive){
|
for(i = 0; i < nelem(contexts); i++)
|
||||||
for(i = 0; i < nelem(contexts); i++)
|
if(contexts[i].conn != nil)
|
||||||
if(contexts[i].conn != nil)
|
dropcontext(&contexts[i]);
|
||||||
dropcontext(&contexts[i]);
|
pumpstop(&f->pump);
|
||||||
while(channbrecv(f->trace, &req) > 0)
|
|
||||||
;
|
|
||||||
token = 0;
|
|
||||||
chansend(f->stop, &token);
|
|
||||||
chanrecv(f->done, &token);
|
|
||||||
}
|
|
||||||
memset(contexts, 0, sizeof contexts);
|
memset(contexts, 0, sizeof contexts);
|
||||||
memset(conns, 0, sizeof conns);
|
memset(conns, 0, sizeof conns);
|
||||||
nconns = 0;
|
nconns = 0;
|
||||||
|
preowner = nil;
|
||||||
testengineinit(LangEN);
|
testengineinit(LangEN);
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
while(channbrecv(drawc, &dc) > 0)
|
||||||
;
|
;
|
||||||
free(f->c1);
|
|
||||||
free(f->c2);
|
|
||||||
chanfree(replyc);
|
chanfree(replyc);
|
||||||
replyc = f->oldreply;
|
replyc = f->oldreply;
|
||||||
chanfree(f->trace);
|
|
||||||
chanfree(f->stop);
|
|
||||||
chanfree(f->done);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Keyreq
|
static Keyreq
|
||||||
@@ -234,10 +130,12 @@ nexttrace(struct ct *t, Ibusfix *f, int op, Ictx *ctx)
|
|||||||
Keyreq req;
|
Keyreq req;
|
||||||
|
|
||||||
memset(&req, 0, sizeof req);
|
memset(&req, 0, sizeof req);
|
||||||
if(!CT_CHECK(t, channbrecv(f->trace, &req) > 0))
|
if(!CT_CHECK(t, channbrecv(f->pump.trace, &req) > 0))
|
||||||
return req;
|
return req;
|
||||||
CT_EQ_INT(t, op, req.op);
|
CT_EQ_INT(t, op, req.op);
|
||||||
CT_EQ_PTR(t, ctx, req.owner);
|
CT_EQ_PTR(t, ctx, req.owner);
|
||||||
|
if(ctx != nil)
|
||||||
|
CT_EQ_INT(t, clientpreedit(ctx), req.clientpre);
|
||||||
CT_EQ_PTR(t, replyc, req.reply);
|
CT_EQ_PTR(t, replyc, req.reply);
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
@@ -247,40 +145,122 @@ notrace(struct ct *t, Ibusfix *f)
|
|||||||
{
|
{
|
||||||
Keyreq req;
|
Keyreq req;
|
||||||
|
|
||||||
CT_CHECK(t, channbrecv(f->trace, &req) <= 0);
|
CT_CHECK(t, channbrecv(f->pump.trace, &req) <= 0);
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
checkpreedit(struct ct *t, char *want, Keyres *res)
|
|
||||||
{
|
|
||||||
char got[Maxutf];
|
|
||||||
|
|
||||||
stoutf(&res->preedit, got, sizeof got);
|
|
||||||
CT_EQ_STR(t, want, got);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
checkenginepreedit(struct ct *t, char *want)
|
|
||||||
{
|
|
||||||
char got[Maxutf];
|
|
||||||
Str preedit;
|
|
||||||
|
|
||||||
testenginepreedit(&preedit);
|
|
||||||
stoutf(&preedit, got, sizeof got);
|
|
||||||
CT_EQ_STR(t, want, got);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Keyres
|
static Keyres
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ibus_capability_policy(struct ct *t)
|
||||||
|
{
|
||||||
|
Ibusfix f;
|
||||||
|
Ictx *a, *b;
|
||||||
|
Keyres res;
|
||||||
|
|
||||||
|
ibusbegin(&f);
|
||||||
|
a = newcontext(f.c1, "/context/cap-a");
|
||||||
|
b = newcontext(f.c2, "/context/cap-b");
|
||||||
|
if(!CT_CHECK(t, a != nil && b != nil))
|
||||||
|
goto cleanup;
|
||||||
|
CT_EQ_INT(t, 0, a->cap);
|
||||||
|
a->focused = 1;
|
||||||
|
res = contextkey(t, &f, a, 'n', Testctrlmask);
|
||||||
|
CT_CHECK(t, res.eaten);
|
||||||
|
res = contextkey(t, &f, a, 'k', 0);
|
||||||
|
checkstr(t, "preedit", "k", &res.preedit);
|
||||||
|
CT_EQ_PTR(t, a, testengineowner());
|
||||||
|
|
||||||
|
a->cap = Ibuscappreedit;
|
||||||
|
memset(&res, 0, sizeof res);
|
||||||
|
sendrequest(a, Keycap, 0, 0, &res);
|
||||||
|
nexttrace(t, &f, Keycap, a);
|
||||||
|
CT_CHECK(t, res.eaten);
|
||||||
|
checkstr(t, "preedit", "k", &res.preedit);
|
||||||
|
|
||||||
|
b->focused = 1;
|
||||||
|
b->cap = Ibuscappreedit;
|
||||||
|
memset(&res, 0, sizeof res);
|
||||||
|
sendrequest(b, Keycap, 0, 0, &res);
|
||||||
|
nexttrace(t, &f, Keycap, b);
|
||||||
|
CT_CHECK(t, !res.eaten);
|
||||||
|
CT_EQ_PTR(t, a, testengineowner());
|
||||||
|
checkenginepreedit(t, "k");
|
||||||
|
|
||||||
|
a->cap = 0;
|
||||||
|
memset(&res, 0, sizeof res);
|
||||||
|
sendrequest(a, Keycap, 0, 0, &res);
|
||||||
|
nexttrace(t, &f, Keycap, a);
|
||||||
|
CT_CHECK(t, res.eaten);
|
||||||
|
checkstr(t, "preedit", "k", &res.preedit);
|
||||||
|
cleanup:
|
||||||
|
ibusend(&f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ibus_private_input_policy(struct ct *t)
|
||||||
|
{
|
||||||
|
static const struct { u32int purpose; int want; } cases[] = {
|
||||||
|
{ 0, 0 },
|
||||||
|
{ Purposepassword, 1 },
|
||||||
|
{ Purposepin, 1 },
|
||||||
|
{ 37, 0 },
|
||||||
|
};
|
||||||
|
Ibusfix f;
|
||||||
|
Ictx *ctx;
|
||||||
|
Keyres res;
|
||||||
|
char text[Maxutf];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
ibusbegin(&f);
|
||||||
|
ctx = newcontext(f.c1, "/context/private");
|
||||||
|
if(!CT_CHECK(t, ctx != nil))
|
||||||
|
goto cleanup;
|
||||||
|
for(i = 0; i < nelem(cases); i++){
|
||||||
|
ctx->purpose = cases[i].purpose;
|
||||||
|
CT_EQ_INT(t, cases[i].want, hidden(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->focused = 1;
|
||||||
|
ctx->cap = 1;
|
||||||
|
ctx->purpose = 0;
|
||||||
|
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
||||||
|
res = contextkey(t, &f, ctx, 'k', 0);
|
||||||
|
checkstr(t, "preedit", "k", &res.preedit);
|
||||||
|
ctx->purpose = Purposepassword;
|
||||||
|
memset(&res, 0, sizeof res);
|
||||||
|
CT_CHECK(t, !processkey(ctx, 'x', 0, text, sizeof text, &res));
|
||||||
|
CT_CHECK(t, !res.eaten);
|
||||||
|
notrace(t, &f);
|
||||||
|
checkenginepreedit(t, "k");
|
||||||
|
|
||||||
|
ctx->purpose = 0;
|
||||||
|
res = contextkey(t, &f, ctx, 'a', 0);
|
||||||
|
checkstr(t, "preedit", "か", &res.preedit);
|
||||||
|
cleanup:
|
||||||
|
ibusend(&f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A second release is silent, and dropping frees the slot. */
|
||||||
|
static void
|
||||||
|
checkdrop(struct ct *t, Ibusfix *f, Ictx *ctx, char *path)
|
||||||
|
{
|
||||||
|
releasecontext(ctx);
|
||||||
|
notrace(t, f);
|
||||||
|
dropcontext(ctx);
|
||||||
|
notrace(t, f);
|
||||||
|
CT_EQ_PTR(t, nil, findcontext(f->c1, path));
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ibus_context_lifecycle(struct ct *t)
|
ibus_context_lifecycle(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -289,9 +269,9 @@ 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))
|
ibusbegin(&f);
|
||||||
goto cleanup;
|
|
||||||
a = newcontext(f.c1, "/context/one");
|
a = newcontext(f.c1, "/context/one");
|
||||||
b = newcontext(f.c2, "/context/one");
|
b = newcontext(f.c2, "/context/one");
|
||||||
if(!CT_CHECK(t, a != nil && b != nil && a != b))
|
if(!CT_CHECK(t, a != nil && b != nil && a != b))
|
||||||
@@ -300,44 +280,40 @@ 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);
|
setcursor(a, -10, -20, 14);
|
||||||
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);
|
|
||||||
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);
|
||||||
CT_EQ_INT(t, 20, req.caret.y);
|
CT_EQ_INT(t, -20, req.caret.y);
|
||||||
CT_EQ_INT(t, 14, req.caret.h);
|
CT_EQ_INT(t, 14, req.caret.h);
|
||||||
CT_EQ_PTR(t, a, testengineowner());
|
CT_EQ_PTR(t, a, testengineowner());
|
||||||
testenginecaret(&at);
|
testenginecaret(&at);
|
||||||
CT_CHECK(t, at.valid);
|
CT_CHECK(t, at.valid);
|
||||||
CT_EQ_INT(t, 10, at.x);
|
CT_EQ_INT(t, -10, at.x);
|
||||||
CT_EQ_INT(t, 20, at.y);
|
CT_EQ_INT(t, -20, at.y);
|
||||||
res = contextkey(t, &f, a, 'n', Testctrlmask);
|
res = contextkey(t, &f, a, 'n', Testctrlmask);
|
||||||
CT_CHECK(t, res.eaten);
|
CT_CHECK(t, res.eaten);
|
||||||
contextkey(t, &f, a, 'k', 0);
|
contextkey(t, &f, a, 'k', 0);
|
||||||
res = contextkey(t, &f, a, 'a', 0);
|
res = contextkey(t, &f, a, 'a', 0);
|
||||||
checkpreedit(t, "か", &res);
|
checkstr(t, "preedit", "か", &res.preedit);
|
||||||
|
|
||||||
b->focused = 1;
|
b->focused = 1;
|
||||||
res = contextkey(t, &f, b, 'n', 0);
|
res = contextkey(t, &f, b, 'n', 0);
|
||||||
checkpreedit(t, "ん", &res);
|
checkstr(t, "preedit", "ん", &res.preedit);
|
||||||
CT_EQ_PTR(t, b, testengineowner());
|
CT_EQ_PTR(t, b, testengineowner());
|
||||||
|
|
||||||
setcursor(b, 50, 60, 12);
|
setcursor(b, 50, 60, 12);
|
||||||
@@ -351,7 +327,7 @@ ibus_context_lifecycle(struct ct *t)
|
|||||||
memset(&res, 0, sizeof res);
|
memset(&res, 0, sizeof res);
|
||||||
sendrequest(a, Keyreset, 0, 0, &res);
|
sendrequest(a, Keyreset, 0, 0, &res);
|
||||||
nexttrace(t, &f, Keyreset, a);
|
nexttrace(t, &f, Keyreset, a);
|
||||||
checkpreedit(t, "", &res);
|
checkstr(t, "preedit", "", &res.preedit);
|
||||||
checkenginepreedit(t, "ん");
|
checkenginepreedit(t, "ん");
|
||||||
CT_EQ_PTR(t, b, testengineowner());
|
CT_EQ_PTR(t, b, testengineowner());
|
||||||
|
|
||||||
@@ -367,11 +343,7 @@ ibus_context_lifecycle(struct ct *t)
|
|||||||
CT_CHECK(t, !a->caret.valid);
|
CT_CHECK(t, !a->caret.valid);
|
||||||
checkenginepreedit(t, "ん");
|
checkenginepreedit(t, "ん");
|
||||||
CT_EQ_PTR(t, b, testengineowner());
|
CT_EQ_PTR(t, b, testengineowner());
|
||||||
releasecontext(a);
|
checkdrop(t, &f, a, "/context/one");
|
||||||
notrace(t, &f);
|
|
||||||
dropcontext(a);
|
|
||||||
notrace(t, &f);
|
|
||||||
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/one"));
|
|
||||||
|
|
||||||
reused = newcontext(f.c1, "/context/stale-destroy");
|
reused = newcontext(f.c1, "/context/stale-destroy");
|
||||||
CT_EQ_PTR(t, a, reused);
|
CT_EQ_PTR(t, a, reused);
|
||||||
@@ -396,7 +368,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());
|
||||||
@@ -411,25 +383,24 @@ ibus_active_release_lifecycle(struct ct *t)
|
|||||||
Ictx *ctx, *reused;
|
Ictx *ctx, *reused;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
|
|
||||||
if(!ibusbegin(t, &f))
|
ibusbegin(&f);
|
||||||
goto cleanup;
|
|
||||||
ctx = newcontext(f.c1, "/context/reset");
|
ctx = newcontext(f.c1, "/context/reset");
|
||||||
if(!CT_CHECK(t, ctx != nil))
|
if(!CT_CHECK(t, ctx != nil))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
ctx->focused = 1;
|
ctx->focused = 1;
|
||||||
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
||||||
res = contextkey(t, &f, ctx, 'n', 0);
|
res = contextkey(t, &f, ctx, 'n', 0);
|
||||||
checkpreedit(t, "ん", &res);
|
checkstr(t, "preedit", "ん", &res.preedit);
|
||||||
|
|
||||||
memset(&res, 0, sizeof res);
|
memset(&res, 0, sizeof res);
|
||||||
sendrequest(ctx, Keyreset, 0, 0, &res);
|
sendrequest(ctx, Keyreset, 0, 0, &res);
|
||||||
nexttrace(t, &f, Keyreset, ctx);
|
nexttrace(t, &f, Keyreset, ctx);
|
||||||
checkpreedit(t, "", &res);
|
checkstr(t, "preedit", "", &res.preedit);
|
||||||
checkenginepreedit(t, "");
|
checkenginepreedit(t, "");
|
||||||
CT_CHECK(t, ctx->focused);
|
CT_CHECK(t, ctx->focused);
|
||||||
CT_EQ_PTR(t, ctx, testengineowner());
|
CT_EQ_PTR(t, ctx, testengineowner());
|
||||||
res = contextkey(t, &f, ctx, 'k', 0);
|
res = contextkey(t, &f, ctx, 'k', 0);
|
||||||
checkpreedit(t, "k", &res);
|
checkstr(t, "preedit", "k", &res.preedit);
|
||||||
CT_EQ_PTR(t, ctx, testengineowner());
|
CT_EQ_PTR(t, ctx, testengineowner());
|
||||||
|
|
||||||
releasecontext(ctx);
|
releasecontext(ctx);
|
||||||
@@ -437,11 +408,7 @@ ibus_active_release_lifecycle(struct ct *t)
|
|||||||
CT_CHECK(t, !ctx->focused);
|
CT_CHECK(t, !ctx->focused);
|
||||||
checkenginepreedit(t, "");
|
checkenginepreedit(t, "");
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
releasecontext(ctx);
|
checkdrop(t, &f, ctx, "/context/reset");
|
||||||
notrace(t, &f);
|
|
||||||
dropcontext(ctx);
|
|
||||||
notrace(t, &f);
|
|
||||||
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/reset"));
|
|
||||||
|
|
||||||
reused = newcontext(f.c1, "/context/destroy");
|
reused = newcontext(f.c1, "/context/destroy");
|
||||||
CT_EQ_PTR(t, ctx, reused);
|
CT_EQ_PTR(t, ctx, reused);
|
||||||
@@ -457,12 +424,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);
|
||||||
|
|||||||
@@ -1,538 +1,33 @@
|
|||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#include <dirent.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <signal.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/inotify.h>
|
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/un.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "../ipc.h"
|
#include "ipc.h"
|
||||||
|
#include "live.h"
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Maxclients = 64,
|
Maxclients = 64,
|
||||||
Calltimeout = 4000,
|
Cappreedit = 1,
|
||||||
Starttimeout = 8000,
|
|
||||||
Stoptimeout = 3000,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Daemon Daemon;
|
|
||||||
typedef struct Response Response;
|
typedef struct Response Response;
|
||||||
|
|
||||||
struct Daemon
|
|
||||||
{
|
|
||||||
pid_t pid;
|
|
||||||
int errfd;
|
|
||||||
char root[256];
|
|
||||||
char runtime[320];
|
|
||||||
char config[320];
|
|
||||||
char ibus[384];
|
|
||||||
char bus[448];
|
|
||||||
char home[320];
|
|
||||||
char socket[384];
|
|
||||||
char err[4096];
|
|
||||||
size_t nerr;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Response
|
struct Response
|
||||||
{
|
{
|
||||||
int eaten;
|
int eaten;
|
||||||
|
int del;
|
||||||
char commit[Ipcfieldmax+1];
|
char commit[Ipcfieldmax+1];
|
||||||
char preedit[Ipcfieldmax+1];
|
char preedit[Ipcfieldmax+1];
|
||||||
};
|
};
|
||||||
|
|
||||||
static int
|
|
||||||
fail(char *fmt, ...)
|
|
||||||
{
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
fprintf(stderr, "ipc_live_test: ");
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
fputc('\n', stderr);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t
|
|
||||||
nowms(void)
|
|
||||||
{
|
|
||||||
struct timespec ts;
|
|
||||||
|
|
||||||
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
|
||||||
return 0;
|
|
||||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
leftms(int64_t deadline)
|
|
||||||
{
|
|
||||||
int64_t n;
|
|
||||||
|
|
||||||
n = deadline - nowms();
|
|
||||||
if(n <= 0)
|
|
||||||
return 0;
|
|
||||||
if(n > 0x7fffffff)
|
|
||||||
return 0x7fffffff;
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
makedir(char *path)
|
|
||||||
{
|
|
||||||
if(mkdir(path, 0700) == 0)
|
|
||||||
return 1;
|
|
||||||
return fail("mkdir %s: %s", path, strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
readerrors(Daemon *d)
|
|
||||||
{
|
|
||||||
ssize_t n;
|
|
||||||
|
|
||||||
if(d->errfd < 0)
|
|
||||||
return;
|
|
||||||
while(d->nerr + 1 < sizeof d->err){
|
|
||||||
n = read(d->errfd, d->err + d->nerr,
|
|
||||||
sizeof d->err - d->nerr - 1);
|
|
||||||
if(n > 0){
|
|
||||||
d->nerr += n;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
d->err[d->nerr] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
showerrors(Daemon *d)
|
|
||||||
{
|
|
||||||
readerrors(d);
|
|
||||||
if(d->nerr != 0)
|
|
||||||
fprintf(stderr, "ipc_live_test: daemon stderr:\n%s", d->err);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
waitsocket(Daemon *d, int notifyfd)
|
|
||||||
{
|
|
||||||
struct pollfd pfd[2];
|
|
||||||
struct stat st;
|
|
||||||
char buf[4096];
|
|
||||||
int n, status, timeout;
|
|
||||||
int64_t deadline;
|
|
||||||
|
|
||||||
deadline = nowms() + Starttimeout;
|
|
||||||
for(;;){
|
|
||||||
if(lstat(d->socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
|
||||||
(st.st_mode & 0777) == 0600)
|
|
||||||
return 1;
|
|
||||||
if(waitpid(d->pid, &status, WNOHANG) == d->pid){
|
|
||||||
d->pid = -1;
|
|
||||||
readerrors(d);
|
|
||||||
return fail("daemon exited before creating IPC socket");
|
|
||||||
}
|
|
||||||
timeout = leftms(deadline);
|
|
||||||
if(timeout == 0)
|
|
||||||
return fail("timed out waiting for protected IPC socket %s",
|
|
||||||
d->socket);
|
|
||||||
pfd[0].fd = notifyfd;
|
|
||||||
pfd[0].events = POLLIN;
|
|
||||||
pfd[0].revents = 0;
|
|
||||||
pfd[1].fd = d->errfd;
|
|
||||||
pfd[1].events = POLLIN;
|
|
||||||
pfd[1].revents = 0;
|
|
||||||
n = poll(pfd, 2, timeout);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n < 0)
|
|
||||||
return fail("poll for IPC socket: %s", strerror(errno));
|
|
||||||
if(n == 0)
|
|
||||||
continue;
|
|
||||||
if(pfd[1].revents != 0)
|
|
||||||
readerrors(d);
|
|
||||||
if(pfd[0].revents & POLLIN)
|
|
||||||
while(read(notifyfd, buf, sizeof buf) < 0 && errno == EINTR)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
startdaemon(Daemon *d, char *program, char *mapdir)
|
|
||||||
{
|
|
||||||
char missing[384];
|
|
||||||
int fd, notifyfd, watch, errpipe[2];
|
|
||||||
long maxfd;
|
|
||||||
pid_t pid;
|
|
||||||
|
|
||||||
memset(d, 0, sizeof *d);
|
|
||||||
d->pid = -1;
|
|
||||||
d->errfd = -1;
|
|
||||||
snprintf(d->root, sizeof d->root, "/tmp/strans-ipc.XXXXXX");
|
|
||||||
if(mkdtemp(d->root) == NULL)
|
|
||||||
return fail("mkdtemp: %s", strerror(errno));
|
|
||||||
if(snprintf(d->runtime, sizeof d->runtime, "%s/runtime", d->root)
|
|
||||||
>= (int)sizeof d->runtime ||
|
|
||||||
snprintf(d->config, sizeof d->config, "%s/config", d->root)
|
|
||||||
>= (int)sizeof d->config ||
|
|
||||||
snprintf(d->ibus, sizeof d->ibus, "%s/ibus", d->config)
|
|
||||||
>= (int)sizeof d->ibus ||
|
|
||||||
snprintf(d->bus, sizeof d->bus, "%s/bus", d->ibus)
|
|
||||||
>= (int)sizeof d->bus ||
|
|
||||||
snprintf(d->home, sizeof d->home, "%s/home", d->root)
|
|
||||||
>= (int)sizeof d->home ||
|
|
||||||
snprintf(d->socket, sizeof d->socket, "%s/strans.sock", d->runtime)
|
|
||||||
>= (int)sizeof d->socket ||
|
|
||||||
snprintf(missing, sizeof missing, "%s/no-such-font.ttf", d->root)
|
|
||||||
>= (int)sizeof missing)
|
|
||||||
return fail("temporary path is too long");
|
|
||||||
if(!makedir(d->runtime) || !makedir(d->config) || !makedir(d->ibus) ||
|
|
||||||
!makedir(d->bus) || !makedir(d->home))
|
|
||||||
return 0;
|
|
||||||
notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
|
|
||||||
if(notifyfd < 0)
|
|
||||||
return fail("inotify_init1: %s", strerror(errno));
|
|
||||||
watch = inotify_add_watch(notifyfd, d->runtime,
|
|
||||||
IN_CREATE|IN_MOVED_TO|IN_ATTRIB);
|
|
||||||
if(watch < 0){
|
|
||||||
close(notifyfd);
|
|
||||||
return fail("inotify_add_watch: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0){
|
|
||||||
close(notifyfd);
|
|
||||||
return fail("pipe2: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
pid = fork();
|
|
||||||
if(pid < 0){
|
|
||||||
close(errpipe[0]);
|
|
||||||
close(errpipe[1]);
|
|
||||||
close(notifyfd);
|
|
||||||
return fail("fork: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
if(pid == 0){
|
|
||||||
close(errpipe[0]);
|
|
||||||
close(notifyfd);
|
|
||||||
if(dup2(errpipe[1], STDOUT_FILENO) < 0 ||
|
|
||||||
dup2(errpipe[1], STDERR_FILENO) < 0)
|
|
||||||
_exit(126);
|
|
||||||
close(errpipe[1]);
|
|
||||||
if(close_range(3, UINT_MAX, 0) < 0){
|
|
||||||
maxfd = sysconf(_SC_OPEN_MAX);
|
|
||||||
if(maxfd < 0)
|
|
||||||
maxfd = 1024;
|
|
||||||
for(fd = 3; fd < maxfd; fd++)
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
if(setenv("XDG_RUNTIME_DIR", d->runtime, 1) < 0 ||
|
|
||||||
setenv("XDG_CONFIG_HOME", d->config, 1) < 0 ||
|
|
||||||
setenv("HOME", d->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
|
||||||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
|
||||||
unsetenv("IBUS_ADDRESS") < 0){
|
|
||||||
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
|
|
||||||
strerror(errno));
|
|
||||||
_exit(126);
|
|
||||||
}
|
|
||||||
execl(program, program, mapdir, missing, (char*)0);
|
|
||||||
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
|
||||||
_exit(127);
|
|
||||||
}
|
|
||||||
close(errpipe[1]);
|
|
||||||
d->pid = pid;
|
|
||||||
d->errfd = errpipe[0];
|
|
||||||
if(!waitsocket(d, notifyfd)){
|
|
||||||
inotify_rm_watch(notifyfd, watch);
|
|
||||||
close(notifyfd);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
inotify_rm_watch(notifyfd, watch);
|
|
||||||
close(notifyfd);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
clearbus(Daemon *d)
|
|
||||||
{
|
|
||||||
DIR *dir;
|
|
||||||
struct dirent *de;
|
|
||||||
char path[576];
|
|
||||||
int ok;
|
|
||||||
|
|
||||||
dir = opendir(d->bus);
|
|
||||||
if(dir == NULL)
|
|
||||||
return errno == ENOENT || fail("open cleanup directory %s: %s",
|
|
||||||
d->bus, strerror(errno));
|
|
||||||
ok = 1;
|
|
||||||
errno = 0;
|
|
||||||
while((de = readdir(dir)) != NULL){
|
|
||||||
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
|
||||||
continue;
|
|
||||||
if(snprintf(path, sizeof path, "%s/%s", d->bus, de->d_name)
|
|
||||||
>= (int)sizeof path){
|
|
||||||
fail("cleanup path is too long: %s", de->d_name);
|
|
||||||
ok = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(unlink(path) < 0 && errno != ENOENT){
|
|
||||||
fail("remove IBus address file %s: %s", de->d_name,
|
|
||||||
strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
errno = 0;
|
|
||||||
}
|
|
||||||
if(errno != 0){
|
|
||||||
fail("read cleanup directory %s: %s", d->bus, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
closedir(dir);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
rmdirknown(char *path)
|
|
||||||
{
|
|
||||||
if(path[0] == '\0' || rmdir(path) == 0 || errno == ENOENT)
|
|
||||||
return 1;
|
|
||||||
return fail("rmdir %s: %s", path, strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
killdaemon(Daemon *d, int *status)
|
|
||||||
{
|
|
||||||
int n, ok;
|
|
||||||
|
|
||||||
ok = 1;
|
|
||||||
if(kill(d->pid, SIGKILL) < 0 && errno != ESRCH){
|
|
||||||
fail("kill -9 %ld: %s", (long)d->pid, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
do
|
|
||||||
n = waitpid(d->pid, status, 0);
|
|
||||||
while(n < 0 && errno == EINTR);
|
|
||||||
if(n != d->pid){
|
|
||||||
fail("reap %ld after SIGKILL: %s", (long)d->pid,
|
|
||||||
n < 0 ? strerror(errno) : "wrong child");
|
|
||||||
ok = 0;
|
|
||||||
}else
|
|
||||||
d->pid = -1;
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
stopdaemon(Daemon *d)
|
|
||||||
{
|
|
||||||
struct pollfd pfd;
|
|
||||||
int ok, status, n, reaped;
|
|
||||||
int64_t deadline;
|
|
||||||
|
|
||||||
ok = 1;
|
|
||||||
if(d->pid > 0){
|
|
||||||
do
|
|
||||||
n = waitpid(d->pid, &status, WNOHANG);
|
|
||||||
while(n < 0 && errno == EINTR);
|
|
||||||
if(n == d->pid){
|
|
||||||
fail("daemon exited before test termination with wait status %#x",
|
|
||||||
status);
|
|
||||||
d->pid = -1;
|
|
||||||
ok = 0;
|
|
||||||
}else if(n < 0){
|
|
||||||
fail("check daemon %ld before termination: %s",
|
|
||||||
(long)d->pid, strerror(errno));
|
|
||||||
if(errno == ECHILD)
|
|
||||||
d->pid = -1;
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(d->pid > 0){
|
|
||||||
if(kill(d->pid, SIGTERM) < 0){
|
|
||||||
fail("kill %ld: %s", (long)d->pid, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
reaped = 0;
|
|
||||||
deadline = nowms() + Stoptimeout;
|
|
||||||
for(;;){
|
|
||||||
n = waitpid(d->pid, &status, WNOHANG);
|
|
||||||
if(n == d->pid){
|
|
||||||
reaped = 1;
|
|
||||||
d->pid = -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n < 0){
|
|
||||||
fail("waitpid %ld: %s", (long)d->pid, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
if(errno == ECHILD)
|
|
||||||
d->pid = -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
n = leftms(deadline);
|
|
||||||
if(n == 0){
|
|
||||||
fail("daemon %ld did not stop after SIGTERM", (long)d->pid);
|
|
||||||
ok = 0;
|
|
||||||
if(!killdaemon(d, &status))
|
|
||||||
ok = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pfd.fd = d->errfd;
|
|
||||||
pfd.events = POLLIN|POLLHUP;
|
|
||||||
pfd.revents = 0;
|
|
||||||
if(poll(&pfd, 1, n) < 0 && errno != EINTR){
|
|
||||||
fail("poll daemon %ld: %s", (long)d->pid, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
if(!killdaemon(d, &status))
|
|
||||||
ok = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
readerrors(d);
|
|
||||||
}
|
|
||||||
/* plan9port turns a caught termination note into exit status 1. */
|
|
||||||
if(reaped &&
|
|
||||||
!((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) ||
|
|
||||||
(WIFEXITED(status) && WEXITSTATUS(status) == 1))){
|
|
||||||
fail("daemon exited with unexpected wait status %#x", status);
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
readerrors(d);
|
|
||||||
if(d->errfd >= 0){
|
|
||||||
close(d->errfd);
|
|
||||||
d->errfd = -1;
|
|
||||||
}
|
|
||||||
if(d->socket[0] != '\0' && unlink(d->socket) < 0 && errno != ENOENT){
|
|
||||||
fail("remove IPC socket %s: %s", d->socket, strerror(errno));
|
|
||||||
ok = 0;
|
|
||||||
}
|
|
||||||
if(!clearbus(d)) ok = 0;
|
|
||||||
if(!rmdirknown(d->bus)) ok = 0;
|
|
||||||
if(!rmdirknown(d->ibus)) ok = 0;
|
|
||||||
if(!rmdirknown(d->config)) ok = 0;
|
|
||||||
if(!rmdirknown(d->runtime)) ok = 0;
|
|
||||||
if(!rmdirknown(d->home)) ok = 0;
|
|
||||||
if(!rmdirknown(d->root)) ok = 0;
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
connectuntil(char *path, int64_t deadline)
|
|
||||||
{
|
|
||||||
struct sockaddr_un addr;
|
|
||||||
struct pollfd pfd;
|
|
||||||
socklen_t nerr;
|
|
||||||
int err, fd, flags, n;
|
|
||||||
|
|
||||||
memset(&addr, 0, sizeof addr);
|
|
||||||
addr.sun_family = AF_UNIX;
|
|
||||||
if(snprintf(addr.sun_path, sizeof addr.sun_path, "%s", path)
|
|
||||||
>= (int)sizeof addr.sun_path){
|
|
||||||
errno = ENAMETOOLONG;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
|
|
||||||
if(fd < 0)
|
|
||||||
return -1;
|
|
||||||
if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0 &&
|
|
||||||
errno != EINPROGRESS){
|
|
||||||
err = errno;
|
|
||||||
close(fd);
|
|
||||||
errno = err;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
pfd.fd = fd;
|
|
||||||
pfd.events = POLLOUT;
|
|
||||||
pfd.revents = 0;
|
|
||||||
for(;;){
|
|
||||||
n = leftms(deadline);
|
|
||||||
if(n == 0){
|
|
||||||
close(fd);
|
|
||||||
errno = ETIMEDOUT;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
n = poll(&pfd, 1, n);
|
|
||||||
if(n < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(n <= 0){
|
|
||||||
err = n == 0 ? ETIMEDOUT : errno;
|
|
||||||
close(fd);
|
|
||||||
errno = err;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
err = 0;
|
|
||||||
nerr = sizeof err;
|
|
||||||
if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &nerr) < 0 || err != 0){
|
|
||||||
if(err == 0)
|
|
||||||
err = errno;
|
|
||||||
close(fd);
|
|
||||||
errno = err;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
flags = fcntl(fd, F_GETFL);
|
|
||||||
if(flags < 0 || fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0){
|
|
||||||
err = errno;
|
|
||||||
close(fd);
|
|
||||||
errno = err;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 1 is complete, 0 is peer closure, and -1 is a timeout or I/O error. */
|
|
||||||
static int
|
|
||||||
readuntil(int fd, void *buf, size_t n, int64_t deadline)
|
|
||||||
{
|
|
||||||
struct pollfd pfd;
|
|
||||||
unsigned char *p;
|
|
||||||
ssize_t r;
|
|
||||||
int timeout;
|
|
||||||
|
|
||||||
p = buf;
|
|
||||||
while(n > 0){
|
|
||||||
timeout = leftms(deadline);
|
|
||||||
if(timeout == 0){
|
|
||||||
errno = ETIMEDOUT;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
pfd.fd = fd;
|
|
||||||
pfd.events = POLLIN;
|
|
||||||
pfd.revents = 0;
|
|
||||||
r = poll(&pfd, 1, timeout);
|
|
||||||
if(r < 0){
|
|
||||||
if(errno == EINTR)
|
|
||||||
continue;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if(r == 0){
|
|
||||||
errno = ETIMEDOUT;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
r = recv(fd, p, n, 0);
|
|
||||||
if(r < 0 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
if(r < 0)
|
|
||||||
return -1;
|
|
||||||
if(r == 0)
|
|
||||||
return 0;
|
|
||||||
p += r;
|
|
||||||
n -= r;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
getlen(unsigned char p[Ipclensz])
|
getlen(unsigned char p[Ipclensz])
|
||||||
{
|
{
|
||||||
@@ -556,7 +51,8 @@ readresponseuntil(int fd, int want, Response *res, int64_t deadline,
|
|||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
res->eaten = hdr[0] != 0;
|
res->eaten = hdr[0] != 0;
|
||||||
n = getlen(hdr + 1);
|
res->del = hdr[1];
|
||||||
|
n = getlen(hdr + 2);
|
||||||
if(n > Ipcfieldmax){
|
if(n > Ipcfieldmax){
|
||||||
fail("invalid commit length %zu", n);
|
fail("invalid commit length %zu", n);
|
||||||
return -1;
|
return -1;
|
||||||
@@ -616,6 +112,27 @@ sendreset(int fd, int want)
|
|||||||
return ipcsend(fd, req, sizeof req) == 0;
|
return ipcsend(fd, req, sizeof req) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sendcap(int fd, int cap)
|
||||||
|
{
|
||||||
|
unsigned char req[Ipcreqsz];
|
||||||
|
|
||||||
|
ipcpackcap(req, cap);
|
||||||
|
return ipcsend(fd, req, sizeof req) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sendsurround(int fd, char *text)
|
||||||
|
{
|
||||||
|
unsigned char req[Ipcreqsz];
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
n = strlen(text);
|
||||||
|
ipcpacksurround(req, n);
|
||||||
|
return ipcsend(fd, req, sizeof req) == 0 &&
|
||||||
|
(n == 0 || ipcsend(fd, text, n) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
||||||
char *where)
|
char *where)
|
||||||
@@ -632,11 +149,12 @@ expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
requestreset(int fd, int want, int eaten, char *preedit, char *where)
|
requestreset(int fd, int want, int eaten, char *commit, char *preedit,
|
||||||
|
char *where)
|
||||||
{
|
{
|
||||||
if(!sendreset(fd, want))
|
if(!sendreset(fd, want))
|
||||||
return fail("send %s: %s", where, strerror(errno));
|
return fail("send %s: %s", where, strerror(errno));
|
||||||
return expectresponse(fd, want, eaten, "", preedit, where);
|
return expectresponse(fd, want, eaten, commit, preedit, where);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -648,16 +166,43 @@ requestkey(int fd, int want, uint32_t mod, uint32_t key, int eaten,
|
|||||||
return expectresponse(fd, want, eaten, commit, preedit, where);
|
return expectresponse(fd, want, eaten, commit, preedit, where);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The Korean habit: 한자 typed, then the Hanja key. The 한 is the
|
||||||
|
* client's by then, so the daemon must ask for it back and answer with
|
||||||
|
* the whole word.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
requesthanja(int fd)
|
||||||
|
{
|
||||||
|
Response res;
|
||||||
|
|
||||||
|
if(!sendsurround(fd, "저는 한"))
|
||||||
|
return fail("send surrounding text: %s", strerror(errno));
|
||||||
|
if(!requestkey(fd, 1, Mctrl, 's', 1, "", "", "select Korean") ||
|
||||||
|
!requestkey(fd, 1, 0, 'w', 1, "", "ㅈ", "jamo") ||
|
||||||
|
!requestkey(fd, 1, 0, 'k', 1, "", "자", "syllable") ||
|
||||||
|
!requestkey(fd, 1, Mctrl, 'h', 1, "", "자", "Hanja search"))
|
||||||
|
return 0;
|
||||||
|
if(!sendkey(fd, 1, 0, Kret))
|
||||||
|
return fail("send Hanja pick: %s", strerror(errno));
|
||||||
|
if(readresponseuntil(fd, 1, &res, nowms() + Calltimeout, 0) != 1)
|
||||||
|
return 0;
|
||||||
|
if(strcmp(res.commit, "漢字") != 0 || res.del != 1)
|
||||||
|
return fail("Hanja pick committed %s and took back %d",
|
||||||
|
res.commit, res.del);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
tryclient(char *path, int64_t deadline, int *client)
|
tryclient(char *path, int64_t deadline, int *client)
|
||||||
{
|
{
|
||||||
Response res;
|
Response res;
|
||||||
int fd, rv;
|
int fd, rv;
|
||||||
|
|
||||||
fd = connectuntil(path, deadline);
|
fd = connectsocket(path, deadline);
|
||||||
if(fd < 0)
|
if(fd < 0)
|
||||||
return errno == ETIMEDOUT ? -1 : 0;
|
return errno == ETIMEDOUT ? -1 : 0;
|
||||||
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
if(!sendkey(fd, 1, 0, 0)){
|
||||||
close(fd);
|
close(fd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -701,10 +246,10 @@ overflowrejected(char *path)
|
|||||||
int64_t deadline;
|
int64_t deadline;
|
||||||
|
|
||||||
deadline = nowms() + Calltimeout;
|
deadline = nowms() + Calltimeout;
|
||||||
fd = connectuntil(path, deadline);
|
fd = connectsocket(path, deadline);
|
||||||
if(fd < 0)
|
if(fd < 0)
|
||||||
return fail("overflow connect: %s", strerror(errno));
|
return fail("overflow connect: %s", strerror(errno));
|
||||||
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
if(!sendkey(fd, 1, 0, 0)){
|
||||||
close(fd);
|
close(fd);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -742,7 +287,26 @@ overflowrejected(char *path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
runlistener(Daemon *d)
|
runsmoke(char *path)
|
||||||
|
{
|
||||||
|
int client, ok;
|
||||||
|
|
||||||
|
client = connectsocket(path, nowms() + Calltimeout);
|
||||||
|
if(client < 0)
|
||||||
|
return fail("connect client: %s", strerror(errno));
|
||||||
|
ok = sendcap(client, Cappreedit) &&
|
||||||
|
expectresponse(client, 1, 1, "", "", "negotiate preedit") &&
|
||||||
|
requestkey(client, 1, Mctrl, 'n', 1, "", "",
|
||||||
|
"select Japanese") &&
|
||||||
|
requestkey(client, 1, 0, 'k', 1, "", "k", "preedit") &&
|
||||||
|
requestreset(client, 1, 1, "k", "", "reset") &&
|
||||||
|
requesthanja(client);
|
||||||
|
close(client);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
runcapacity(char *path)
|
||||||
{
|
{
|
||||||
int client[Maxclients];
|
int client[Maxclients];
|
||||||
int i, ok;
|
int i, ok;
|
||||||
@@ -750,42 +314,33 @@ runlistener(Daemon *d)
|
|||||||
for(i = 0; i < Maxclients; i++)
|
for(i = 0; i < Maxclients; i++)
|
||||||
client[i] = -1;
|
client[i] = -1;
|
||||||
ok = 0;
|
ok = 0;
|
||||||
client[0] = connectuntil(d->socket, nowms() + Calltimeout);
|
client[0] = connectsocket(path, nowms() + Calltimeout);
|
||||||
if(client[0] < 0){
|
if(client[0] < 0){
|
||||||
fail("connect first client: %s", strerror(errno));
|
fail("connect first client: %s", strerror(errno));
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
if(!requestkey(client[0], 1, Mctrl, 'n', 1, "", "", "select Japanese") ||
|
if(!requestkey(client[0], 1, Mctrl, 'n', 1, "", "",
|
||||||
!requestkey(client[0], 1, 0, 'k', 1, "", "k", "GTK preedit") ||
|
"select Japanese"))
|
||||||
!requestreset(client[0], 1, 1, "", "active reset"))
|
|
||||||
goto out;
|
|
||||||
if(!sendkey(client[0], 0, 0, 'k') ||
|
|
||||||
!sendkey(client[0], 1, 0, Kmodfirst)){
|
|
||||||
fail("send pipelined XIM/GTK requests: %s", strerror(errno));
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if(!expectresponse(client[0], 0, 1, "", "", "XIM framing") ||
|
|
||||||
!expectresponse(client[0], 1, 0, "", "k", "GTK after XIM framing"))
|
|
||||||
goto out;
|
goto out;
|
||||||
for(i = 1; i < Maxclients; i++){
|
for(i = 1; i < Maxclients; i++){
|
||||||
client[i] = connectuntil(d->socket, nowms() + Calltimeout);
|
client[i] = connectsocket(path, nowms() + Calltimeout);
|
||||||
if(client[i] < 0){
|
if(client[i] < 0){
|
||||||
fail("connect capacity client %d: %s", i, strerror(errno));
|
fail("connect capacity client %d: %s", i, strerror(errno));
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
if(!requestkey(client[i], 1, 0, Kmodfirst, 0, "", "",
|
if(!requestkey(client[i], 1, 0, 0, 0, "", "",
|
||||||
"capacity modifier"))
|
"capacity modifier"))
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
if(!overflowrejected(d->socket))
|
if(!overflowrejected(path))
|
||||||
goto out;
|
goto out;
|
||||||
close(client[Maxclients-1]);
|
close(client[Maxclients-1]);
|
||||||
client[Maxclients-1] = -1;
|
client[Maxclients-1] = -1;
|
||||||
if(!waitslot(d->socket, &client[Maxclients-1], "inactive slot recovery"))
|
if(!waitslot(path, &client[Maxclients-1], "inactive slot recovery"))
|
||||||
goto out;
|
goto out;
|
||||||
close(client[0]);
|
close(client[0]);
|
||||||
client[0] = -1;
|
client[0] = -1;
|
||||||
if(!waitslot(d->socket, &client[0], "active slot recovery") ||
|
if(!waitslot(path, &client[0], "active slot recovery") ||
|
||||||
!requestkey(client[0], 1, 0, 'a', 1, "", "あ",
|
!requestkey(client[0], 1, 0, 'a', 1, "", "あ",
|
||||||
"post-disconnect composition"))
|
"post-disconnect composition"))
|
||||||
goto out;
|
goto out;
|
||||||
@@ -801,21 +356,35 @@ int
|
|||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
Daemon daemon;
|
Daemon daemon;
|
||||||
int ok;
|
Live live;
|
||||||
|
int capacity, ok;
|
||||||
|
|
||||||
if(argc != 3){
|
testname = "ipc_live_test";
|
||||||
fprintf(stderr, "usage: ipc_live_test strans mapdir\n");
|
capacity = argc == 4 && strcmp(argv[1], "--capacity") == 0;
|
||||||
|
if((!capacity && argc != 3) || (capacity && argc != 4)){
|
||||||
|
fprintf(stderr,
|
||||||
|
"usage: ipc_live_test [--capacity] strans mapdir\n");
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
ok = startdaemon(&daemon, argv[1], argv[2]);
|
daemoninit(&daemon, "daemon");
|
||||||
|
ok = livesetup(&live, "ipc") &&
|
||||||
|
startdaemon(&live, &daemon, argv[1+capacity], argv[2+capacity]) &&
|
||||||
|
waitready(&live, &daemon, NULL, 0);
|
||||||
if(ok)
|
if(ok)
|
||||||
ok = runlistener(&daemon);
|
ok = capacity ? runcapacity(live.socket) : runsmoke(live.socket);
|
||||||
if(!ok)
|
if(!ok)
|
||||||
showerrors(&daemon);
|
showerrors(&daemon);
|
||||||
if(!stopdaemon(&daemon))
|
if(!stopdaemon(&daemon))
|
||||||
ok = 0;
|
ok = 0;
|
||||||
|
/* SIGTERM must leave no socket behind. */
|
||||||
|
if(access(live.socket, F_OK) == 0)
|
||||||
|
ok = fail("socket %s survived the daemon", live.socket);
|
||||||
|
if(!closeerrors(&daemon))
|
||||||
|
ok = 0;
|
||||||
|
if(!liveclean(&live))
|
||||||
|
ok = 0;
|
||||||
if(!ok)
|
if(!ok)
|
||||||
return 1;
|
return 1;
|
||||||
printf("ipc live listener: ok\n");
|
printf("ipc %s: ok\n", capacity ? "connection capacity" : "live smoke");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
322
tests/ipc_test.c
322
tests/ipc_test.c
@@ -1,10 +1,33 @@
|
|||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
|
#undef accept
|
||||||
|
#undef listen
|
||||||
|
#undef send
|
||||||
|
|
||||||
|
static int64_t
|
||||||
|
nowms(void)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
||||||
|
return 0;
|
||||||
|
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ipcclientdeadlines(struct ct*);
|
||||||
|
static void ipcconnectcloexec(struct ct*);
|
||||||
|
|
||||||
void
|
void
|
||||||
ipc_masks_modifiers(struct ct *t)
|
ipc_masks_modifiers(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -25,6 +48,105 @@ ipc_masks_modifiers(struct ct *t)
|
|||||||
CT_EQ_UINT(t, 0, mod);
|
CT_EQ_UINT(t, 0, mod);
|
||||||
CT_EQ_UINT(t, 0, key);
|
CT_EQ_UINT(t, 0, key);
|
||||||
CT_CHECK(t, ipcreqreset(buf));
|
CT_CHECK(t, ipcreqreset(buf));
|
||||||
|
|
||||||
|
/* Frontends share one keysym and modifier mapping. */
|
||||||
|
CT_EQ_UINT(t, 'a', ipckeysym('a', 'a'));
|
||||||
|
CT_EQ_UINT(t, '1', ipckeysym(0xffb1, '1')); /* KP_1 */
|
||||||
|
CT_EQ_UINT(t, Kret, ipckeysym(0xff0d, '\r'));
|
||||||
|
CT_EQ_UINT(t, Kret, ipckeysym(0xff8d, '\r')); /* KP_Enter */
|
||||||
|
CT_EQ_UINT(t, Ktab, ipckeysym(0xfe20, 0)); /* ISO_Left_Tab */
|
||||||
|
CT_EQ_UINT(t, Kdown, ipckeysym(0xff99, 0)); /* KP_Down */
|
||||||
|
CT_EQ_UINT(t, Kback, ipckeysym(0xff08, 8));
|
||||||
|
CT_EQ_UINT(t, Kspec + 0xff, ipckeysym(0xffff, 0x7f)); /* Delete */
|
||||||
|
CT_EQ_UINT(t, 0x1f642, ipckeysym(0x101f642, 0x1f642));
|
||||||
|
CT_EQ_UINT(t, 0, ipckeysym(0xfe03, 0)); /* ISO_Level3_Shift */
|
||||||
|
CT_EQ_UINT(t, 0, ipckeysym(0xffe1, 0)); /* Shift_L */
|
||||||
|
CT_EQ_UINT(t, Mshift|Mctrl, ipcmod(Mshift|Mctrl|(1<<1)|(1<<4)));
|
||||||
|
CT_EQ_UINT(t, Msuper, ipcmod(1<<6));
|
||||||
|
CT_EQ_UINT(t, Msuper, ipcmod(1<<26));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ipc_control_and_caret_frames(struct ct *t)
|
||||||
|
{
|
||||||
|
static const uchar capon[Ipcreqsz] = {
|
||||||
|
Ipcext|Ipcreqwant, Ipcversion, Ipcopcap, 0, 0, 0,
|
||||||
|
};
|
||||||
|
static const uchar capoff[Ipcreqsz] = {
|
||||||
|
Ipcext, Ipcversion, Ipcopcap, 0, 0, 0,
|
||||||
|
};
|
||||||
|
static const uchar caret[Ipccaretsz] = {
|
||||||
|
Ipcext, Ipcversion, Ipcopcaret, 1,
|
||||||
|
0xfe, 0xff, 0xff, 0xff,
|
||||||
|
0x04, 0x03, 0x02, 0x01,
|
||||||
|
0x06, 0x05, 0x00, 0x00,
|
||||||
|
};
|
||||||
|
static const uchar nocaret[Ipccaretsz] = {
|
||||||
|
Ipcext, Ipcversion, Ipcopcaret, 0,
|
||||||
|
};
|
||||||
|
uchar buf[Ipccaretsz], bad[Ipccaretsz];
|
||||||
|
u32int key, mod;
|
||||||
|
int valid, want;
|
||||||
|
int32_t x, y, h;
|
||||||
|
|
||||||
|
ipcpackcap(buf, 1);
|
||||||
|
CT_EQ_MEM(t, capon, buf, sizeof capon);
|
||||||
|
CT_EQ_INT(t, Ipccap, ipcreqtype(buf));
|
||||||
|
/* The probe remains a harmless key-zero request to an old daemon. */
|
||||||
|
ipcunpackreq(buf, &want, &mod, &key);
|
||||||
|
CT_EQ_INT(t, 1, want);
|
||||||
|
CT_EQ_UINT(t, 0, key);
|
||||||
|
ipcpackcap(buf, 0);
|
||||||
|
CT_EQ_MEM(t, capoff, buf, sizeof capoff);
|
||||||
|
ipcunpackreq(buf, &want, &mod, &key);
|
||||||
|
CT_EQ_INT(t, 0, want);
|
||||||
|
CT_EQ_UINT(t, 0, key);
|
||||||
|
|
||||||
|
ipcpackcaret(buf, 1, -2, 0x01020304, 0x506);
|
||||||
|
CT_EQ_MEM(t, caret, buf, sizeof caret);
|
||||||
|
CT_EQ_INT(t, Ipccaret, ipcreqtype(buf));
|
||||||
|
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h))){
|
||||||
|
CT_EQ_INT(t, 1, valid);
|
||||||
|
CT_EQ_INT(t, -2, x);
|
||||||
|
CT_EQ_INT(t, 0x01020304, y);
|
||||||
|
CT_EQ_INT(t, 0x506, h);
|
||||||
|
}
|
||||||
|
ipcpackcaret(buf, 1, INT32_MIN, INT32_MAX, 0);
|
||||||
|
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h))){
|
||||||
|
CT_EQ_INT(t, INT32_MIN, x);
|
||||||
|
CT_EQ_INT(t, INT32_MAX, y);
|
||||||
|
CT_EQ_INT(t, 0, h);
|
||||||
|
}
|
||||||
|
ipcpackcaret(buf, 0, -2, 3, 4);
|
||||||
|
CT_EQ_MEM(t, nocaret, buf, sizeof nocaret);
|
||||||
|
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h)))
|
||||||
|
CT_EQ_INT(t, 0, valid);
|
||||||
|
|
||||||
|
memcpy(bad, caret, sizeof bad);
|
||||||
|
bad[1]++;
|
||||||
|
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
||||||
|
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
||||||
|
memcpy(bad, caret, sizeof bad);
|
||||||
|
bad[2]++;
|
||||||
|
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
||||||
|
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
||||||
|
memcpy(bad, caret, sizeof bad);
|
||||||
|
bad[3] = 2;
|
||||||
|
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
||||||
|
memcpy(bad, caret, sizeof bad);
|
||||||
|
bad[0] |= Ipcreqwant;
|
||||||
|
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
||||||
|
memcpy(bad, caret, sizeof bad);
|
||||||
|
bad[15] = 0x80;
|
||||||
|
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
||||||
|
memcpy(bad, capon, sizeof capon);
|
||||||
|
bad[3] = 1;
|
||||||
|
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
||||||
|
|
||||||
|
ipcpackreq(buf, 1, 0, 'a');
|
||||||
|
CT_EQ_INT(t, Ipckey, ipcreqtype(buf));
|
||||||
|
ipcpackreset(buf, 1);
|
||||||
|
CT_EQ_INT(t, Ipckey, ipcreqtype(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -45,7 +167,6 @@ ipc_runtime_path(struct ct *t)
|
|||||||
snprint(fallback, sizeof fallback, "/tmp/strans.%d", getuid());
|
snprint(fallback, sizeof fallback, "/tmp/strans.%d", getuid());
|
||||||
CT_EQ_STR(t, fallback, buf);
|
CT_EQ_STR(t, fallback, buf);
|
||||||
CT_EQ_INT(t, -1, ipcpath(buf, 4));
|
CT_EQ_INT(t, -1, ipcpath(buf, 4));
|
||||||
CT_EQ_INT(t, -1, ipcpath(nil, sizeof buf));
|
|
||||||
if(saved != nil){
|
if(saved != nil){
|
||||||
setenv("XDG_RUNTIME_DIR", saved, 1);
|
setenv("XDG_RUNTIME_DIR", saved, 1);
|
||||||
free(saved);
|
free(saved);
|
||||||
@@ -56,50 +177,49 @@ ipc_runtime_path(struct ct *t)
|
|||||||
void
|
void
|
||||||
ipc_response_pack_boundaries(struct ct *t)
|
ipc_response_pack_boundaries(struct ct *t)
|
||||||
{
|
{
|
||||||
static const uchar withpre[] = { 1, 1, 0, 'A', 2, 0, 'x', 'y' };
|
static const uchar withpre[] = { 1, 3, 1, 0, 'A', 2, 0, 'x', 'y' };
|
||||||
static const uchar withoutpre[] = { 1, 1, 0, 'A' };
|
static const uchar withoutpre[] = { 1, 0, 1, 0, 'A' };
|
||||||
uchar out[Ipcmaxresp+1], field[Ipcfieldmax];
|
uchar out[Ipcmaxresp+1], field[Ipcfieldmax];
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
n = ipcpackresp(out, sizeof out, 7, "A", 1, "xy", 2, 1);
|
n = ipcpackresp(out, sizeof out, 7, 3, "A", 1, "xy", 2, 1);
|
||||||
CT_EQ_INT(t, sizeof withpre, n);
|
CT_EQ_INT(t, sizeof withpre, n);
|
||||||
CT_EQ_MEM(t, withpre, out, sizeof withpre);
|
CT_EQ_MEM(t, withpre, out, sizeof withpre);
|
||||||
n = ipcpackresp(out, sizeof out, 1, "A", 1, "xy", 2, 0);
|
n = ipcpackresp(out, sizeof out, 1, 0, "A", 1, "xy", 2, 0);
|
||||||
CT_EQ_INT(t, sizeof withoutpre, n);
|
CT_EQ_INT(t, sizeof withoutpre, n);
|
||||||
CT_EQ_MEM(t, withoutpre, out, sizeof withoutpre);
|
CT_EQ_MEM(t, withoutpre, out, sizeof withoutpre);
|
||||||
n = ipcpackresp(out, sizeof out, 0, nil, 0, nil, 0, 1);
|
n = ipcpackresp(out, sizeof out, 0, 0, nil, 0, nil, 0, 1);
|
||||||
CT_EQ_INT(t, Ipcresphdrsz + Ipclensz, n);
|
CT_EQ_INT(t, Ipcresphdrsz + Ipclensz, n);
|
||||||
|
|
||||||
memset(field, 'x', sizeof field);
|
memset(field, 'x', sizeof field);
|
||||||
memset(out, 0xa5, sizeof out);
|
memset(out, 0xa5, sizeof out);
|
||||||
n = ipcpackresp(out, Ipcmaxresp, 1,
|
n = ipcpackresp(out, Ipcmaxresp, 1, 0,
|
||||||
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
||||||
CT_EQ_INT(t, Ipcmaxresp, n);
|
CT_EQ_INT(t, Ipcmaxresp, n);
|
||||||
CT_EQ_INT(t, 0, out[1]);
|
CT_EQ_INT(t, 0, out[2]);
|
||||||
CT_EQ_INT(t, 1, out[2]);
|
CT_EQ_INT(t, 1, out[3]);
|
||||||
CT_EQ_INT(t, 0, out[3+Ipcfieldmax]);
|
CT_EQ_INT(t, 0, out[4+Ipcfieldmax]);
|
||||||
CT_EQ_INT(t, 1, out[4+Ipcfieldmax]);
|
CT_EQ_INT(t, 1, out[5+Ipcfieldmax]);
|
||||||
CT_EQ_INT(t, 0xa5, out[Ipcmaxresp]);
|
CT_EQ_INT(t, 0xa5, out[Ipcmaxresp]);
|
||||||
CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1,
|
CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1, 0,
|
||||||
(char*)field, sizeof field, (char*)field, sizeof field, 1));
|
(char*)field, sizeof field, (char*)field, sizeof field, 1));
|
||||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, 0,
|
||||||
nil, 1, nil, 0, 0));
|
(char*)field, Ipcfieldmax+1, "", 0, 0));
|
||||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
/* A take-back is a rune count and never leaves its byte. */
|
||||||
nil, 0, nil, 1, 1));
|
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, -1, "", 0, "", 0, 0));
|
||||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, 256, "", 0, "", 0, 0));
|
||||||
(char*)field, Ipcfieldmax+1, nil, 0, 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ipc_response_empty_and_preedit(struct ct *t)
|
ipc_response_empty_and_preedit(struct ct *t)
|
||||||
{
|
{
|
||||||
uchar first[Ipcmaxresp], second[Ipcmaxresp];
|
uchar first[Ipcmaxresp], second[Ipcmaxresp];
|
||||||
char commit[16], preedit[16];
|
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
int fd[2], nfirst, nsecond;
|
int fd[2], nfirst, nsecond;
|
||||||
|
|
||||||
nfirst = ipcpackresp(first, sizeof first, 0, nil, 0, nil, 0, 0);
|
nfirst = ipcpackresp(first, sizeof first, 0, 0, "", 0, "", 0, 0);
|
||||||
nsecond = ipcpackresp(second, sizeof second, 1,
|
nsecond = ipcpackresp(second, sizeof second, 1, 0,
|
||||||
"go", 2, "kana", 4, 1);
|
"go", 2, "kana", 4, 1);
|
||||||
if(!CT_CHECK(t, nfirst > 0 && nsecond > 0))
|
if(!CT_CHECK(t, nfirst > 0 && nsecond > 0))
|
||||||
return;
|
return;
|
||||||
@@ -109,18 +229,16 @@ ipc_response_empty_and_preedit(struct ct *t)
|
|||||||
}
|
}
|
||||||
if(CT_EQ_INT(t, 0, ipcsend(fd[0], first, nfirst)) &&
|
if(CT_EQ_INT(t, 0, ipcsend(fd[0], first, nfirst)) &&
|
||||||
CT_EQ_INT(t, 0, ipcsend(fd[0], second, nsecond)) &&
|
CT_EQ_INT(t, 0, ipcsend(fd[0], second, nsecond)) &&
|
||||||
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, sizeof commit,
|
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, preedit, &resp))){
|
||||||
nil, 0, &resp))){
|
|
||||||
CT_EQ_INT(t, 0, resp.eaten);
|
CT_EQ_INT(t, 0, resp.eaten);
|
||||||
CT_EQ_SIZE(t, 0, resp.ncommit);
|
CT_EQ_SIZE(t, 0, resp.commitlen);
|
||||||
CT_EQ_SIZE(t, 0, resp.npreedit);
|
CT_EQ_SIZE(t, 0, resp.preeditlen);
|
||||||
CT_EQ_STR(t, "", commit);
|
CT_EQ_STR(t, "", commit);
|
||||||
}
|
}
|
||||||
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, sizeof commit,
|
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit, &resp))){
|
||||||
preedit, sizeof preedit, &resp))){
|
|
||||||
CT_EQ_INT(t, 1, resp.eaten);
|
CT_EQ_INT(t, 1, resp.eaten);
|
||||||
CT_EQ_SIZE(t, 2, resp.ncommit);
|
CT_EQ_SIZE(t, 2, resp.commitlen);
|
||||||
CT_EQ_SIZE(t, 4, resp.npreedit);
|
CT_EQ_SIZE(t, 4, resp.preeditlen);
|
||||||
CT_EQ_STR(t, "go", commit);
|
CT_EQ_STR(t, "go", commit);
|
||||||
CT_EQ_STR(t, "kana", preedit);
|
CT_EQ_STR(t, "kana", preedit);
|
||||||
}
|
}
|
||||||
@@ -132,14 +250,14 @@ void
|
|||||||
ipc_response_max_and_drain(struct ct *t)
|
ipc_response_max_and_drain(struct ct *t)
|
||||||
{
|
{
|
||||||
uchar first[Ipcmaxresp], second[Ipcmaxresp], field[Ipcfieldmax];
|
uchar first[Ipcmaxresp], second[Ipcmaxresp], field[Ipcfieldmax];
|
||||||
char commit[8], preedit[8];
|
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
int fd[2], nfirst, nsecond;
|
int fd[2], nfirst, nsecond;
|
||||||
|
|
||||||
memset(field, 'x', sizeof field);
|
memset(field, 'x', sizeof field);
|
||||||
nfirst = ipcpackresp(first, sizeof first, 1,
|
nfirst = ipcpackresp(first, sizeof first, 1, 0,
|
||||||
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
||||||
nsecond = ipcpackresp(second, sizeof second, 0, "ok", 2, nil, 0, 0);
|
nsecond = ipcpackresp(second, sizeof second, 0, 0, "ok", 2, "", 0, 0);
|
||||||
if(!CT_CHECK(t, nfirst == Ipcmaxresp && nsecond > 0))
|
if(!CT_CHECK(t, nfirst == Ipcmaxresp && nsecond > 0))
|
||||||
return;
|
return;
|
||||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
||||||
@@ -148,15 +266,15 @@ ipc_response_max_and_drain(struct ct *t)
|
|||||||
}
|
}
|
||||||
if(CT_EQ_INT(t, 0, ipcsend(fd[0], first, nfirst)) &&
|
if(CT_EQ_INT(t, 0, ipcsend(fd[0], first, nfirst)) &&
|
||||||
CT_EQ_INT(t, 0, ipcsend(fd[0], second, nsecond)) &&
|
CT_EQ_INT(t, 0, ipcsend(fd[0], second, nsecond)) &&
|
||||||
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, sizeof commit,
|
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit, &resp))){
|
||||||
preedit, sizeof preedit, &resp))){
|
CT_EQ_SIZE(t, Ipcfieldmax, resp.commitlen);
|
||||||
CT_EQ_SIZE(t, Ipcfieldmax, resp.ncommit);
|
CT_EQ_SIZE(t, Ipcfieldmax, resp.preeditlen);
|
||||||
CT_EQ_SIZE(t, Ipcfieldmax, resp.npreedit);
|
CT_EQ_MEM(t, field, commit, Ipcfieldmax);
|
||||||
CT_EQ_STR(t, "xxxxxxx", commit);
|
CT_EQ_MEM(t, field, preedit, Ipcfieldmax);
|
||||||
CT_EQ_STR(t, "xxxxxxx", preedit);
|
CT_EQ_INT(t, 0, commit[Ipcfieldmax]);
|
||||||
|
CT_EQ_INT(t, 0, preedit[Ipcfieldmax]);
|
||||||
}
|
}
|
||||||
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, sizeof commit,
|
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, preedit, &resp)))
|
||||||
nil, 0, &resp)))
|
|
||||||
CT_EQ_STR(t, "ok", commit);
|
CT_EQ_STR(t, "ok", commit);
|
||||||
close(fd[0]);
|
close(fd[0]);
|
||||||
close(fd[1]);
|
close(fd[1]);
|
||||||
@@ -166,11 +284,11 @@ void
|
|||||||
ipc_response_fragmented_and_truncated(struct ct *t)
|
ipc_response_fragmented_and_truncated(struct ct *t)
|
||||||
{
|
{
|
||||||
uchar frame[Ipcmaxresp];
|
uchar frame[Ipcmaxresp];
|
||||||
char commit[8], preedit[8];
|
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
int fd[2], i, n;
|
int fd[2], i, n;
|
||||||
|
|
||||||
n = ipcpackresp(frame, sizeof frame, 1, "abc", 3, "xy", 2, 1);
|
n = ipcpackresp(frame, sizeof frame, 1, 2, "abc", 3, "xy", 2, 1);
|
||||||
if(!CT_CHECK(t, n > 0))
|
if(!CT_CHECK(t, n > 0))
|
||||||
return;
|
return;
|
||||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
||||||
@@ -182,8 +300,9 @@ ipc_response_fragmented_and_truncated(struct ct *t)
|
|||||||
CT_ERRORF(t, "fragment send failed");
|
CT_ERRORF(t, "fragment send failed");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(i == n && CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1,
|
if(i == n && CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit,
|
||||||
commit, sizeof commit, preedit, sizeof preedit, &resp))){
|
&resp))){
|
||||||
|
CT_EQ_INT(t, 2, resp.del);
|
||||||
CT_EQ_STR(t, "abc", commit);
|
CT_EQ_STR(t, "abc", commit);
|
||||||
CT_EQ_STR(t, "xy", preedit);
|
CT_EQ_STR(t, "xy", preedit);
|
||||||
}
|
}
|
||||||
@@ -196,8 +315,19 @@ ipc_response_fragmented_and_truncated(struct ct *t)
|
|||||||
}
|
}
|
||||||
CT_EQ_INT(t, 0, ipcsend(fd[0], frame, n-1));
|
CT_EQ_INT(t, 0, ipcsend(fd[0], frame, n-1));
|
||||||
shutdown(fd[0], SHUT_WR);
|
shutdown(fd[0], SHUT_WR);
|
||||||
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, sizeof commit,
|
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, preedit, &resp));
|
||||||
preedit, sizeof preedit, &resp));
|
close(fd[0]);
|
||||||
|
close(fd[1]);
|
||||||
|
|
||||||
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
||||||
|
CT_ERRORF(t, "socketpair failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frame[0] = 2;
|
||||||
|
CT_EQ_INT(t, 0, ipcsend(fd[0], frame, n));
|
||||||
|
errno = 0;
|
||||||
|
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, preedit, &resp));
|
||||||
|
CT_EQ_INT(t, EPROTO, errno);
|
||||||
close(fd[0]);
|
close(fd[0]);
|
||||||
close(fd[1]);
|
close(fd[1]);
|
||||||
}
|
}
|
||||||
@@ -214,4 +344,104 @@ ipc_broken_peer_send(struct ct *t)
|
|||||||
close(fd[1]);
|
close(fd[1]);
|
||||||
CT_EQ_INT(t, -1, ipcsend(fd[0], "x", 1));
|
CT_EQ_INT(t, -1, ipcsend(fd[0], "x", 1));
|
||||||
close(fd[0]);
|
close(fd[0]);
|
||||||
|
ipcclientdeadlines(t);
|
||||||
|
ipcconnectcloexec(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ipcclientdeadlines(struct ct *t)
|
||||||
|
{
|
||||||
|
char fill[4096], commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
||||||
|
Ipcresp resp;
|
||||||
|
int fd[2];
|
||||||
|
int64_t start, elapsed;
|
||||||
|
ssize_t n;
|
||||||
|
|
||||||
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
||||||
|
CT_ERRORF(t, "socketpair failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
start = nowms();
|
||||||
|
errno = 0;
|
||||||
|
CT_EQ_INT(t, -1, ipcreadresp(fd[0], 0, commit, preedit, &resp));
|
||||||
|
elapsed = nowms() - start;
|
||||||
|
CT_EQ_INT(t, ETIMEDOUT, errno);
|
||||||
|
CT_CHECK(t, elapsed >= 0 && elapsed < 4*Ipcwaitms);
|
||||||
|
|
||||||
|
memset(fill, 'x', sizeof fill);
|
||||||
|
for(;;){
|
||||||
|
n = send(fd[0], fill, sizeof fill, MSG_DONTWAIT|MSG_NOSIGNAL);
|
||||||
|
if(n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
|
||||||
|
break;
|
||||||
|
if(n <= 0){
|
||||||
|
CT_ERRORF(t, "fill send failed: %s", strerror(errno));
|
||||||
|
close(fd[0]);
|
||||||
|
close(fd[1]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start = nowms();
|
||||||
|
errno = 0;
|
||||||
|
CT_EQ_INT(t, -1, ipcsend(fd[0], "x", 1));
|
||||||
|
elapsed = nowms() - start;
|
||||||
|
CT_EQ_INT(t, ETIMEDOUT, errno);
|
||||||
|
CT_CHECK(t, elapsed >= 0 && elapsed < 4*Ipcwaitms);
|
||||||
|
close(fd[0]);
|
||||||
|
close(fd[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ipcconnectcloexec(struct ct *t)
|
||||||
|
{
|
||||||
|
struct sockaddr_un addr;
|
||||||
|
char root[] = "/tmp/strans-ipc.XXXXXX";
|
||||||
|
char *old, *saved;
|
||||||
|
int accepted, client, flags, full, listener;
|
||||||
|
|
||||||
|
accepted = client = full = listener = -1;
|
||||||
|
old = getenv("XDG_RUNTIME_DIR");
|
||||||
|
saved = old == nil ? nil : strdup(old);
|
||||||
|
if(mkdtemp(root) == nil){
|
||||||
|
CT_ERRORF(t, "mkdtemp failed: %s", strerror(errno));
|
||||||
|
free(saved);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setenv("XDG_RUNTIME_DIR", root, 1);
|
||||||
|
memset(&addr, 0, sizeof addr);
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
if(ipcpath(addr.sun_path, sizeof addr.sun_path) < 0 ||
|
||||||
|
(listener = socket(AF_UNIX, SOCK_STREAM, 0)) < 0 ||
|
||||||
|
bind(listener, (struct sockaddr*)&addr, sizeof addr) < 0 ||
|
||||||
|
listen(listener, 0) < 0){
|
||||||
|
CT_ERRORF(t, "listen failed: %s", strerror(errno));
|
||||||
|
goto Out;
|
||||||
|
}
|
||||||
|
client = ipcconnect();
|
||||||
|
if(!CT_CHECK(t, client >= 0))
|
||||||
|
goto Out;
|
||||||
|
flags = fcntl(client, F_GETFD);
|
||||||
|
CT_CHECK(t, flags >= 0 && (flags & FD_CLOEXEC) != 0);
|
||||||
|
errno = 0;
|
||||||
|
full = ipcconnect();
|
||||||
|
CT_EQ_INT(t, -1, full);
|
||||||
|
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
|
||||||
|
accepted = accept(listener, nil, nil);
|
||||||
|
if(!CT_CHECK(t, accepted >= 0))
|
||||||
|
goto Out;
|
||||||
|
Out:
|
||||||
|
if(full >= 0)
|
||||||
|
close(full);
|
||||||
|
if(accepted >= 0)
|
||||||
|
close(accepted);
|
||||||
|
if(client >= 0)
|
||||||
|
close(client);
|
||||||
|
if(listener >= 0)
|
||||||
|
close(listener);
|
||||||
|
unlink(addr.sun_path);
|
||||||
|
rmdir(root);
|
||||||
|
if(saved != nil){
|
||||||
|
setenv("XDG_RUNTIME_DIR", saved, 1);
|
||||||
|
free(saved);
|
||||||
|
}else
|
||||||
|
unsetenv("XDG_RUNTIME_DIR");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,14 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
static Str
|
static Str
|
||||||
kotrans(char *keys)
|
kotrans(char *keys)
|
||||||
{
|
{
|
||||||
Emit e;
|
Str out, raw;
|
||||||
Im state;
|
|
||||||
Str out;
|
|
||||||
char *p;
|
|
||||||
|
|
||||||
memset(&state, 0, sizeof state);
|
raw = mkstr(keys);
|
||||||
state.l = getlang(LangKO);
|
transstr(getlang(LangKO), nil, &raw, &out);
|
||||||
sclear(&out);
|
|
||||||
for(p = keys; *p != '\0'; p++){
|
|
||||||
e = transko(&state, (uchar)*p);
|
|
||||||
sappend(&out, &e.s);
|
|
||||||
state.pre = e.next;
|
|
||||||
if(!e.eat)
|
|
||||||
sputr(&out, (uchar)*p);
|
|
||||||
}
|
|
||||||
sappend(&out, &state.pre);
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,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;
|
||||||
@@ -91,6 +87,7 @@ korean_backspace(struct ct *t)
|
|||||||
{
|
{
|
||||||
static const struct { char *before, *after; } cases[] = {
|
static const struct { char *before, *after; } cases[] = {
|
||||||
{ "ㄱ", "" },
|
{ "ㄱ", "" },
|
||||||
|
{ "ㄳ", "ㄱ" },
|
||||||
{ "가", "ㄱ" },
|
{ "가", "ㄱ" },
|
||||||
{ "과", "고" }, { "괘", "고" }, { "괴", "고" },
|
{ "과", "고" }, { "괘", "고" }, { "괴", "고" },
|
||||||
{ "궈", "구" }, { "궤", "구" }, { "귀", "구" },
|
{ "궈", "구" }, { "궤", "구" }, { "귀", "구" },
|
||||||
|
|||||||
774
tests/live.c
Normal file
774
tests/live.c
Normal file
@@ -0,0 +1,774 @@
|
|||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "ipc.h"
|
||||||
|
#include "live.h"
|
||||||
|
|
||||||
|
char *testname = "live_test";
|
||||||
|
|
||||||
|
int
|
||||||
|
fail(char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
fprintf(stderr, "%s: ", testname);
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vfprintf(stderr, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
fputc('\n', stderr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t
|
||||||
|
nowms(void)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
||||||
|
return -1;
|
||||||
|
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A clock that cannot be read counts as an expired deadline. */
|
||||||
|
int
|
||||||
|
leftms(int64_t deadline)
|
||||||
|
{
|
||||||
|
int64_t n;
|
||||||
|
|
||||||
|
n = nowms();
|
||||||
|
if(n < 0)
|
||||||
|
return 0;
|
||||||
|
n = deadline - n;
|
||||||
|
if(n <= 0)
|
||||||
|
return 0;
|
||||||
|
return n > INT_MAX ? INT_MAX : (int)n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
pausems(int ms)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
ts.tv_sec = ms / 1000;
|
||||||
|
ts.tv_nsec = (ms % 1000) * 1000000L;
|
||||||
|
while(nanosleep(&ts, &ts) < 0 && errno == EINTR)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
makedir(char *path)
|
||||||
|
{
|
||||||
|
if(mkdir(path, 0700) == 0)
|
||||||
|
return 1;
|
||||||
|
return fail("mkdir %s: %s", path, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cleardir(char *path)
|
||||||
|
{
|
||||||
|
DIR *dir;
|
||||||
|
struct dirent *de;
|
||||||
|
char name[576];
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
if(path[0] == '\0')
|
||||||
|
return 1;
|
||||||
|
dir = opendir(path);
|
||||||
|
if(dir == NULL)
|
||||||
|
return errno == ENOENT ||
|
||||||
|
fail("open %s: %s", path, strerror(errno));
|
||||||
|
ok = 1;
|
||||||
|
errno = 0;
|
||||||
|
while((de = readdir(dir)) != NULL){
|
||||||
|
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
||||||
|
continue;
|
||||||
|
if(snprintf(name, sizeof name, "%s/%s", path, de->d_name)
|
||||||
|
>= (int)sizeof name)
|
||||||
|
ok = fail("cleanup path is too long: %s", de->d_name);
|
||||||
|
else if(unlink(name) < 0 && errno != ENOENT)
|
||||||
|
ok = fail("remove %s: %s", name, strerror(errno));
|
||||||
|
errno = 0;
|
||||||
|
}
|
||||||
|
if(errno != 0)
|
||||||
|
ok = fail("read %s: %s", path, strerror(errno));
|
||||||
|
if(closedir(dir) < 0)
|
||||||
|
ok = fail("close %s: %s", path, strerror(errno));
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rmdirknown(char *path)
|
||||||
|
{
|
||||||
|
if(path[0] == '\0' || rmdir(path) == 0 || errno == ENOENT)
|
||||||
|
return 1;
|
||||||
|
return fail("rmdir %s: %s", path, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
livesetup(Live *l, char *prefix)
|
||||||
|
{
|
||||||
|
memset(l, 0, sizeof *l);
|
||||||
|
if(nowms() < 0)
|
||||||
|
return fail("read monotonic clock: %s", strerror(errno));
|
||||||
|
if(snprintf(l->root, sizeof l->root, "/tmp/strans-%s.XXXXXX", prefix)
|
||||||
|
>= (int)sizeof l->root){
|
||||||
|
l->root[0] = '\0';
|
||||||
|
return fail("temporary root name is too long");
|
||||||
|
}
|
||||||
|
if(mkdtemp(l->root) == NULL){
|
||||||
|
l->root[0] = '\0';
|
||||||
|
return fail("mkdtemp: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
if(snprintf(l->runtime, sizeof l->runtime, "%s/runtime", l->root)
|
||||||
|
>= (int)sizeof l->runtime ||
|
||||||
|
snprintf(l->config, sizeof l->config, "%s/config", l->root)
|
||||||
|
>= (int)sizeof l->config ||
|
||||||
|
snprintf(l->ibus, sizeof l->ibus, "%s/ibus", l->config)
|
||||||
|
>= (int)sizeof l->ibus ||
|
||||||
|
snprintf(l->bus, sizeof l->bus, "%s/bus", l->ibus)
|
||||||
|
>= (int)sizeof l->bus ||
|
||||||
|
snprintf(l->home, sizeof l->home, "%s/home", l->root)
|
||||||
|
>= (int)sizeof l->home ||
|
||||||
|
snprintf(l->socket, sizeof l->socket, "%s/strans.sock", l->runtime)
|
||||||
|
>= (int)sizeof l->socket)
|
||||||
|
return fail("temporary path is too long");
|
||||||
|
return makedir(l->runtime) && makedir(l->config) && makedir(l->ibus) &&
|
||||||
|
makedir(l->bus) && makedir(l->home);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The daemon owns its endpoints, so leftovers elsewhere are a failure. */
|
||||||
|
int
|
||||||
|
liveclean(Live *l)
|
||||||
|
{
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
ok = 1;
|
||||||
|
if(l->socket[0] != '\0' && unlink(l->socket) < 0 && errno != ENOENT)
|
||||||
|
ok = fail("remove IPC socket %s: %s", l->socket, strerror(errno));
|
||||||
|
if(!cleardir(l->bus)) ok = 0;
|
||||||
|
if(!rmdirknown(l->bus)) ok = 0;
|
||||||
|
if(!rmdirknown(l->ibus)) ok = 0;
|
||||||
|
if(!rmdirknown(l->config)) ok = 0;
|
||||||
|
if(!rmdirknown(l->runtime)) ok = 0;
|
||||||
|
if(!rmdirknown(l->home)) ok = 0;
|
||||||
|
if(!rmdirknown(l->root)) ok = 0;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
daemoninit(Daemon *d, char *name)
|
||||||
|
{
|
||||||
|
memset(d, 0, sizeof *d);
|
||||||
|
d->pid = -1;
|
||||||
|
d->errfd = -1;
|
||||||
|
d->name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
readerrors(Daemon *d)
|
||||||
|
{
|
||||||
|
ssize_t n;
|
||||||
|
|
||||||
|
if(d->errfd < 0)
|
||||||
|
return;
|
||||||
|
while(d->nerr + 1 < sizeof d->err){
|
||||||
|
n = read(d->errfd, d->err + d->nerr, sizeof d->err - d->nerr - 1);
|
||||||
|
if(n > 0){
|
||||||
|
d->nerr += n;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(n < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
d->err[d->nerr] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
showerrors(Daemon *d)
|
||||||
|
{
|
||||||
|
readerrors(d);
|
||||||
|
if(d->nerr != 0)
|
||||||
|
fprintf(stderr, "%s: %s stderr:\n%s", testname, d->name, d->err);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
closeerrors(Daemon *d)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
readerrors(d);
|
||||||
|
fd = d->errfd;
|
||||||
|
d->errfd = -1;
|
||||||
|
if(fd >= 0 && close(fd) < 0)
|
||||||
|
return fail("close %s stderr: %s", d->name, strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Point both standard streams at the capture pipe, even if it is 1 or 2. */
|
||||||
|
static int
|
||||||
|
childfds(int fd)
|
||||||
|
{
|
||||||
|
int flags;
|
||||||
|
|
||||||
|
if(fd > STDERR_FILENO){
|
||||||
|
if(dup2(fd, STDOUT_FILENO) < 0 || dup2(fd, STDERR_FILENO) < 0)
|
||||||
|
return 0;
|
||||||
|
close(fd);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
flags = fcntl(fd, F_GETFD);
|
||||||
|
if(flags < 0 || fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC) < 0)
|
||||||
|
return 0;
|
||||||
|
return dup2(fd, STDOUT_FILENO) >= 0 && dup2(fd, STDERR_FILENO) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A private X server for the tests that need one; its display goes in
|
||||||
|
* Live, so the daemon and the clients started after it share it.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
startxvfb(Live *l, Daemon *d, char *program)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
char fdarg[16], line[32];
|
||||||
|
int errpipe[2], n, pipefd[2];
|
||||||
|
int64_t deadline;
|
||||||
|
pid_t pid;
|
||||||
|
ssize_t nr;
|
||||||
|
|
||||||
|
if(pipe2(pipefd, O_CLOEXEC) < 0)
|
||||||
|
return fail("Xvfb display pipe: %s", strerror(errno));
|
||||||
|
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0){
|
||||||
|
close(pipefd[0]);
|
||||||
|
close(pipefd[1]);
|
||||||
|
return fail("pipe2: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
pid = fork();
|
||||||
|
if(pid < 0){
|
||||||
|
close(pipefd[0]);
|
||||||
|
close(pipefd[1]);
|
||||||
|
close(errpipe[0]);
|
||||||
|
close(errpipe[1]);
|
||||||
|
return fail("fork Xvfb: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
if(pid == 0){
|
||||||
|
close(pipefd[0]);
|
||||||
|
close(errpipe[0]);
|
||||||
|
n = fcntl(pipefd[1], F_GETFD);
|
||||||
|
if(n < 0 || fcntl(pipefd[1], F_SETFD, n & ~FD_CLOEXEC) < 0 ||
|
||||||
|
!childfds(errpipe[1]))
|
||||||
|
_exit(126);
|
||||||
|
snprintf(fdarg, sizeof fdarg, "%d", pipefd[1]);
|
||||||
|
execlp(program, program, "-displayfd", fdarg, "-screen", "0",
|
||||||
|
"1024x768x24", "-nolisten", "tcp", "-noreset", (char*)0);
|
||||||
|
_exit(127);
|
||||||
|
}
|
||||||
|
close(pipefd[1]);
|
||||||
|
close(errpipe[1]);
|
||||||
|
d->pid = pid;
|
||||||
|
d->errfd = errpipe[0];
|
||||||
|
pfd.fd = pipefd[0];
|
||||||
|
pfd.events = POLLIN|POLLHUP;
|
||||||
|
deadline = nowms() + Starttimeout;
|
||||||
|
n = 0;
|
||||||
|
while(n + 1 < (int)sizeof line){
|
||||||
|
pfd.revents = 0;
|
||||||
|
if(poll(&pfd, 1, leftms(deadline)) <= 0)
|
||||||
|
break;
|
||||||
|
nr = read(pipefd[0], line + n, sizeof line - n - 1);
|
||||||
|
if(nr < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if(nr <= 0)
|
||||||
|
break;
|
||||||
|
n += nr;
|
||||||
|
line[n] = '\0';
|
||||||
|
if(strchr(line, '\n') != NULL)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
close(pipefd[0]);
|
||||||
|
if(n == 0 || strchr(line, '\n') == NULL)
|
||||||
|
return fail("Xvfb did not report a private display");
|
||||||
|
line[strcspn(line, "\r\n")] = '\0';
|
||||||
|
if(strspn(line, "0123456789") != strlen(line))
|
||||||
|
return fail("invalid Xvfb display number %s", line);
|
||||||
|
if(snprintf(l->display, sizeof l->display, ":%s", line)
|
||||||
|
>= (int)sizeof l->display)
|
||||||
|
return fail("Xvfb display number is too long");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
startdaemon(Live *l, Daemon *d, char *program, char *arg)
|
||||||
|
{
|
||||||
|
int errpipe[2], fd;
|
||||||
|
long maxfd;
|
||||||
|
pid_t pid;
|
||||||
|
|
||||||
|
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0)
|
||||||
|
return fail("pipe2: %s", strerror(errno));
|
||||||
|
pid = fork();
|
||||||
|
if(pid < 0){
|
||||||
|
close(errpipe[0]);
|
||||||
|
close(errpipe[1]);
|
||||||
|
return fail("fork: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
if(pid == 0){
|
||||||
|
close(errpipe[0]);
|
||||||
|
if(!childfds(errpipe[1]))
|
||||||
|
_exit(126);
|
||||||
|
if(close_range(3, UINT_MAX, 0) < 0){
|
||||||
|
maxfd = sysconf(_SC_OPEN_MAX);
|
||||||
|
if(maxfd < 0)
|
||||||
|
maxfd = 1024;
|
||||||
|
for(fd = 3; fd < maxfd; fd++)
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
if(setenv("XDG_RUNTIME_DIR", l->runtime, 1) < 0 ||
|
||||||
|
setenv("XDG_CONFIG_HOME", l->config, 1) < 0 ||
|
||||||
|
setenv("HOME", l->home, 1) < 0 ||
|
||||||
|
(l->display[0] != '\0' ?
|
||||||
|
setenv("DISPLAY", l->display, 1) :
|
||||||
|
unsetenv("DISPLAY")) < 0 ||
|
||||||
|
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
||||||
|
unsetenv("IBUS_ADDRESS") < 0 ||
|
||||||
|
unsetenv("IBUS_ADDRESS_FILE") < 0){
|
||||||
|
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
|
||||||
|
strerror(errno));
|
||||||
|
_exit(126);
|
||||||
|
}
|
||||||
|
execl(program, program, arg, (char*)0);
|
||||||
|
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
||||||
|
_exit(127);
|
||||||
|
}
|
||||||
|
close(errpipe[1]);
|
||||||
|
d->pid = pid;
|
||||||
|
d->errfd = errpipe[0];
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
daemonalive(Daemon *d)
|
||||||
|
{
|
||||||
|
int n, status;
|
||||||
|
|
||||||
|
if(d->pid <= 0)
|
||||||
|
return fail("%s is no longer running", d->name);
|
||||||
|
do
|
||||||
|
n = waitpid(d->pid, &status, WNOHANG);
|
||||||
|
while(n < 0 && errno == EINTR);
|
||||||
|
if(n == 0)
|
||||||
|
return 1;
|
||||||
|
if(n == d->pid){
|
||||||
|
d->pid = -1;
|
||||||
|
return fail("%s exited unexpectedly with wait status %#x", d->name,
|
||||||
|
status);
|
||||||
|
}
|
||||||
|
if(n < 0){
|
||||||
|
fail("check %s %ld: %s", d->name, (long)d->pid, strerror(errno));
|
||||||
|
if(errno == ECHILD)
|
||||||
|
d->pid = -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return fail("waitpid returned the wrong child for %s", d->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
killdaemon(Daemon *d, int *status)
|
||||||
|
{
|
||||||
|
int n, ok, wait;
|
||||||
|
|
||||||
|
if(d->pid <= 0)
|
||||||
|
return 1;
|
||||||
|
ok = 1;
|
||||||
|
if(kill(d->pid, SIGKILL) < 0 && errno != ESRCH)
|
||||||
|
ok = fail("kill -9 %s %ld: %s", d->name, (long)d->pid,
|
||||||
|
strerror(errno));
|
||||||
|
do
|
||||||
|
n = waitpid(d->pid, &wait, 0);
|
||||||
|
while(n < 0 && errno == EINTR);
|
||||||
|
if(n != d->pid)
|
||||||
|
ok = fail("reap %s %ld after SIGKILL: %s", d->name, (long)d->pid,
|
||||||
|
n < 0 ? strerror(errno) : "wrong child");
|
||||||
|
else if(status != NULL)
|
||||||
|
*status = wait;
|
||||||
|
d->pid = -1;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
stopdaemon(Daemon *d)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
int n, ok, reaped, status;
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
if(d->pid <= 0)
|
||||||
|
return 1;
|
||||||
|
if(!daemonalive(d))
|
||||||
|
return 0;
|
||||||
|
ok = 1;
|
||||||
|
status = 0;
|
||||||
|
reaped = 0;
|
||||||
|
if(kill(d->pid, SIGTERM) < 0 && errno != ESRCH)
|
||||||
|
ok = fail("kill %s %ld: %s", d->name, (long)d->pid, strerror(errno));
|
||||||
|
deadline = nowms() + Stoptimeout;
|
||||||
|
while(d->pid > 0){
|
||||||
|
do
|
||||||
|
n = waitpid(d->pid, &status, WNOHANG);
|
||||||
|
while(n < 0 && errno == EINTR);
|
||||||
|
if(n == d->pid){
|
||||||
|
reaped = 1;
|
||||||
|
d->pid = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(n < 0){
|
||||||
|
fail("waitpid %s %ld: %s", d->name, (long)d->pid,
|
||||||
|
strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
if(errno == ECHILD)
|
||||||
|
d->pid = -1;
|
||||||
|
else if(!killdaemon(d, NULL))
|
||||||
|
ok = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
n = leftms(deadline);
|
||||||
|
if(n == 0){
|
||||||
|
fail("%s %ld did not stop after SIGTERM", d->name, (long)d->pid);
|
||||||
|
ok = 0;
|
||||||
|
if(!killdaemon(d, NULL))
|
||||||
|
ok = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pfd.fd = d->errfd;
|
||||||
|
pfd.events = POLLIN|POLLHUP;
|
||||||
|
pfd.revents = 0;
|
||||||
|
if(poll(&pfd, 1, n) < 0 && errno != EINTR){
|
||||||
|
fail("poll %s %ld: %s", d->name, (long)d->pid, strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
if(!killdaemon(d, NULL))
|
||||||
|
ok = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
readerrors(d);
|
||||||
|
}
|
||||||
|
/* plan9port turns a caught termination note into exit status 1. */
|
||||||
|
if(reaped && !((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) ||
|
||||||
|
(WIFEXITED(status) && WEXITSTATUS(status) <= 1)))
|
||||||
|
ok = fail("%s exited with unexpected wait status %#x", d->name,
|
||||||
|
status);
|
||||||
|
readerrors(d);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
findaddress(Live *l)
|
||||||
|
{
|
||||||
|
DIR *dir;
|
||||||
|
struct dirent *de;
|
||||||
|
int count, ok;
|
||||||
|
|
||||||
|
dir = opendir(l->bus);
|
||||||
|
if(dir == NULL){
|
||||||
|
fail("open IBus directory %s: %s", l->bus, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
count = 0;
|
||||||
|
ok = 1;
|
||||||
|
errno = 0;
|
||||||
|
while((de = readdir(dir)) != NULL){
|
||||||
|
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0 ||
|
||||||
|
strstr(de->d_name, ".tmp.") != NULL)
|
||||||
|
continue;
|
||||||
|
count++;
|
||||||
|
if(snprintf(l->addrfile, sizeof l->addrfile, "%s/%s", l->bus,
|
||||||
|
de->d_name) >= (int)sizeof l->addrfile){
|
||||||
|
ok = fail("IBus address path is too long");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(errno != 0)
|
||||||
|
ok = fail("read IBus directory %s: %s", l->bus, strerror(errno));
|
||||||
|
if(closedir(dir) < 0)
|
||||||
|
ok = fail("close IBus directory %s: %s", l->bus, strerror(errno));
|
||||||
|
if(!ok)
|
||||||
|
return -1;
|
||||||
|
if(count > 1){
|
||||||
|
fail("found %d IBus address files", count);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
readfile(char *path, char *buf, size_t cap, size_t *nread)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
ssize_t n;
|
||||||
|
size_t off;
|
||||||
|
char extra;
|
||||||
|
int fd, ok;
|
||||||
|
|
||||||
|
fd = open(path, O_RDONLY|O_CLOEXEC);
|
||||||
|
if(fd < 0)
|
||||||
|
return fail("open %s: %s", path, strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
if(fstat(fd, &st) < 0){
|
||||||
|
fail("stat open file %s: %s", path, strerror(errno));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if(st.st_size < 0 || (uintmax_t)st.st_size >= cap){
|
||||||
|
fail("file %s is too large", path);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
off = 0;
|
||||||
|
while(off < (size_t)st.st_size){
|
||||||
|
n = read(fd, buf + off, (size_t)st.st_size - off);
|
||||||
|
if(n < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if(n <= 0){
|
||||||
|
fail("read %s: %s", path,
|
||||||
|
n == 0 ? "unexpected EOF" : strerror(errno));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
off += n;
|
||||||
|
}
|
||||||
|
do
|
||||||
|
n = read(fd, &extra, 1);
|
||||||
|
while(n < 0 && errno == EINTR);
|
||||||
|
if(n != 0){
|
||||||
|
fail("file %s changed while being read", path);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
buf[off] = '\0';
|
||||||
|
*nread = off;
|
||||||
|
ok = 1;
|
||||||
|
out:
|
||||||
|
if(close(fd) < 0){
|
||||||
|
fail("close %s: %s", path, strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
parseaddress(char *contents, size_t ncontents, char *address, size_t naddress,
|
||||||
|
pid_t *declared)
|
||||||
|
{
|
||||||
|
char found[512];
|
||||||
|
long pid;
|
||||||
|
int consumed;
|
||||||
|
|
||||||
|
consumed = -1;
|
||||||
|
if(strlen(contents) != ncontents ||
|
||||||
|
sscanf(contents, "IBUS_ADDRESS=%511[^\n]\nIBUS_DAEMON_PID=%ld\n%n",
|
||||||
|
found, &pid, &consumed) != 2 || consumed != (int)ncontents || pid <= 0)
|
||||||
|
return fail("invalid IBus address file contents");
|
||||||
|
if(snprintf(address, naddress, "%s", found) >= (int)naddress)
|
||||||
|
return fail("private IBus address is too long");
|
||||||
|
*declared = (pid_t)pid;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for the protected IPC socket and, when addr is not null, for the
|
||||||
|
* IBus address file the daemon publishes for itself.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
waitready(Live *l, Daemon *d, char *addr, size_t naddr)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
struct stat st;
|
||||||
|
char contents[2048];
|
||||||
|
size_t ncontents;
|
||||||
|
pid_t declared;
|
||||||
|
int count, n, timeout;
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
deadline = nowms() + Starttimeout;
|
||||||
|
for(;;){
|
||||||
|
if(lstat(l->socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
||||||
|
(st.st_mode & 0777) == 0600){
|
||||||
|
if(addr == NULL)
|
||||||
|
return 1;
|
||||||
|
count = findaddress(l);
|
||||||
|
if(count < 0)
|
||||||
|
return 0;
|
||||||
|
if(count == 1 &&
|
||||||
|
readfile(l->addrfile, contents, sizeof contents, &ncontents) &&
|
||||||
|
parseaddress(contents, ncontents, addr, naddr, &declared) &&
|
||||||
|
declared == d->pid)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if(!daemonalive(d))
|
||||||
|
return 0;
|
||||||
|
timeout = leftms(deadline);
|
||||||
|
if(timeout == 0)
|
||||||
|
return fail("timed out waiting for the %s endpoints", d->name);
|
||||||
|
pfd.fd = d->errfd;
|
||||||
|
pfd.events = POLLIN|POLLHUP;
|
||||||
|
pfd.revents = 0;
|
||||||
|
n = poll(&pfd, 1, timeout > 20 ? 20 : timeout);
|
||||||
|
if(n < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if(n < 0)
|
||||||
|
return fail("poll %s readiness: %s", d->name, strerror(errno));
|
||||||
|
if(pfd.revents & (POLLERR|POLLNVAL))
|
||||||
|
return fail("%s stderr pipe became unusable: %#x", d->name,
|
||||||
|
pfd.revents);
|
||||||
|
if(pfd.revents & (POLLIN|POLLHUP))
|
||||||
|
readerrors(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
connectsocket(char *path, int64_t deadline)
|
||||||
|
{
|
||||||
|
struct sockaddr_un addr;
|
||||||
|
struct pollfd pfd;
|
||||||
|
socklen_t nerr;
|
||||||
|
int err, fd, flags, n;
|
||||||
|
|
||||||
|
memset(&addr, 0, sizeof addr);
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
if(snprintf(addr.sun_path, sizeof addr.sun_path, "%s", path)
|
||||||
|
>= (int)sizeof addr.sun_path){
|
||||||
|
errno = ENAMETOOLONG;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
|
||||||
|
if(fd < 0)
|
||||||
|
return -1;
|
||||||
|
if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0 &&
|
||||||
|
errno != EINPROGRESS){
|
||||||
|
err = errno;
|
||||||
|
close(fd);
|
||||||
|
errno = err;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
for(;;){
|
||||||
|
n = leftms(deadline);
|
||||||
|
if(n == 0){
|
||||||
|
close(fd);
|
||||||
|
errno = ETIMEDOUT;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pfd.fd = fd;
|
||||||
|
pfd.events = POLLOUT;
|
||||||
|
pfd.revents = 0;
|
||||||
|
n = poll(&pfd, 1, n);
|
||||||
|
if(n < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if(n <= 0){
|
||||||
|
err = n == 0 ? ETIMEDOUT : errno;
|
||||||
|
close(fd);
|
||||||
|
errno = err;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
err = 0;
|
||||||
|
nerr = sizeof err;
|
||||||
|
if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &nerr) < 0 || err != 0){
|
||||||
|
if(err == 0)
|
||||||
|
err = errno;
|
||||||
|
close(fd);
|
||||||
|
errno = err;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
flags = fcntl(fd, F_GETFL);
|
||||||
|
if(flags < 0 || fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0){
|
||||||
|
err = errno;
|
||||||
|
close(fd);
|
||||||
|
errno = err;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1 is complete, 0 is peer closure, and -1 is a timeout or I/O error. */
|
||||||
|
int
|
||||||
|
readuntil(int fd, void *buf, size_t n, int64_t deadline)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
unsigned char *p;
|
||||||
|
ssize_t r;
|
||||||
|
int timeout;
|
||||||
|
|
||||||
|
p = buf;
|
||||||
|
while(n > 0){
|
||||||
|
timeout = leftms(deadline);
|
||||||
|
if(timeout == 0){
|
||||||
|
errno = ETIMEDOUT;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pfd.fd = fd;
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
pfd.revents = 0;
|
||||||
|
r = poll(&pfd, 1, timeout);
|
||||||
|
if(r < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if(r <= 0){
|
||||||
|
if(r == 0)
|
||||||
|
errno = ETIMEDOUT;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
r = recv(fd, p, n, 0);
|
||||||
|
if(r < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if(r < 0)
|
||||||
|
return -1;
|
||||||
|
if(r == 0)
|
||||||
|
return 0;
|
||||||
|
p += r;
|
||||||
|
n -= r;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ipcrequest(int fd, uint32_t mod, uint32_t key, unsigned char *want,
|
||||||
|
size_t nwant, char *where)
|
||||||
|
{
|
||||||
|
unsigned char req[Ipcreqsz], got[16];
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
if(nwant > sizeof got)
|
||||||
|
return fail("%s expected IPC response is too large", where);
|
||||||
|
ipcpackreq(req, 1, mod, key);
|
||||||
|
if(ipcsend(fd, req, sizeof req) < 0)
|
||||||
|
return fail("send %s IPC request: %s", where, strerror(errno));
|
||||||
|
rv = readuntil(fd, got, nwant, nowms() + Calltimeout);
|
||||||
|
if(rv != 1)
|
||||||
|
return fail("read %s IPC response: %s", where,
|
||||||
|
rv == 0 ? "peer closed" : strerror(errno));
|
||||||
|
if(memcmp(got, want, nwant) != 0)
|
||||||
|
return fail("%s IPC response did not match", where);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ipcprobe(int fd, char *where)
|
||||||
|
{
|
||||||
|
/* eaten, take-back, no commit, no preedit */
|
||||||
|
unsigned char empty[] = {0, 0, 0, 0, 0, 0};
|
||||||
|
|
||||||
|
return ipcrequest(fd, 0, 0, empty, sizeof empty, where);
|
||||||
|
}
|
||||||
75
tests/live.h
Normal file
75
tests/live.h
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Harness shared by the live tests: a strans daemon started in a private
|
||||||
|
* XDG_RUNTIME_DIR with its output captured, the endpoints it publishes,
|
||||||
|
* and the IPC probe used to prove the daemon answers.
|
||||||
|
* Set testname before anything else; fail prefixes it to every message.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct Daemon Daemon;
|
||||||
|
typedef struct Live Live;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Calltimeout = 4000,
|
||||||
|
Starttimeout = 8000,
|
||||||
|
Stoptimeout = 3000,
|
||||||
|
Errmax = 8192,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Daemon
|
||||||
|
{
|
||||||
|
pid_t pid;
|
||||||
|
int errfd;
|
||||||
|
char *name;
|
||||||
|
char err[Errmax];
|
||||||
|
size_t nerr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Live
|
||||||
|
{
|
||||||
|
char root[256];
|
||||||
|
char runtime[320];
|
||||||
|
char config[320];
|
||||||
|
char ibus[384];
|
||||||
|
char bus[448];
|
||||||
|
char home[320];
|
||||||
|
char socket[384];
|
||||||
|
char addrfile[512];
|
||||||
|
char display[32];
|
||||||
|
};
|
||||||
|
|
||||||
|
extern char *testname;
|
||||||
|
|
||||||
|
int fail(char*, ...);
|
||||||
|
int64_t nowms(void);
|
||||||
|
int leftms(int64_t);
|
||||||
|
void pausems(int);
|
||||||
|
int makedir(char*);
|
||||||
|
int cleardir(char*);
|
||||||
|
|
||||||
|
int livesetup(Live*, char*);
|
||||||
|
int liveclean(Live*);
|
||||||
|
|
||||||
|
void daemoninit(Daemon*, char*);
|
||||||
|
int startxvfb(Live*, Daemon*, char*);
|
||||||
|
int startdaemon(Live*, Daemon*, char*, char*);
|
||||||
|
int waitready(Live*, Daemon*, char*, size_t);
|
||||||
|
int daemonalive(Daemon*);
|
||||||
|
int stopdaemon(Daemon*);
|
||||||
|
int killdaemon(Daemon*, int*);
|
||||||
|
void readerrors(Daemon*);
|
||||||
|
void showerrors(Daemon*);
|
||||||
|
int closeerrors(Daemon*);
|
||||||
|
|
||||||
|
int findaddress(Live*);
|
||||||
|
int readfile(char*, char*, size_t, size_t*);
|
||||||
|
int parseaddress(char*, size_t, char*, size_t, pid_t*);
|
||||||
|
|
||||||
|
int connectsocket(char*, int64_t);
|
||||||
|
int readuntil(int, void*, size_t, int64_t);
|
||||||
|
int ipcrequest(int, uint32_t, uint32_t, unsigned char*, size_t, char*);
|
||||||
|
int ipcprobe(int, char*);
|
||||||
168
tests/livebus.c
Normal file
168
tests/livebus.c
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <dbus/dbus.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "live.h"
|
||||||
|
#include "livebus.h"
|
||||||
|
|
||||||
|
DBusConnection*
|
||||||
|
openbus(char *address, char *where)
|
||||||
|
{
|
||||||
|
DBusConnection *conn;
|
||||||
|
DBusError err;
|
||||||
|
|
||||||
|
dbus_error_init(&err);
|
||||||
|
conn = dbus_connection_open_private(address, &err);
|
||||||
|
if(conn == NULL){
|
||||||
|
fail("open %s private IBus connection: %s", where,
|
||||||
|
err.message != NULL ? err.message : "D-Bus error");
|
||||||
|
dbus_error_free(&err);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
dbus_error_free(&err);
|
||||||
|
dbus_connection_set_exit_on_disconnect(conn, FALSE);
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
closebus(DBusConnection **conn)
|
||||||
|
{
|
||||||
|
if(*conn == NULL)
|
||||||
|
return;
|
||||||
|
dbus_connection_close(*conn);
|
||||||
|
dbus_connection_unref(*conn);
|
||||||
|
*conn = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBusMessage*
|
||||||
|
contextcall(char *path, char *member)
|
||||||
|
{
|
||||||
|
return dbus_message_new_method_call("org.freedesktop.IBus", path,
|
||||||
|
"org.freedesktop.IBus.InputContext", member);
|
||||||
|
}
|
||||||
|
|
||||||
|
DBusMessage*
|
||||||
|
sendcall(DBusConnection *conn, DBusMessage *m, char *where)
|
||||||
|
{
|
||||||
|
DBusMessage *reply;
|
||||||
|
DBusPendingCall *pending;
|
||||||
|
int timeout;
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
if(m == NULL){
|
||||||
|
fail("allocate %s call", where);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
pending = NULL;
|
||||||
|
if(!dbus_connection_send_with_reply(conn, m, &pending, Calltimeout) ||
|
||||||
|
pending == NULL){
|
||||||
|
dbus_message_unref(m);
|
||||||
|
fail("queue %s call", where);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
dbus_message_unref(m);
|
||||||
|
deadline = nowms() + Calltimeout;
|
||||||
|
while(!dbus_pending_call_get_completed(pending)){
|
||||||
|
timeout = leftms(deadline);
|
||||||
|
if(timeout == 0){
|
||||||
|
fail("timed out waiting for %s", where);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if(!dbus_connection_read_write_dispatch(conn, timeout)){
|
||||||
|
fail("connection closed waiting for %s", where);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reply = dbus_pending_call_steal_reply(pending);
|
||||||
|
dbus_pending_call_unref(pending);
|
||||||
|
if(reply == NULL)
|
||||||
|
fail("%s completed without a reply", where);
|
||||||
|
return reply;
|
||||||
|
out:
|
||||||
|
dbus_pending_call_cancel(pending);
|
||||||
|
dbus_pending_call_unref(pending);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBusMessage*
|
||||||
|
callret(DBusConnection *conn, DBusMessage *m, char *where)
|
||||||
|
{
|
||||||
|
DBusMessage *reply;
|
||||||
|
|
||||||
|
reply = sendcall(conn, m, where);
|
||||||
|
if(reply == NULL)
|
||||||
|
return NULL;
|
||||||
|
if(dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_METHOD_RETURN){
|
||||||
|
fail("%s returned D-Bus message type %d", where,
|
||||||
|
dbus_message_get_type(reply));
|
||||||
|
dbus_message_unref(reply);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
hello(DBusConnection *conn, char *name, size_t nname, char *where)
|
||||||
|
{
|
||||||
|
DBusMessage *m, *reply;
|
||||||
|
DBusError err;
|
||||||
|
const char *s;
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
m = dbus_message_new_method_call("org.freedesktop.DBus",
|
||||||
|
"/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello");
|
||||||
|
reply = callret(conn, m, where);
|
||||||
|
if(reply == NULL)
|
||||||
|
return 0;
|
||||||
|
dbus_error_init(&err);
|
||||||
|
ok = dbus_message_has_signature(reply, "s") &&
|
||||||
|
dbus_message_get_args(reply, &err, DBUS_TYPE_STRING, &s,
|
||||||
|
DBUS_TYPE_INVALID);
|
||||||
|
if(!ok)
|
||||||
|
fail("%s returned an invalid Hello reply", where);
|
||||||
|
else if(s[0] != ':')
|
||||||
|
ok = fail("%s returned invalid Hello name %s", where, s);
|
||||||
|
else if(name != NULL && snprintf(name, nname, "%s", s) >= (int)nname)
|
||||||
|
ok = fail("%s Hello name is too long", where);
|
||||||
|
dbus_error_free(&err);
|
||||||
|
dbus_message_unref(reply);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
createcontext(DBusConnection *conn, char *path, size_t npath, char *where)
|
||||||
|
{
|
||||||
|
DBusMessage *m, *reply;
|
||||||
|
DBusError err;
|
||||||
|
const char *client, *p;
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
client = testname;
|
||||||
|
m = dbus_message_new_method_call("org.freedesktop.IBus",
|
||||||
|
"/org/freedesktop/IBus", "org.freedesktop.IBus",
|
||||||
|
"CreateInputContext");
|
||||||
|
if(m == NULL || !dbus_message_append_args(m, DBUS_TYPE_STRING, &client,
|
||||||
|
DBUS_TYPE_INVALID)){
|
||||||
|
if(m != NULL)
|
||||||
|
dbus_message_unref(m);
|
||||||
|
return fail("build %s CreateInputContext call", where);
|
||||||
|
}
|
||||||
|
reply = callret(conn, m, where);
|
||||||
|
if(reply == NULL)
|
||||||
|
return 0;
|
||||||
|
dbus_error_init(&err);
|
||||||
|
ok = dbus_message_has_signature(reply, "o") &&
|
||||||
|
dbus_message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, &p,
|
||||||
|
DBUS_TYPE_INVALID);
|
||||||
|
if(!ok)
|
||||||
|
fail("%s returned an invalid context reply", where);
|
||||||
|
else if(p[0] != '/')
|
||||||
|
ok = fail("%s returned invalid context path %s", where, p);
|
||||||
|
else if(path != NULL && snprintf(path, npath, "%s", p) >= (int)npath)
|
||||||
|
ok = fail("%s context path is too long", where);
|
||||||
|
dbus_error_free(&err);
|
||||||
|
dbus_message_unref(reply);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
17
tests/livebus.h
Normal file
17
tests/livebus.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include <dbus/dbus.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* D-Bus half of the live harness: private connections to the IBus address
|
||||||
|
* a strans daemon publishes, and the calls every IBus client makes first.
|
||||||
|
* sendcall returns any reply, callret only a method return.
|
||||||
|
*/
|
||||||
|
|
||||||
|
DBusConnection* openbus(char*, char*);
|
||||||
|
void closebus(DBusConnection**);
|
||||||
|
DBusMessage* contextcall(char*, char*);
|
||||||
|
DBusMessage* sendcall(DBusConnection*, DBusMessage*, char*);
|
||||||
|
DBusMessage* callret(DBusConnection*, DBusMessage*, char*);
|
||||||
|
int hello(DBusConnection*, char*, size_t, char*);
|
||||||
|
int createcontext(DBusConnection*, char*, size_t, char*);
|
||||||
@@ -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,31 @@ class MkemojiTest(unittest.TestCase):
|
|||||||
result = generate()
|
result = generate()
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
data = table(result.stdout)
|
data = table(result.stdout)
|
||||||
self.assertEqual(data["^"].split()[:9], list("¹²³⁴⁵⁶⁷⁸⁹"))
|
# The engine walks a prefix's children in rune order, so only the
|
||||||
self.assertEqual(data["_"].split()[:9], list("₁₂₃₄₅₆₇₈₉"))
|
# prefix's own row decides which candidate a digit picks.
|
||||||
|
self.assertEqual(data["^"].split(), list("¹²³⁴⁵⁶⁷⁸⁹"))
|
||||||
|
self.assertEqual(data["_"].split(), list("₁₂₃₄₅₆₇₈₉"))
|
||||||
self.assertEqual(data["<"].split()[2], "♥")
|
self.assertEqual(data["<"].split()[2], "♥")
|
||||||
self.assertEqual(data["^1"], "¹")
|
self.assertEqual(data["^1"], "¹")
|
||||||
self.assertEqual(data["_2"], "₂")
|
self.assertEqual(data["_2"], "₂")
|
||||||
self.assertEqual(data["<3"], "♥")
|
self.assertEqual(data["<3"].split()[0], "♥")
|
||||||
|
self.assertIn("😀", data["smile"].split())
|
||||||
|
self.assertIn("😀", data["웃음"].split())
|
||||||
|
|
||||||
def test_fold_normalize_and_exact_first(self):
|
def test_fold_normalize_and_source_order(self):
|
||||||
source = self.source(
|
source = self.source(
|
||||||
"β\tALPHABET\n"
|
"β\tALPHABET\talpha\n"
|
||||||
"α\talpha\n"
|
"α\talpha\n"
|
||||||
"e\u0301\tE\u0301\n"
|
"é\tÉ\n"
|
||||||
"#\thash\n"
|
"#\thash\n"
|
||||||
)
|
)
|
||||||
first = generate(source)
|
first = generate(source)
|
||||||
second = generate(source)
|
second = generate(source, source)
|
||||||
self.assertEqual(first.returncode, 0, first.stderr)
|
self.assertEqual(first.returncode, 0, first.stderr)
|
||||||
self.assertEqual(first.stdout, second.stdout)
|
self.assertEqual(first.stdout, second.stdout)
|
||||||
data = table(first.stdout)
|
data = table(first.stdout)
|
||||||
self.assertEqual(data["alpha"].split(), ["α", "β"])
|
self.assertEqual(list(data), ["alphabet", "alpha", "é", "hash"])
|
||||||
self.assertEqual(data["al"].split(), ["β", "α"])
|
self.assertEqual(data["alpha"].split(), ["β", "α"])
|
||||||
self.assertEqual(data["é"], "é")
|
self.assertEqual(data["é"], "é")
|
||||||
self.assertEqual(data["hash"], "#")
|
self.assertEqual(data["hash"], "#")
|
||||||
|
|
||||||
|
|||||||
@@ -42,25 +42,45 @@ 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_imports_symbol_rows_under_a_jamo(self):
|
||||||
|
result = run(
|
||||||
|
IMPORT,
|
||||||
|
self.source(
|
||||||
|
"\u3141:\u203b:reference mark\n"
|
||||||
|
"\u3134:\u300c:bracket\n"
|
||||||
|
"\u3131:\u3000:ideographic space\n"
|
||||||
|
"\u3131:\u00ad:soft hyphen\n"
|
||||||
|
"\u3131:\u52a0:Hanja under a jamo\n"
|
||||||
|
"\u3141:\u203b\u203b:two runes\n"
|
||||||
|
"\u3141\u3134:\u203b:two jamo\n"
|
||||||
|
"\u314f:\u203b:a vowel\n"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertEqual(result.stdout.splitlines(),
|
||||||
|
["\u203b\t\u3141", "\u300c\t\u3134"])
|
||||||
|
|
||||||
def test_import_rejects_malformed_and_duplicate_rows(self):
|
def test_import_rejects_malformed_and_duplicate_rows(self):
|
||||||
bad = [
|
bad = [
|
||||||
@@ -91,6 +111,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,16 +124,36 @@ class MkhanjaTest(unittest.TestCase):
|
|||||||
"한\t漢 韓",
|
"한\t漢 韓",
|
||||||
"가\t㐀 家",
|
"가\t㐀 家",
|
||||||
"금\t金",
|
"금\t金",
|
||||||
|
"한자\t漢字",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_groups_symbols_under_their_jamo(self):
|
||||||
|
result = run(
|
||||||
|
GENERATE,
|
||||||
|
self.source(
|
||||||
|
"※\tㅁ\n"
|
||||||
|
"○\tㅁ\n"
|
||||||
|
"「\tㄴ\n"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertEqual(result.stdout.splitlines(),
|
||||||
|
["ㅁ\t※ ○", "ㄴ\t「"])
|
||||||
|
|
||||||
def test_generator_rejects_bad_rows(self):
|
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",
|
||||||
|
"加\tㅁ\n",
|
||||||
|
"※\t한\n",
|
||||||
|
"※\tㅁㄴ\n",
|
||||||
|
"※※\tㅁ\n",
|
||||||
|
"\u3000\tㄱ\n",
|
||||||
|
"\u00ad\tㄱ\n",
|
||||||
]
|
]
|
||||||
for text in bad:
|
for text in bad:
|
||||||
with self.subTest(text=repr(text)):
|
with self.subTest(text=repr(text)):
|
||||||
@@ -127,13 +168,22 @@ class MkhanjaTest(unittest.TestCase):
|
|||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
self.assertIn("한\t", result.stdout)
|
self.assertIn("한\t", result.stdout)
|
||||||
|
|
||||||
|
def test_generator_reads_both_sources(self):
|
||||||
|
result = run(GENERATE)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
lines = result.stdout.splitlines()
|
||||||
|
self.assertEqual(lines.count(";; All rights reserved."), 2)
|
||||||
|
rows = dict(line.split("\t") for line in lines if "\t" in line)
|
||||||
|
self.assertIn("漢", rows["한"].split())
|
||||||
|
self.assertIn("※", rows["ㅁ"].split())
|
||||||
|
|
||||||
def test_generator_keeps_runtime_candidate_limit(self):
|
def test_generator_keeps_runtime_candidate_limit(self):
|
||||||
rows = "".join(f"{chr(0x4E00 + n)}\t한\n" for n in range(33))
|
rows = "".join(f"{chr(0x4E00 + n)}\t한\n" for n in range(129))
|
||||||
result = run(GENERATE, self.source(rows))
|
result = run(GENERATE, self.source(rows))
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
candidates = result.stdout.rstrip().split("\t", 1)[1].split()
|
candidates = result.stdout.rstrip().split("\t", 1)[1].split()
|
||||||
self.assertEqual(len(candidates), 32)
|
self.assertEqual(len(candidates), 128)
|
||||||
self.assertEqual(candidates[-1], chr(0x4E00 + 31))
|
self.assertEqual(candidates[-1], chr(0x4E00 + 127))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,34 +1,323 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
#include <limits.h>
|
|
||||||
|
/* A popup with every section, tall enough for the tests. */
|
||||||
|
#define Imgh (2*PopupPad + (Maxdisp + 2)*Fontsz + PopupSep)
|
||||||
|
|
||||||
|
enum {
|
||||||
|
Rgbmask = 0xffffff,
|
||||||
|
};
|
||||||
|
|
||||||
|
static u32int
|
||||||
|
pixel(u32int *img, Popup *p, int x, int y)
|
||||||
|
{
|
||||||
|
return img[y*p->w + x] & Rgbmask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rectcolor(u32int *img, Popup *p, int x0, int y0, int x1, int y1,
|
||||||
|
u32int color)
|
||||||
|
{
|
||||||
|
int x, y;
|
||||||
|
|
||||||
|
color &= Rgbmask;
|
||||||
|
for(y = y0; y < y1; y++)
|
||||||
|
for(x = x0; x < x1; x++)
|
||||||
|
if(pixel(img, p, x, y) != color)
|
||||||
|
return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
checkpadding(struct ct *t, u32int *img, Popup *p)
|
||||||
|
{
|
||||||
|
CT_CHECK(t, rectcolor(img, p, 0, 0, p->w, PopupPad, Colbg));
|
||||||
|
CT_CHECK(t, rectcolor(img, p, 0, p->h - PopupPad,
|
||||||
|
p->w, p->h, Colbg));
|
||||||
|
CT_CHECK(t, rectcolor(img, p, 0, 0, PopupPad, p->h, Colbg));
|
||||||
|
CT_CHECK(t, rectcolor(img, p, p->w - PopupPad, 0,
|
||||||
|
p->w, p->h, Colbg));
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
popup_layout(struct ct *t)
|
popup_layout(struct ct *t)
|
||||||
{
|
{
|
||||||
Rune heart[] = { 0x2764, 0xfe0f };
|
static const struct {
|
||||||
|
int first;
|
||||||
|
int shown;
|
||||||
|
int total;
|
||||||
|
char *want;
|
||||||
|
} markers[] = {
|
||||||
|
{ 0, 0, 0, "" },
|
||||||
|
{ 0, 9, 9, "" },
|
||||||
|
{ 0, 3, 9, "1-3/9" },
|
||||||
|
{ 8, 1, 9, "9-9/9" },
|
||||||
|
{ 0, 9, 10, "1-9/10" },
|
||||||
|
{ 9, 1, 10, "10-10/10" },
|
||||||
|
{ 0, 9, 18, "1-9/18" },
|
||||||
|
{ 9, 9, 18, "10-18/18" },
|
||||||
|
{ 9, 9, 32, "10-18/32" },
|
||||||
|
{ 18, 9, 32, "19-27/32" },
|
||||||
|
{ 27, 5, 32, "28-32/32" },
|
||||||
|
};
|
||||||
|
char buf[32];
|
||||||
|
Area area, mon[2], out, work;
|
||||||
Caret caret;
|
Caret caret;
|
||||||
int x, y;
|
Drawcmd dc;
|
||||||
|
Popup p;
|
||||||
|
Str longrow;
|
||||||
|
u32int c, *img;
|
||||||
|
int i, ink, markink, n, numink, selbg, textink, x, y;
|
||||||
|
|
||||||
|
for(i = 0; i < nelem(markers); i++){
|
||||||
|
n = pagemarker(buf, sizeof buf, markers[i].first,
|
||||||
|
markers[i].shown, markers[i].total);
|
||||||
|
CT_EQ_INT(t, strlen(markers[i].want), n);
|
||||||
|
CT_CHECK(t, strcmp(markers[i].want, buf) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monitor selection retains origins and intersects the EWMH work area. */
|
||||||
|
mon[0] = (Area){0, 0, 1920, 1080};
|
||||||
|
mon[1] = (Area){1920, 200, 1280, 1024};
|
||||||
|
work = (Area){0, 30, 3200, 1070};
|
||||||
|
popuparea(mon, 2, &work, 2500, 500, &out);
|
||||||
|
CT_EQ_INT(t, 1920, out.x);
|
||||||
|
CT_EQ_INT(t, 200, out.y);
|
||||||
|
CT_EQ_INT(t, 1280, out.w);
|
||||||
|
CT_EQ_INT(t, 900, out.h);
|
||||||
|
work = (Area){60, 0, 3140, 1224};
|
||||||
|
popuparea(mon, 2, &work, 100, 100, &out);
|
||||||
|
CT_EQ_INT(t, 60, out.x);
|
||||||
|
CT_EQ_INT(t, 0, out.y);
|
||||||
|
CT_EQ_INT(t, 1860, out.w);
|
||||||
|
CT_EQ_INT(t, 1080, out.h);
|
||||||
|
work = (Area){5000, 0, 100, 100};
|
||||||
|
popuparea(mon, 2, &work, 2500, 500, &out);
|
||||||
|
CT_EQ_INT(t, mon[1].x, out.x);
|
||||||
|
CT_EQ_INT(t, mon[1].y, out.y);
|
||||||
|
CT_EQ_INT(t, mon[1].w, out.w);
|
||||||
|
CT_EQ_INT(t, mon[1].h, out.h);
|
||||||
|
mon[0] = (Area){0, 0, 100, 100};
|
||||||
|
mon[1] = (Area){200, 0, 100, 100};
|
||||||
|
popuparea(mon, 2, nil, 150, 50, &out);
|
||||||
|
CT_EQ_INT(t, 0, out.x);
|
||||||
|
|
||||||
CT_EQ_INT(t, 1, popupcells(heart, nelem(heart)));
|
|
||||||
memset(&caret, 0, sizeof caret);
|
memset(&caret, 0, sizeof caret);
|
||||||
popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y);
|
area = (Area){1920, 30, 1280, 900};
|
||||||
CT_EQ_INT(t, 50, x);
|
popupposition(&caret, 2000, 50, &area, 100, 60, &x, &y);
|
||||||
|
CT_EQ_INT(t, 2010, x);
|
||||||
CT_EQ_INT(t, 60, y);
|
CT_EQ_INT(t, 60, y);
|
||||||
|
popupposition(&caret, 2000, 900, &area, 100, 60, &x, &y);
|
||||||
|
CT_EQ_INT(t, 830, y); /* above the pointer, not over it */
|
||||||
caret.valid = 1;
|
caret.valid = 1;
|
||||||
caret.x = 70;
|
caret.x = 2100;
|
||||||
caret.y = 80;
|
caret.y = 80;
|
||||||
caret.h = 20;
|
caret.h = 20;
|
||||||
popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y);
|
popupposition(&caret, 0, 0, &area, 100, 60, &x, &y);
|
||||||
CT_EQ_INT(t, 70, x);
|
CT_EQ_INT(t, 2100, x);
|
||||||
CT_EQ_INT(t, 100, y);
|
CT_EQ_INT(t, 100, y);
|
||||||
caret.x = 790;
|
caret.x = 3150;
|
||||||
caret.y = 590;
|
caret.y = 850;
|
||||||
popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y);
|
popupposition(&caret, 0, 0, &area, 200, 100, &x, &y);
|
||||||
CT_EQ_INT(t, 700, x);
|
CT_EQ_INT(t, 3000, x);
|
||||||
CT_EQ_INT(t, 540, y);
|
CT_EQ_INT(t, 750, y);
|
||||||
caret.x = INT_MIN;
|
|
||||||
caret.y = INT_MAX;
|
/* A popup no larger than a small work area stays inside its origin. */
|
||||||
caret.h = INT_MAX;
|
area = (Area){100, 200, 80, 80};
|
||||||
popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y);
|
caret.x = 120;
|
||||||
CT_EQ_INT(t, 0, x);
|
caret.y = 230;
|
||||||
CT_EQ_INT(t, 540, y);
|
caret.h = 20;
|
||||||
|
popupposition(&caret, 0, 0, &area, 80, 80, &x, &y);
|
||||||
|
CT_EQ_INT(t, 100, x);
|
||||||
|
CT_EQ_INT(t, 200, y);
|
||||||
|
|
||||||
|
textinit();
|
||||||
|
|
||||||
|
/* Empty sections reserve nothing. */
|
||||||
|
memset(&dc, 0, sizeof dc);
|
||||||
|
dc.sel = -1;
|
||||||
|
popuplayout(&dc, PopupBasew, Imgh, &p);
|
||||||
|
CT_EQ_INT(t, 0, p.w);
|
||||||
|
CT_EQ_INT(t, 0, p.h);
|
||||||
|
CT_EQ_INT(t, -1, p.prey);
|
||||||
|
CT_EQ_INT(t, -1, p.rowsy);
|
||||||
|
CT_EQ_INT(t, -1, p.marky);
|
||||||
|
|
||||||
|
/* Preedit only: as wide as its text. */
|
||||||
|
dc.pre = mkstr("preedit");
|
||||||
|
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
|
||||||
|
CT_EQ_INT(t, textwidth(&dc.pre) + 2*PopupPad, p.w);
|
||||||
|
CT_EQ_INT(t, PopupPad, p.prey);
|
||||||
|
CT_EQ_INT(t, -1, p.sepy);
|
||||||
|
CT_EQ_INT(t, -1, p.rowsy);
|
||||||
|
CT_EQ_INT(t, -1, p.marky);
|
||||||
|
CT_EQ_INT(t, 2*PopupPad + Fontsz, p.h);
|
||||||
|
|
||||||
|
/* Candidates only. */
|
||||||
|
memset(&dc, 0, sizeof dc);
|
||||||
|
dc.sel = -1;
|
||||||
|
dc.nkouho = 2;
|
||||||
|
dc.kouho[0] = mkstr("short");
|
||||||
|
dc.kouho[1] = mkstr("candidate");
|
||||||
|
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
|
||||||
|
CT_EQ_INT(t, PopupBasew, p.w);
|
||||||
|
CT_EQ_INT(t, -1, p.prey);
|
||||||
|
CT_EQ_INT(t, -1, p.sepy);
|
||||||
|
CT_EQ_INT(t, PopupPad, p.rowsy);
|
||||||
|
CT_EQ_INT(t, -1, p.marky);
|
||||||
|
CT_EQ_INT(t, 2*PopupPad + 2*Fontsz, p.h);
|
||||||
|
CT_EQ_INT(t, PopupTextw, p.textw);
|
||||||
|
CT_EQ_INT(t, p.w - PopupPad, PopupPad + PopupNumw + p.textw);
|
||||||
|
|
||||||
|
/* Preedit and candidates meet at one separator, with no empty rows. */
|
||||||
|
dc.pre = mkstr("preedit");
|
||||||
|
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
|
||||||
|
CT_EQ_INT(t, PopupBasew, p.w);
|
||||||
|
CT_EQ_INT(t, PopupPad, p.prey);
|
||||||
|
CT_EQ_INT(t, p.prey + Fontsz, p.sepy);
|
||||||
|
CT_EQ_INT(t, p.sepy + PopupSep, p.rowsy);
|
||||||
|
CT_EQ_INT(t, -1, p.marky);
|
||||||
|
CT_EQ_INT(t, p.rowsy + 2*Fontsz + PopupPad, p.h);
|
||||||
|
CT_EQ_INT(t, 2*PopupPad + 3*Fontsz + PopupSep, p.h);
|
||||||
|
|
||||||
|
/* Long preedit expands to the monitor cap just like a candidate. */
|
||||||
|
sclear(&longrow);
|
||||||
|
for(i = 0; i < Maxrunes; i++)
|
||||||
|
sputr(&longrow, 'W');
|
||||||
|
memset(&dc, 0, sizeof dc);
|
||||||
|
dc.pre = longrow;
|
||||||
|
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
|
||||||
|
CT_EQ_INT(t, 2*PopupBasew, p.w);
|
||||||
|
|
||||||
|
/* The largest panel includes all sections and fits its backing image. */
|
||||||
|
memset(&dc, 0, sizeof dc);
|
||||||
|
dc.pre = mkstr("preedit");
|
||||||
|
dc.nkouho = Maxdisp;
|
||||||
|
dc.sel = 4;
|
||||||
|
dc.first = 0;
|
||||||
|
dc.total = Maxdisp + 1;
|
||||||
|
for(i = 0; i < Maxdisp; i++)
|
||||||
|
dc.kouho[i] = mkstr(i == dc.sel ? "M" : "candidate");
|
||||||
|
dc.kouho[0] = longrow;
|
||||||
|
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
|
||||||
|
CT_EQ_INT(t, Maxdisp, p.n);
|
||||||
|
CT_EQ_INT(t, PopupPad, p.prey);
|
||||||
|
CT_EQ_INT(t, p.prey + Fontsz, p.sepy);
|
||||||
|
CT_EQ_INT(t, p.sepy + PopupSep, p.rowsy);
|
||||||
|
CT_EQ_INT(t, p.rowsy + Maxdisp*Fontsz, p.marky);
|
||||||
|
CT_EQ_INT(t, p.marky + Fontsz + PopupPad, p.h);
|
||||||
|
CT_EQ_INT(t, Imgh, p.h);
|
||||||
|
CT_EQ_INT(t, 2*PopupBasew, p.w);
|
||||||
|
CT_CHECK(t, p.h <= Imgh);
|
||||||
|
CT_EQ_INT(t, p.w - PopupPad, p.markx + p.markw);
|
||||||
|
CT_EQ_INT(t, p.w - 2*PopupPad, p.selw);
|
||||||
|
CT_EQ_INT(t, p.rowsy + dc.sel*Fontsz, p.sely);
|
||||||
|
CT_EQ_INT(t, p.w - PopupPad, PopupPad + PopupNumw + p.textw);
|
||||||
|
|
||||||
|
img = emalloc(2*PopupBasew*Imgh*sizeof img[0]);
|
||||||
|
popupdraw(img, &dc, &p);
|
||||||
|
checkpadding(t, img, &p);
|
||||||
|
for(x = 0; x < p.w; x++)
|
||||||
|
CT_EQ_UINT(t,
|
||||||
|
x >= PopupPad && x < p.w - PopupPad ? Colsep : Colbg,
|
||||||
|
pixel(img, &p, x, p.sepy));
|
||||||
|
|
||||||
|
/* Selection paint and both foreground columns stay inside the row. */
|
||||||
|
selbg = numink = textink = 0;
|
||||||
|
for(y = p.sely; y < p.sely + Fontsz; y++){
|
||||||
|
for(x = 0; x < p.w; x++){
|
||||||
|
c = pixel(img, &p, x, y);
|
||||||
|
if(x < PopupPad || x >= PopupPad + p.selw){
|
||||||
|
CT_EQ_UINT(t, Colbg, c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(c == (Colsel & Rgbmask)){
|
||||||
|
selbg++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CT_CHECK(t,
|
||||||
|
(x >= PopupPad && x < PopupPad + PopupNumw) ||
|
||||||
|
(x >= (PopupPad + PopupNumw) && x < (PopupPad + PopupNumw) + p.textw));
|
||||||
|
if(x < PopupPad + PopupNumw)
|
||||||
|
numink++;
|
||||||
|
else
|
||||||
|
textink++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CT_CHECK(t, Colsel != Colselfg);
|
||||||
|
CT_CHECK(t, selbg > p.selw*Fontsz/2);
|
||||||
|
CT_CHECK(t, numink > 0);
|
||||||
|
CT_CHECK(t, textink > 0);
|
||||||
|
|
||||||
|
/* The marker occupies only its compact, right-aligned footer box. */
|
||||||
|
markink = 0;
|
||||||
|
for(y = p.marky; y < p.marky + Fontsz; y++){
|
||||||
|
for(x = 0; x < p.w; x++){
|
||||||
|
if(pixel(img, &p, x, y) == (Colbg & Rgbmask))
|
||||||
|
continue;
|
||||||
|
markink++;
|
||||||
|
CT_CHECK(t, x >= p.markx && x < p.markx + p.markw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CT_CHECK(t, markink > 0);
|
||||||
|
|
||||||
|
/* A long candidate grows from the stable base to the monitor cap. */
|
||||||
|
memset(&dc, 0, sizeof dc);
|
||||||
|
dc.sel = -1;
|
||||||
|
dc.nkouho = 1;
|
||||||
|
dc.kouho[0] = longrow;
|
||||||
|
n = textwidth(&longrow);
|
||||||
|
popuplayout(&dc, 2*PopupBasew, Imgh, &p);
|
||||||
|
CT_EQ_INT(t, 2*PopupBasew, p.w);
|
||||||
|
CT_CHECK(t, p.textw > PopupTextw);
|
||||||
|
CT_CHECK(t, n > p.textw);
|
||||||
|
CT_EQ_INT(t, PopupPad, p.rowsy);
|
||||||
|
CT_EQ_INT(t, p.w - PopupPad, PopupPad + PopupNumw + p.textw);
|
||||||
|
popupdraw(img, &dc, &p);
|
||||||
|
ink = 0;
|
||||||
|
for(y = p.rowsy; y < p.rowsy + Fontsz; y++){
|
||||||
|
for(x = 0; x < p.w; x++){
|
||||||
|
c = pixel(img, &p, x, y);
|
||||||
|
if(x < PopupPad || x >= p.w - PopupPad)
|
||||||
|
CT_EQ_UINT(t, Colbg, c);
|
||||||
|
if(x >= (PopupPad + PopupNumw) && x < (PopupPad + PopupNumw) + p.textw &&
|
||||||
|
c != (Colbg & Rgbmask))
|
||||||
|
ink++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CT_CHECK(t, ink > 0);
|
||||||
|
CT_EQ_INT(t, n, textwidth(&longrow));
|
||||||
|
|
||||||
|
popuplayout(&dc, PopupBasew - 1, Imgh, &p);
|
||||||
|
CT_EQ_INT(t, PopupBasew - 1, p.w);
|
||||||
|
|
||||||
|
/* Height truncation scrolls the row window to the selected candidate. */
|
||||||
|
memset(&dc, 0, sizeof dc);
|
||||||
|
dc.nkouho = Maxdisp;
|
||||||
|
dc.sel = Maxdisp - 1;
|
||||||
|
dc.total = Maxdisp;
|
||||||
|
for(i = 0; i < Maxdisp; i++)
|
||||||
|
dc.kouho[i] = mkstr("candidate");
|
||||||
|
popuplayout(&dc, PopupBasew, 2*PopupPad + 2*Fontsz, &p);
|
||||||
|
CT_EQ_INT(t, 1, p.n);
|
||||||
|
CT_EQ_INT(t, Maxdisp - 1, p.row0);
|
||||||
|
CT_EQ_INT(t, PopupPad, p.sely);
|
||||||
|
CT_CHECK(t, p.marky >= 0);
|
||||||
|
CT_CHECK(t, p.h <= 2*PopupPad + 2*Fontsz);
|
||||||
|
popupdraw(img, &dc, &p);
|
||||||
|
numink = 0;
|
||||||
|
for(y = p.rowsy; y < p.rowsy + Fontsz; y++)
|
||||||
|
for(x = PopupPad; x < PopupPad + PopupNumw; x++)
|
||||||
|
if(pixel(img, &p, x, y) != (Colsel & Rgbmask))
|
||||||
|
numink++;
|
||||||
|
markink = 0;
|
||||||
|
for(y = p.marky; y < p.marky + Fontsz; y++)
|
||||||
|
for(x = p.markx; x < p.markx + p.markw; x++)
|
||||||
|
if(pixel(img, &p, x, y) != (Colbg & Rgbmask))
|
||||||
|
markink++;
|
||||||
|
CT_CHECK(t, numink > 0); /* full-width 9 */
|
||||||
|
CT_CHECK(t, markink > 0); /* 9-9/9 */
|
||||||
|
|
||||||
|
free(img);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,25 +3,19 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "../srv.c"
|
#include "srv.c"
|
||||||
#include "../cutest/cutest.h"
|
#include "test.h"
|
||||||
|
|
||||||
void testengineinit(int);
|
|
||||||
void testenginehandle(Keyreq*);
|
|
||||||
void* testengineowner(void);
|
|
||||||
void testenginepreedit(Str*);
|
|
||||||
|
|
||||||
#undef recv
|
#undef recv
|
||||||
|
|
||||||
typedef struct Enginegate Enginegate;
|
typedef struct Serverfix Serverfix;
|
||||||
typedef struct Testclient Testclient;
|
typedef struct Testclient Testclient;
|
||||||
|
|
||||||
struct Enginegate
|
/* A held pump plus a private client-slot channel of the wanted depth. */
|
||||||
|
struct Serverfix
|
||||||
{
|
{
|
||||||
Channel *seen;
|
Pump pump;
|
||||||
Channel *go;
|
Channel *oldclientc;
|
||||||
Channel *stop;
|
|
||||||
Channel *done;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Testclient
|
struct Testclient
|
||||||
@@ -34,30 +28,30 @@ struct Testclient
|
|||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
enginegate(void *arg)
|
serverbegin(Serverfix *f, int nclients)
|
||||||
{
|
{
|
||||||
Enginegate *g;
|
Drawcmd dc;
|
||||||
Keyreq req;
|
|
||||||
uchar token;
|
uchar token;
|
||||||
Alt alts[] = {
|
|
||||||
{keyc, &req, CHANRCV, nil},
|
|
||||||
{nil, &token, CHANRCV, nil},
|
|
||||||
{nil, nil, CHANEND, nil},
|
|
||||||
};
|
|
||||||
|
|
||||||
g = arg;
|
f->oldclientc = clientc;
|
||||||
alts[1].c = g->stop;
|
while(channbrecv(drawc, &dc) > 0)
|
||||||
for(;;)
|
;
|
||||||
switch(alt(alts)){
|
clientc = chancreate(sizeof token, nclients);
|
||||||
case 0:
|
testengineinit(LangJP);
|
||||||
chansend(g->seen, &req);
|
pumpstart(&f->pump, 0);
|
||||||
chanrecv(g->go, &token);
|
pumphold(&f->pump, Pumpall);
|
||||||
testenginehandle(&req);
|
}
|
||||||
break;
|
|
||||||
case 1:
|
static void
|
||||||
chansend(g->done, &token);
|
serverend(Serverfix *f)
|
||||||
return;
|
{
|
||||||
}
|
Drawcmd dc;
|
||||||
|
|
||||||
|
pumpstop(&f->pump);
|
||||||
|
chanfree(clientc);
|
||||||
|
clientc = f->oldclientc;
|
||||||
|
while(channbrecv(drawc, &dc) > 0)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -142,38 +136,70 @@ sendreset(struct ct *t, Testclient *client, int want)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Keyreq
|
static Keyreq
|
||||||
nextrequest(struct ct *t, Enginegate *g, int op)
|
nextrequestcap(struct ct *t, Pump *p, int op, int cap)
|
||||||
{
|
{
|
||||||
Keyreq req;
|
Keyreq req;
|
||||||
|
|
||||||
memset(&req, 0, sizeof req);
|
memset(&req, 0, sizeof req);
|
||||||
chanrecv(g->seen, &req);
|
chanrecv(p->trace, &req);
|
||||||
CT_EQ_INT(t, op, req.op);
|
CT_EQ_INT(t, op, req.op);
|
||||||
|
CT_EQ_INT(t, cap, req.clientpre);
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Keyreq
|
||||||
|
nextrequest(struct ct *t, Pump *p, int op)
|
||||||
|
{
|
||||||
|
return nextrequestcap(t, p, op, 1);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
allowrequest(Enginegate *g)
|
allowrequest(Pump *p)
|
||||||
{
|
{
|
||||||
uchar token;
|
uchar token;
|
||||||
|
|
||||||
token = 0;
|
token = 0;
|
||||||
chansend(g->go, &token);
|
chansend(p->go, &token);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
readreply(struct ct *t, Testclient *client, int want, char *preedit, int npreedit)
|
readreply(struct ct *t, Testclient *client, int want, char *wantcommit,
|
||||||
|
char *preedit)
|
||||||
{
|
{
|
||||||
char commit[Maxutf];
|
char commit[Ipcfieldmax+1];
|
||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
|
|
||||||
if(ipcreadresp(client->peer, want, commit, sizeof commit,
|
if(ipcreadresp(client->peer, want, commit, preedit, &resp) < 0)
|
||||||
preedit, npreedit, &resp) < 0)
|
|
||||||
return CT_ERRORF(t, "response read failed: %s", strerror(errno));
|
return CT_ERRORF(t, "response read failed: %s", strerror(errno));
|
||||||
CT_EQ_STR(t, "", commit);
|
CT_EQ_STR(t, wantcommit, commit);
|
||||||
return resp.eaten;
|
return resp.eaten;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sendframe(struct ct *t, Testclient *client, uchar *frame, int n, int fragment)
|
||||||
|
{
|
||||||
|
int i, m;
|
||||||
|
|
||||||
|
for(i = 0; i < n; i += m){
|
||||||
|
m = fragment && n-i > 1 ? 1 : n-i;
|
||||||
|
if(ipcsend(client->peer, frame+i, m) < 0)
|
||||||
|
return CT_ERRORF(t, "frame send failed: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
checknoreply(struct ct *t, Testclient *client)
|
||||||
|
{
|
||||||
|
uchar byte;
|
||||||
|
ssize_t n;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
n = recv(client->peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
|
||||||
|
CT_EQ_INT(t, -1, n);
|
||||||
|
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
waitclient(struct ct *t, Testclient *client)
|
waitclient(struct ct *t, Testclient *client)
|
||||||
{
|
{
|
||||||
@@ -191,127 +217,113 @@ waitclient(struct ct *t, Testclient *client)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
disconnectclient(struct ct *t, Enginegate *g, Testclient *client, void *owner)
|
disconnectclient(struct ct *t, Pump *p, Testclient *client, void *owner)
|
||||||
{
|
{
|
||||||
Keyreq req;
|
Keyreq req;
|
||||||
uchar token;
|
uchar token;
|
||||||
|
|
||||||
close(client->peer);
|
close(client->peer);
|
||||||
client->peer = -1;
|
client->peer = -1;
|
||||||
req = nextrequest(t, g, Keyrelease);
|
req = nextrequest(t, p, Keyrelease);
|
||||||
if(owner != nil)
|
if(owner != nil)
|
||||||
CT_EQ_PTR(t, owner, req.owner);
|
CT_EQ_PTR(t, owner, req.owner);
|
||||||
CT_CHECK(t, channbrecv(client->done, &token) <= 0);
|
CT_CHECK(t, channbrecv(client->done, &token) <= 0);
|
||||||
allowrequest(g);
|
allowrequest(p);
|
||||||
waitclient(t, client);
|
waitclient(t, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
server_connection_ownership(struct ct *t)
|
server_connection_ownership(struct ct *t)
|
||||||
{
|
{
|
||||||
Channel *oldclientc;
|
Serverfix f;
|
||||||
Enginegate gate;
|
|
||||||
Testclient a, b, c;
|
Testclient a, b, c;
|
||||||
Keyreq req;
|
Keyreq req;
|
||||||
Drawcmd dc;
|
|
||||||
Str shown;
|
Str shown;
|
||||||
char preedit[Maxutf];
|
char preedit[Ipcfieldmax+1];
|
||||||
void *aowner, *bowner, *cowner;
|
void *aowner, *bowner, *cowner;
|
||||||
uchar byte, token;
|
uchar byte, token;
|
||||||
ssize_t n;
|
ssize_t n;
|
||||||
int gateactive;
|
|
||||||
|
|
||||||
memset(&gate, 0, sizeof gate);
|
|
||||||
memset(&a, 0, sizeof a);
|
memset(&a, 0, sizeof a);
|
||||||
memset(&b, 0, sizeof b);
|
memset(&b, 0, sizeof b);
|
||||||
memset(&c, 0, sizeof c);
|
memset(&c, 0, sizeof c);
|
||||||
a.fd = a.peer = b.fd = b.peer = c.fd = c.peer = -1;
|
a.fd = a.peer = b.fd = b.peer = c.fd = c.peer = -1;
|
||||||
aowner = bowner = cowner = nil;
|
aowner = bowner = cowner = nil;
|
||||||
oldclientc = clientc;
|
serverbegin(&f, 3);
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
|
||||||
;
|
|
||||||
clientc = chancreate(sizeof token, 3);
|
|
||||||
gate.seen = chancreate(sizeof(Keyreq), 0);
|
|
||||||
gate.go = chancreate(sizeof token, 0);
|
|
||||||
gate.stop = chancreate(sizeof token, 0);
|
|
||||||
gate.done = chancreate(sizeof token, 0);
|
|
||||||
testengineinit(LangJP);
|
|
||||||
gateactive = proccreate(enginegate, &gate, 8192) >= 0;
|
|
||||||
if(!CT_CHECK(t, gateactive))
|
|
||||||
goto cleanup;
|
|
||||||
if(!startclient(t, &a) || !startclient(t, &b))
|
if(!startclient(t, &a) || !startclient(t, &b))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
if(!sendkey(t, &a, 1, 0, 'k'))
|
if(!sendkey(t, &a, 1, 0, 'k'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
aowner = req.owner;
|
aowner = req.owner;
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
|
||||||
CT_EQ_STR(t, "k", preedit);
|
CT_EQ_STR(t, "k", preedit);
|
||||||
if(!sendkey(t, &a, 1, 0, 'a'))
|
if(!sendkey(t, &a, 1, 0, 'a'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
CT_EQ_PTR(t, aowner, req.owner);
|
CT_EQ_PTR(t, aowner, req.owner);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
|
||||||
CT_EQ_STR(t, "か", preedit);
|
CT_EQ_STR(t, "か", preedit);
|
||||||
CT_EQ_PTR(t, aowner, testengineowner());
|
CT_EQ_PTR(t, aowner, testengineowner());
|
||||||
|
|
||||||
if(!sendkey(t, &b, 1, 0, 'n'))
|
if(!sendkey(t, &b, 1, 0, 'n'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
bowner = req.owner;
|
bowner = req.owner;
|
||||||
CT_CHECK(t, bowner != aowner);
|
CT_CHECK(t, bowner != aowner);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
||||||
CT_EQ_STR(t, "ん", preedit);
|
CT_EQ_STR(t, "ん", preedit);
|
||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
|
|
||||||
if(!sendreset(t, &a, 1))
|
if(!sendreset(t, &a, 1))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keyreset);
|
req = nextrequest(t, &f.pump, Keyreset);
|
||||||
CT_EQ_PTR(t, aowner, req.owner);
|
CT_EQ_PTR(t, aowner, req.owner);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
|
/* 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, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, !readreply(t, &b, 1, preedit, sizeof preedit));
|
CT_CHECK(t, !readreply(t, &b, 1, "", preedit));
|
||||||
CT_EQ_STR(t, "ん", preedit);
|
CT_EQ_STR(t, "ん", preedit);
|
||||||
|
|
||||||
disconnectclient(t, &gate, &a, aowner);
|
disconnectclient(t, &f.pump, &a, aowner);
|
||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
if(!sendkey(t, &b, 1, 0, 'y'))
|
if(!sendkey(t, &b, 1, 0, 'y'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
||||||
if(!sendkey(t, &b, 1, 0, 'a'))
|
if(!sendkey(t, &b, 1, 0, 'a'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
||||||
CT_EQ_STR(t, "にゃ", preedit);
|
CT_EQ_STR(t, "にゃ", preedit);
|
||||||
|
|
||||||
if(!sendreset(t, &b, 1))
|
if(!sendreset(t, &b, 1))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keyreset);
|
req = nextrequest(t, &f.pump, Keyreset);
|
||||||
CT_EQ_PTR(t, bowner, req.owner);
|
CT_EQ_PTR(t, bowner, req.owner);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &b, 1, "にゃ", preedit));
|
||||||
CT_EQ_STR(t, "", preedit);
|
CT_EQ_STR(t, "", preedit);
|
||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
|
|
||||||
if(!sendkey(t, &b, 0, 0, 'k'))
|
if(!sendkey(t, &b, 0, 0, 'k'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequestcap(t, &f.pump, Keypress, 0);
|
||||||
CT_EQ_PTR(t, bowner, req.owner);
|
CT_EQ_PTR(t, bowner, req.owner);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &b, 0, nil, 0));
|
CT_CHECK(t, readreply(t, &b, 0, "", preedit));
|
||||||
errno = 0;
|
errno = 0;
|
||||||
n = recv(b.peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
|
n = recv(b.peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
|
||||||
CT_EQ_INT(t, -1, n);
|
CT_EQ_INT(t, -1, n);
|
||||||
@@ -319,12 +331,12 @@ server_connection_ownership(struct ct *t)
|
|||||||
CT_EQ_PTR(t, bowner, testengineowner());
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
if(!sendkey(t, &b, 1, 0, 'a'))
|
if(!sendkey(t, &b, 1, 0, 'a'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
||||||
CT_EQ_STR(t, "か", preedit);
|
CT_EQ_STR(t, "か", preedit);
|
||||||
|
|
||||||
disconnectclient(t, &gate, &b, bowner);
|
disconnectclient(t, &f.pump, &b, bowner);
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
testenginepreedit(&shown);
|
testenginepreedit(&shown);
|
||||||
CT_EQ_INT(t, 0, shown.n);
|
CT_EQ_INT(t, 0, shown.n);
|
||||||
@@ -333,38 +345,205 @@ server_connection_ownership(struct ct *t)
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
if(!sendkey(t, &c, 1, 0, 'k'))
|
if(!sendkey(t, &c, 1, 0, 'k'))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req = nextrequest(t, &gate, Keypress);
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
cowner = req.owner;
|
cowner = req.owner;
|
||||||
close(c.peer);
|
close(c.peer);
|
||||||
c.peer = -1;
|
c.peer = -1;
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
req = nextrequest(t, &gate, Keyrelease);
|
req = nextrequest(t, &f.pump, Keyrelease);
|
||||||
CT_EQ_PTR(t, cowner, req.owner);
|
CT_EQ_PTR(t, cowner, req.owner);
|
||||||
CT_EQ_PTR(t, cowner, testengineowner());
|
CT_EQ_PTR(t, cowner, testengineowner());
|
||||||
CT_CHECK(t, channbrecv(c.done, &token) <= 0);
|
CT_CHECK(t, channbrecv(c.done, &token) <= 0);
|
||||||
allowrequest(&gate);
|
allowrequest(&f.pump);
|
||||||
waitclient(t, &c);
|
waitclient(t, &c);
|
||||||
CT_EQ_PTR(t, nil, testengineowner());
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
CT_CHECK(t, channbrecv(clientc, &token) <= 0);
|
CT_CHECK(t, channbrecv(clientc, &token) <= 0);
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
if(a.peer >= 0)
|
if(a.peer >= 0)
|
||||||
disconnectclient(t, &gate, &a, aowner);
|
disconnectclient(t, &f.pump, &a, aowner);
|
||||||
if(b.peer >= 0)
|
if(b.peer >= 0)
|
||||||
disconnectclient(t, &gate, &b, bowner);
|
disconnectclient(t, &f.pump, &b, bowner);
|
||||||
if(c.peer >= 0)
|
if(c.peer >= 0)
|
||||||
disconnectclient(t, &gate, &c, cowner);
|
disconnectclient(t, &f.pump, &c, cowner);
|
||||||
if(gateactive){
|
serverend(&f);
|
||||||
token = 0;
|
}
|
||||||
chansend(gate.stop, &token);
|
|
||||||
chanrecv(gate.done, &token);
|
void
|
||||||
}
|
server_extension_stream(struct ct *t)
|
||||||
chanfree(gate.seen);
|
{
|
||||||
chanfree(gate.go);
|
Serverfix f;
|
||||||
chanfree(gate.stop);
|
Testclient a, b;
|
||||||
chanfree(gate.done);
|
Keyreq req;
|
||||||
chanfree(clientc);
|
uchar frame[Ipccaretsz];
|
||||||
clientc = oldclientc;
|
char preedit[Ipcfieldmax+1];
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
void *aowner, *bowner;
|
||||||
;
|
|
||||||
|
memset(&a, 0, sizeof a);
|
||||||
|
memset(&b, 0, sizeof b);
|
||||||
|
a.fd = a.peer = b.fd = b.peer = -1;
|
||||||
|
aowner = bowner = nil;
|
||||||
|
serverbegin(&f, 2);
|
||||||
|
if(!startclient(t, &a))
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
/* Capability and caret frames may be split at any byte boundary. */
|
||||||
|
ipcpackcap(frame, 1);
|
||||||
|
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &f.pump, Keycap);
|
||||||
|
aowner = req.owner;
|
||||||
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
|
allowrequest(&f.pump);
|
||||||
|
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
||||||
|
CT_EQ_STR(t, "", preedit);
|
||||||
|
|
||||||
|
ipcpackcaret(frame, 1, -101, -7, 23);
|
||||||
|
if(!sendframe(t, &a, frame, Ipccaretsz, 1))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &f.pump, Keycaret);
|
||||||
|
CT_EQ_PTR(t, aowner, req.owner);
|
||||||
|
CT_EQ_INT(t, 1, req.caret.valid);
|
||||||
|
CT_EQ_INT(t, -101, req.caret.x);
|
||||||
|
CT_EQ_INT(t, -7, req.caret.y);
|
||||||
|
CT_EQ_INT(t, 23, req.caret.h);
|
||||||
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
|
allowrequest(&f.pump);
|
||||||
|
checknoreply(t, &a);
|
||||||
|
|
||||||
|
ipcpackreq(frame, 1, 0, 'k');
|
||||||
|
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
|
CT_EQ_PTR(t, aowner, req.owner);
|
||||||
|
CT_EQ_INT(t, 1, req.caret.valid);
|
||||||
|
CT_EQ_INT(t, -101, req.caret.x);
|
||||||
|
CT_EQ_INT(t, -7, req.caret.y);
|
||||||
|
CT_EQ_INT(t, 23, req.caret.h);
|
||||||
|
allowrequest(&f.pump);
|
||||||
|
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
||||||
|
CT_EQ_STR(t, "k", preedit);
|
||||||
|
CT_EQ_PTR(t, aowner, testengineowner());
|
||||||
|
|
||||||
|
ipcpackcap(frame, 0);
|
||||||
|
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequestcap(t, &f.pump, Keycap, 0);
|
||||||
|
CT_EQ_PTR(t, aowner, req.owner);
|
||||||
|
CT_EQ_INT(t, 1, req.caret.valid);
|
||||||
|
allowrequest(&f.pump);
|
||||||
|
CT_EQ_INT(t, 1, readreply(t, &a, 0, "", preedit));
|
||||||
|
|
||||||
|
/* Reset is still the old six-byte frame and does not discard caret. */
|
||||||
|
ipcpackreset(frame, 0);
|
||||||
|
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequestcap(t, &f.pump, Keyreset, 0);
|
||||||
|
CT_EQ_PTR(t, aowner, req.owner);
|
||||||
|
CT_EQ_INT(t, 1, req.caret.valid);
|
||||||
|
CT_EQ_INT(t, -101, req.caret.x);
|
||||||
|
CT_EQ_INT(t, -7, req.caret.y);
|
||||||
|
CT_EQ_INT(t, 23, req.caret.h);
|
||||||
|
allowrequest(&f.pump);
|
||||||
|
CT_EQ_INT(t, 1, readreply(t, &a, 0, "k", preedit));
|
||||||
|
|
||||||
|
ipcpackcap(frame, 1);
|
||||||
|
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &f.pump, Keycap);
|
||||||
|
allowrequest(&f.pump);
|
||||||
|
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
||||||
|
CT_EQ_STR(t, "", preedit);
|
||||||
|
ipcpackreq(frame, 1, 0, 'n');
|
||||||
|
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
|
CT_EQ_INT(t, 1, req.caret.valid);
|
||||||
|
CT_EQ_INT(t, -101, req.caret.x);
|
||||||
|
allowrequest(&f.pump);
|
||||||
|
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
||||||
|
CT_EQ_STR(t, "ん", preedit);
|
||||||
|
|
||||||
|
disconnectclient(t, &f.pump, &a, aowner);
|
||||||
|
|
||||||
|
/* Disconnect drops state; a legacy client gets the historical defaults. */
|
||||||
|
if(!startclient(t, &b) || !sendkey(t, &b, 1, 0, 'k'))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
|
bowner = req.owner;
|
||||||
|
CT_EQ_INT(t, 0, req.caret.valid);
|
||||||
|
allowrequest(&f.pump);
|
||||||
|
CT_EQ_INT(t, 1, readreply(t, &b, 1, "", preedit));
|
||||||
|
CT_EQ_STR(t, "k", preedit);
|
||||||
|
|
||||||
|
disconnectclient(t, &f.pump, &b, bowner);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if(a.peer >= 0)
|
||||||
|
disconnectclient(t, &f.pump, &a, aowner);
|
||||||
|
if(b.peer >= 0)
|
||||||
|
disconnectclient(t, &f.pump, &b, bowner);
|
||||||
|
serverend(&f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
server_rejects_unknown_extension(struct ct *t)
|
||||||
|
{
|
||||||
|
Serverfix f;
|
||||||
|
Testclient badver, badop, good;
|
||||||
|
Keyreq req;
|
||||||
|
uchar frame[Ipccaretsz];
|
||||||
|
char preedit[Ipcfieldmax+1];
|
||||||
|
void *owner;
|
||||||
|
|
||||||
|
memset(&badver, 0, sizeof badver);
|
||||||
|
memset(&badop, 0, sizeof badop);
|
||||||
|
memset(&good, 0, sizeof good);
|
||||||
|
badver.fd = badver.peer = badop.fd = badop.peer = -1;
|
||||||
|
good.fd = good.peer = -1;
|
||||||
|
owner = nil;
|
||||||
|
serverbegin(&f, 3);
|
||||||
|
|
||||||
|
if(!startclient(t, &badver))
|
||||||
|
goto cleanup;
|
||||||
|
ipcpackcaret(frame, 1, 3, 4, 5);
|
||||||
|
frame[1]++;
|
||||||
|
if(!sendframe(t, &badver, frame, sizeof frame, 0))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &f.pump, Keyrelease);
|
||||||
|
allowrequest(&f.pump);
|
||||||
|
waitclient(t, &badver);
|
||||||
|
close(badver.peer);
|
||||||
|
badver.peer = -1;
|
||||||
|
|
||||||
|
if(!startclient(t, &badop))
|
||||||
|
goto cleanup;
|
||||||
|
ipcpackcaret(frame, 1, 3, 4, 5);
|
||||||
|
frame[2]++;
|
||||||
|
if(!sendframe(t, &badop, frame, sizeof frame, 1))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &f.pump, Keyrelease);
|
||||||
|
allowrequest(&f.pump);
|
||||||
|
waitclient(t, &badop);
|
||||||
|
close(badop.peer);
|
||||||
|
badop.peer = -1;
|
||||||
|
|
||||||
|
/* A malformed peer must not disturb another client connection. */
|
||||||
|
if(!startclient(t, &good) || !sendkey(t, &good, 1, 0, 'k'))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &f.pump, Keypress);
|
||||||
|
owner = req.owner;
|
||||||
|
allowrequest(&f.pump);
|
||||||
|
CT_EQ_INT(t, 1, readreply(t, &good, 1, "", preedit));
|
||||||
|
CT_EQ_STR(t, "k", preedit);
|
||||||
|
disconnectclient(t, &f.pump, &good, owner);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if(badver.peer >= 0)
|
||||||
|
disconnectclient(t, &f.pump, &badver, nil);
|
||||||
|
if(badop.peer >= 0)
|
||||||
|
disconnectclient(t, &f.pump, &badop, nil);
|
||||||
|
if(good.peer >= 0)
|
||||||
|
disconnectclient(t, &f.pump, &good, owner);
|
||||||
|
serverend(&f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,52 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
str_init_utf8(struct ct *t)
|
str_init_utf8(struct ct *t)
|
||||||
{
|
{
|
||||||
|
static char badtail[] = { 'a', (char)0x80 };
|
||||||
|
static char incomplete[] = { (char)0xea, (char)0xb0 };
|
||||||
|
static char overlong[] = { (char)0xc0, (char)0xaf };
|
||||||
|
static char outofrange[] = {
|
||||||
|
(char)0xf4, (char)0x90, (char)0x80, (char)0x80,
|
||||||
|
};
|
||||||
|
static char surrogate[] = {
|
||||||
|
(char)0xed, (char)0xa0, (char)0x80,
|
||||||
|
};
|
||||||
|
static char unexpected[] = { (char)0x80 };
|
||||||
|
static const struct {
|
||||||
|
char *name;
|
||||||
|
char *src;
|
||||||
|
int n;
|
||||||
|
int ok;
|
||||||
|
char *want;
|
||||||
|
} cases[] = {
|
||||||
|
{ "canonical Runeerror", "\xef\xbf\xbd", 3, 1, "\xef\xbf\xbd" },
|
||||||
|
{ "incomplete", incomplete, sizeof incomplete, 0, "" },
|
||||||
|
{ "unexpected continuation", unexpected, sizeof unexpected, 0, "" },
|
||||||
|
{ "overlong", overlong, sizeof overlong, 0, "" },
|
||||||
|
{ "surrogate", surrogate, sizeof surrogate, 0, "" },
|
||||||
|
{ "out of range", outofrange, sizeof outofrange, 0, "" },
|
||||||
|
{ "bad tail", badtail, sizeof badtail, 0, "" },
|
||||||
|
};
|
||||||
char full[Maxrunes+2];
|
char full[Maxrunes+2];
|
||||||
char incomplete[] = { (char)0xea, (char)0xb0 };
|
|
||||||
Str s;
|
Str s;
|
||||||
|
int i;
|
||||||
|
|
||||||
s = mkstr("A한😀");
|
s = mkstr("A한😀");
|
||||||
CT_EQ_INT(t, 3, s.n);
|
CT_EQ_INT(t, 3, s.n);
|
||||||
checkstr(t, "round trip", "A한😀", &s);
|
checkstr(t, "round trip", "A한😀", &s);
|
||||||
sinit(&s, incomplete, sizeof incomplete);
|
for(i = 0; i < nelem(cases); i++){
|
||||||
CT_EQ_INT(t, 0, s.n);
|
memset(&s, 0xa5, sizeof s);
|
||||||
|
if(sinit(&s, cases[i].src, cases[i].n) != cases[i].ok)
|
||||||
|
CT_ERRORF(t, "%s: wrong validity", cases[i].name);
|
||||||
|
checkstr(t, cases[i].name, cases[i].want, &s);
|
||||||
|
}
|
||||||
memset(full, 'a', sizeof full);
|
memset(full, 'a', sizeof full);
|
||||||
full[sizeof full-1] = '\0';
|
full[sizeof full-1] = '\0';
|
||||||
sinit(&s, full, strlen(full));
|
CT_CHECK(t, !sinit(&s, full, strlen(full)));
|
||||||
CT_EQ_INT(t, Maxrunes, s.n);
|
CT_EQ_INT(t, 0, s.n);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -45,7 +76,6 @@ str_utf8_capacity(struct ct *t)
|
|||||||
int n;
|
int n;
|
||||||
char *want;
|
char *want;
|
||||||
} cases[] = {
|
} cases[] = {
|
||||||
{ 0, 0, nil },
|
|
||||||
{ 1, 0, "" },
|
{ 1, 0, "" },
|
||||||
{ 2, 1, "a" },
|
{ 2, 1, "a" },
|
||||||
{ 3, 1, "a" },
|
{ 3, 1, "a" },
|
||||||
@@ -64,10 +94,6 @@ str_utf8_capacity(struct ct *t)
|
|||||||
if(n != cases[i].n)
|
if(n != cases[i].n)
|
||||||
CT_ERRORF(t, "size %d: want length %d, got %d",
|
CT_ERRORF(t, "size %d: want length %d, got %d",
|
||||||
cases[i].size, cases[i].n, n);
|
cases[i].size, cases[i].n, n);
|
||||||
if(cases[i].size == 0){
|
|
||||||
CT_EQ_INT(t, 'Z', buf[0]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(strcmp(cases[i].want, buf) != 0)
|
if(strcmp(cases[i].want, buf) != 0)
|
||||||
CT_ERRORF(t, "size %d: want \"%s\", got \"%s\"",
|
CT_ERRORF(t, "size %d: want \"%s\", got \"%s\"",
|
||||||
cases[i].size, cases[i].want, buf);
|
cases[i].size, cases[i].want, buf);
|
||||||
@@ -89,10 +115,6 @@ str_invalid_and_full_appends(struct ct *t)
|
|||||||
Str s;
|
Str s;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
sinit(&s, nil, 1);
|
|
||||||
CT_EQ_INT(t, 0, s.n);
|
|
||||||
sinit(&s, "x", -1);
|
|
||||||
CT_EQ_INT(t, 0, s.n);
|
|
||||||
for(i = 0; i < Maxrunes; i++)
|
for(i = 0; i < Maxrunes; i++)
|
||||||
s.r[i] = 'a';
|
s.r[i] = 'a';
|
||||||
s.n = Maxrunes;
|
s.n = Maxrunes;
|
||||||
|
|||||||
84
tests/test.h
84
tests/test.h
@@ -1,22 +1,48 @@
|
|||||||
#include "../cutest/cutest.h"
|
/* Included after dat.h and fn.h (or after the source under test). */
|
||||||
#include "../dat.h"
|
#include "cutest/cutest.h"
|
||||||
#include "../fn.h"
|
|
||||||
|
|
||||||
extern Lang testvi;
|
/*
|
||||||
|
* A test's stand-in for imthread: handles engine requests from keyc,
|
||||||
|
* tracing each one first. Requests whose op is holdop (every request
|
||||||
|
* when holdop is Pumpall) wait on go before the engine sees them.
|
||||||
|
*/
|
||||||
|
typedef struct Pump Pump;
|
||||||
|
struct Pump
|
||||||
|
{
|
||||||
|
Channel *trace;
|
||||||
|
Channel *go;
|
||||||
|
Channel *stop;
|
||||||
|
Channel *done;
|
||||||
|
int holdop;
|
||||||
|
int active;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Pumpnone = -1,
|
||||||
|
Pumpall = -2,
|
||||||
|
};
|
||||||
|
|
||||||
Str mkstr(char*);
|
Str mkstr(char*);
|
||||||
int checkstr(struct ct*, char*, char*, Str*);
|
int checkstr(struct ct*, char*, char*, Str*);
|
||||||
Str shownpre(Im*);
|
void checkenginepreedit(struct ct*, char*);
|
||||||
|
void pumpstart(Pump*, int);
|
||||||
|
void pumphold(Pump*, int);
|
||||||
|
void pumpstop(Pump*);
|
||||||
|
|
||||||
|
/* Engine internals reached through engine_test.c. */
|
||||||
|
void testengineinit(int);
|
||||||
|
void testenginehandle(Keyreq*);
|
||||||
|
void* testengineowner(void);
|
||||||
|
void testenginepreedit(Str*);
|
||||||
|
void testenginecaret(Caret*);
|
||||||
|
|
||||||
void str_init_utf8(struct ct*);
|
void str_init_utf8(struct ct*);
|
||||||
void str_edit_and_alias(struct ct*);
|
void str_edit_and_alias(struct ct*);
|
||||||
void str_utf8_capacity(struct ct*);
|
void str_utf8_capacity(struct ct*);
|
||||||
void str_invalid_and_full_appends(struct ct*);
|
void str_invalid_and_full_appends(struct ct*);
|
||||||
void hmap_set_replace_and_grow(struct ct*);
|
|
||||||
void hmap_long_utf8_keys(struct ct*);
|
|
||||||
void hmap_binary_keys_and_invalid_lengths(struct ct*);
|
|
||||||
void trie_exact_prefix_and_duplicate(struct ct*);
|
void trie_exact_prefix_and_duplicate(struct ct*);
|
||||||
void trie_optional_outputs_and_invalid_lengths(struct ct*);
|
void trie_put_and_unloaded(struct ct*);
|
||||||
void popup_layout(struct ct*);
|
void popup_layout(struct ct*);
|
||||||
void font_render(struct ct*);
|
void font_render(struct ct*);
|
||||||
void production_maps_load(struct ct*);
|
void production_maps_load(struct ct*);
|
||||||
@@ -32,10 +58,15 @@ void engine_clears_candidates(struct ct*);
|
|||||||
void engine_backspace_clears_candidates(struct ct*);
|
void engine_backspace_clears_candidates(struct ct*);
|
||||||
void engine_selects_visible_candidate(struct ct*);
|
void engine_selects_visible_candidate(struct ct*);
|
||||||
void engine_candidate_shortcut_modifiers(struct ct*);
|
void engine_candidate_shortcut_modifiers(struct ct*);
|
||||||
void engine_dictionary_queue_latest_wins(struct ct*);
|
void engine_candidate_page_metadata(struct ct*);
|
||||||
|
void engine_candidate_page_snapshots(struct ct*);
|
||||||
|
void engine_candidate_page_movement(struct ct*);
|
||||||
|
void engine_candidate_completion(struct ct*);
|
||||||
void engine_active_owner_lifecycle(struct ct*);
|
void engine_active_owner_lifecycle(struct ct*);
|
||||||
void engine_active_owner_reset(struct ct*);
|
void engine_active_owner_reset(struct ct*);
|
||||||
void engine_active_owner_caret(struct ct*);
|
void engine_active_owner_caret(struct ct*);
|
||||||
|
void engine_popup_preedit_capability(struct ct*);
|
||||||
|
void engine_vietnamese_client_preedit(struct ct*);
|
||||||
void engine_commit_contract(struct ct*);
|
void engine_commit_contract(struct ct*);
|
||||||
void engine_language_switch_state(struct ct*);
|
void engine_language_switch_state(struct ct*);
|
||||||
void engine_telex_history_bound(struct ct*);
|
void engine_telex_history_bound(struct ct*);
|
||||||
@@ -50,20 +81,23 @@ void engine_emoji_queries(struct ct*);
|
|||||||
void engine_emoji_japanese_and_multirune(struct ct*);
|
void engine_emoji_japanese_and_multirune(struct ct*);
|
||||||
void engine_emoji_digit_aliases(struct ct*);
|
void engine_emoji_digit_aliases(struct ct*);
|
||||||
void engine_emoji_navigation(struct ct*);
|
void engine_emoji_navigation(struct ct*);
|
||||||
|
void engine_search_candidate_keys(struct ct*);
|
||||||
void engine_emoji_preedit_languages(struct ct*);
|
void engine_emoji_preedit_languages(struct ct*);
|
||||||
void engine_emoji_start_and_unknown(struct ct*);
|
void engine_emoji_start_and_unknown(struct ct*);
|
||||||
void engine_emoji_dictionary_identity(struct ct*);
|
|
||||||
void engine_hanja_search(struct ct*);
|
void engine_hanja_search(struct ct*);
|
||||||
void engine_hanja_unknown_and_cancel(struct ct*);
|
void engine_hanja_unknown_and_cancel(struct ct*);
|
||||||
|
void engine_hanja_reaches_back(struct ct*);
|
||||||
|
void engine_hanja_word_prefix(struct ct*);
|
||||||
void engine_hanja_korean_keys(struct ct*);
|
void engine_hanja_korean_keys(struct ct*);
|
||||||
void engine_hanja_backspace(struct ct*);
|
void engine_hanja_backspace(struct ct*);
|
||||||
void engine_hanja_input_languages(struct ct*);
|
void engine_hanja_input_languages(struct ct*);
|
||||||
void engine_randomized_stress(struct ct*);
|
void engine_randomized_stress(struct ct*);
|
||||||
void engine_full_boundary_passthrough(struct ct*);
|
void engine_full_boundary_passthrough(struct ct*);
|
||||||
void dictionary_candidates(struct ct*);
|
void dictionary_candidates(struct ct*);
|
||||||
void dictionary_misses_clear_result(struct ct*);
|
void dictionary_misses(struct ct*);
|
||||||
void dictionary_emoji_identity(struct ct*);
|
void dictionary_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_runtime_path(struct ct*);
|
void ipc_runtime_path(struct ct*);
|
||||||
void ipc_response_pack_boundaries(struct ct*);
|
void ipc_response_pack_boundaries(struct ct*);
|
||||||
void ipc_response_empty_and_preedit(struct ct*);
|
void ipc_response_empty_and_preedit(struct ct*);
|
||||||
@@ -71,7 +105,29 @@ void ipc_response_max_and_drain(struct ct*);
|
|||||||
void ipc_response_fragmented_and_truncated(struct ct*);
|
void ipc_response_fragmented_and_truncated(struct ct*);
|
||||||
void ipc_broken_peer_send(struct ct*);
|
void ipc_broken_peer_send(struct ct*);
|
||||||
void server_connection_ownership(struct ct*);
|
void server_connection_ownership(struct ct*);
|
||||||
|
void server_extension_stream(struct ct*);
|
||||||
|
void server_rejects_unknown_extension(struct ct*);
|
||||||
void ibus_machine_id_fallback(struct ct*);
|
void ibus_machine_id_fallback(struct ct*);
|
||||||
void ibus_startup_requires_ownership(struct ct*);
|
void ibus_capability_policy(struct ct*);
|
||||||
|
void ibus_private_input_policy(struct ct*);
|
||||||
void ibus_context_lifecycle(struct ct*);
|
void ibus_context_lifecycle(struct ct*);
|
||||||
void ibus_active_release_lifecycle(struct ct*);
|
void ibus_active_release_lifecycle(struct ct*);
|
||||||
|
void compose_sequences(struct ct*);
|
||||||
|
void xim_keymap_lookup(struct ct*);
|
||||||
|
void xim_compound_text(struct ct*);
|
||||||
|
void xim_adapter_key_contract(struct ct*);
|
||||||
|
void xim_adapter_release_lifecycle(struct ct*);
|
||||||
|
void xim_adapter_free_waits_for_release(struct ct*);
|
||||||
|
void xim_adapter_styles(struct ct*);
|
||||||
|
void xim_adapter_placement(struct ct*);
|
||||||
|
void xim_adapter_placement_updates(struct ct*);
|
||||||
|
void xim_adapter_callback_replacement(struct ct*);
|
||||||
|
void xim_adapter_callback_unicode(struct ct*);
|
||||||
|
void xim_adapter_callback_cleanup(struct ct*);
|
||||||
|
void xim_adapter_callback_transfer(struct ct*);
|
||||||
|
void xim_adapter_callback_owner_loss(struct ct*);
|
||||||
|
void xim_adapter_commit_encoding(struct ct*);
|
||||||
|
void wl_modifier_mask(struct ct*);
|
||||||
|
void wl_forwarded_keys(struct ct*);
|
||||||
|
void wl_surrounding_text(struct ct*);
|
||||||
|
void wl_repeat_ends(struct ct*);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
Str
|
Str
|
||||||
@@ -21,22 +23,82 @@ checkstr(struct ct *t, char *where, char *want, Str *got)
|
|||||||
where, want, buf);
|
where, want, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
Str
|
void
|
||||||
shownpre(Im *state)
|
checkenginepreedit(struct ct *t, char *want)
|
||||||
{
|
{
|
||||||
Str mapped, shown;
|
Str preedit;
|
||||||
|
|
||||||
if(state->l->lang == LangJP || state->l->lang == LangJPK){
|
testenginepreedit(&preedit);
|
||||||
shown = state->pre;
|
checkstr(t, "engine preedit", want, &preedit);
|
||||||
if(state->raw.n == 0)
|
}
|
||||||
return shown;
|
|
||||||
if(mapget(state->l->map, &state->raw, &mapped))
|
static void
|
||||||
sappend(&shown, &mapped);
|
pumpthread(void *arg)
|
||||||
else
|
{
|
||||||
sappend(&shown, &state->raw);
|
Pump *p;
|
||||||
return shown;
|
Keyreq req;
|
||||||
}
|
uchar token;
|
||||||
if(state->l->map != nil && mapget(state->l->map, &state->pre, &shown))
|
Alt alts[] = {
|
||||||
return shown;
|
{nil, &req, CHANRCV, nil},
|
||||||
return state->pre;
|
{nil, &token, CHANRCV, nil},
|
||||||
|
{nil, nil, CHANEND, nil},
|
||||||
|
};
|
||||||
|
|
||||||
|
p = arg;
|
||||||
|
alts[0].c = keyc;
|
||||||
|
alts[1].c = p->stop;
|
||||||
|
for(;;)
|
||||||
|
switch(alt(alts)){
|
||||||
|
case 0:
|
||||||
|
chansend(p->trace, &req);
|
||||||
|
if(p->holdop == Pumpall || p->holdop == req.op)
|
||||||
|
chanrecv(p->go, &token);
|
||||||
|
testenginehandle(&req);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
chansend(p->done, &token);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ntrace is the trace channel's depth: 0 makes every trace a rendezvous. */
|
||||||
|
void
|
||||||
|
pumpstart(Pump *p, int ntrace)
|
||||||
|
{
|
||||||
|
memset(p, 0, sizeof *p);
|
||||||
|
p->trace = chancreate(sizeof(Keyreq), ntrace);
|
||||||
|
p->go = chancreate(sizeof(uchar), 0);
|
||||||
|
p->stop = chancreate(sizeof(uchar), 0);
|
||||||
|
p->done = chancreate(sizeof(uchar), 0);
|
||||||
|
p->holdop = Pumpnone;
|
||||||
|
/* Its own proc: tests block in socket I/O while the engine runs. */
|
||||||
|
proccreate(pumpthread, p, 8192);
|
||||||
|
p->active = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
pumphold(Pump *p, int op)
|
||||||
|
{
|
||||||
|
p->holdop = op;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Every held request must have been let go before stopping. */
|
||||||
|
void
|
||||||
|
pumpstop(Pump *p)
|
||||||
|
{
|
||||||
|
Keyreq req;
|
||||||
|
uchar token;
|
||||||
|
|
||||||
|
if(!p->active)
|
||||||
|
return;
|
||||||
|
while(channbrecv(p->trace, &req) > 0)
|
||||||
|
;
|
||||||
|
token = 0;
|
||||||
|
chansend(p->stop, &token);
|
||||||
|
chanrecv(p->done, &token);
|
||||||
|
chanfree(p->trace);
|
||||||
|
chanfree(p->go);
|
||||||
|
chanfree(p->stop);
|
||||||
|
chanfree(p->done);
|
||||||
|
p->active = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,71 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
trie_exact_prefix_and_duplicate(struct ct *t)
|
trie_exact_prefix_and_duplicate(struct ct *t)
|
||||||
{
|
{
|
||||||
char *v, *sentinel;
|
static const struct {
|
||||||
|
char *key;
|
||||||
|
int match;
|
||||||
|
char *want;
|
||||||
|
} cases[] = {
|
||||||
|
{ "a", TrieExact, "alpha" },
|
||||||
|
{ "ab", TrieExact, "beta" },
|
||||||
|
{ "한", TrieExact, "값" },
|
||||||
|
{ "dupli", TriePrefix, nil },
|
||||||
|
{ "duplicate", TrieExact, "second" },
|
||||||
|
{ "missing", TrieMiss, nil },
|
||||||
|
{ "", TriePrefix, nil },
|
||||||
|
};
|
||||||
|
char *v;
|
||||||
Trie *trie;
|
Trie *trie;
|
||||||
int n;
|
Str key;
|
||||||
|
int i, match, n;
|
||||||
|
|
||||||
trie = trieopen("data/trie.map");
|
trie = trieopen("data/trie.map");
|
||||||
v = trieget(trie, "a", 1, &n);
|
for(i = 0; i < nelem(cases); i++){
|
||||||
if(!CT_CHECK(t, v != nil))
|
key = mkstr(cases[i].key);
|
||||||
goto cleanup;
|
match = trielookup(trie, &key, &v, &n);
|
||||||
CT_EQ_INT(t, 5, n);
|
if(match != cases[i].match){
|
||||||
CT_EQ_MEM(t, "alpha", v, n);
|
CT_ERRORF(t, "case %d: want match %d, got %d",
|
||||||
sentinel = "unchanged";
|
i, cases[i].match, match);
|
||||||
v = sentinel;
|
continue;
|
||||||
n = 77;
|
}
|
||||||
CT_CHECK(t, trielookup(trie, "dupli", 5, &v, &n));
|
if(cases[i].want == nil){
|
||||||
CT_EQ_PTR(t, sentinel, v);
|
CT_EQ_PTR(t, nil, v);
|
||||||
CT_EQ_INT(t, 77, n);
|
CT_EQ_INT(t, 0, n);
|
||||||
v = trieget(trie, "duplicate", 9, &n);
|
}else{
|
||||||
if(!CT_CHECK(t, v != nil))
|
CT_EQ_INT(t, strlen(cases[i].want), n);
|
||||||
goto cleanup;
|
CT_EQ_MEM(t, cases[i].want, v, n);
|
||||||
CT_EQ_INT(t, 6, n);
|
}
|
||||||
CT_EQ_MEM(t, "second", v, n);
|
}
|
||||||
CT_EQ_PTR(t, nil, trieget(trie, "missing", 7, &n));
|
|
||||||
cleanup:
|
|
||||||
trieclose(trie);
|
trieclose(trie);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
trie_optional_outputs_and_invalid_lengths(struct ct *t)
|
trie_put_and_unloaded(struct ct *t)
|
||||||
{
|
{
|
||||||
char *v;
|
char *v;
|
||||||
Trie *trie;
|
Trie *trie;
|
||||||
|
Str key;
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
trie = trieopen("data/trie.map");
|
key = mkstr("k");
|
||||||
CT_CHECK(t, trieget(trie, "a", 1, nil) != nil);
|
CT_EQ_INT(t, TrieMiss, trielookup(nil, &key, &v, &n));
|
||||||
v = nil;
|
trie = trienew();
|
||||||
CT_CHECK(t, trielookup(trie, "a", 1, &v, nil));
|
CT_EQ_INT(t, TrieMiss, trielookup(trie, &key, &v, &n));
|
||||||
CT_CHECK(t, v != nil);
|
trieput(trie, "k", 1, "one two", 7);
|
||||||
n = -1;
|
trieput(trie, "ka", 2, "", 0);
|
||||||
CT_CHECK(t, trielookup(trie, "a", 1, nil, &n));
|
if(CT_EQ_INT(t, TrieExact, trielookup(trie, &key, &v, &n)))
|
||||||
CT_EQ_INT(t, 5, n);
|
CT_EQ_MEM(t, "one two", v, 7);
|
||||||
CT_CHECK(t, trielookup(trie, "dupli", 5, nil, nil));
|
key = mkstr("ka");
|
||||||
CT_EQ_PTR(t, nil, trieget(trie, nil, 1, &n));
|
if(CT_EQ_INT(t, TrieExact, trielookup(trie, &key, &v, &n)))
|
||||||
CT_EQ_PTR(t, nil, trieget(trie, "a", -1, &n));
|
CT_EQ_INT(t, 0, n);
|
||||||
CT_CHECK(t, !trielookup(trie, nil, 1, &v, &n));
|
trieput(trie, "k", 1, "three", 5);
|
||||||
CT_CHECK(t, !trielookup(trie, "a", -1, &v, &n));
|
key = mkstr("k");
|
||||||
|
if(CT_EQ_INT(t, TrieExact, trielookup(trie, &key, &v, &n)))
|
||||||
|
CT_EQ_MEM(t, "three", v, 5);
|
||||||
trieclose(trie);
|
trieclose(trie);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,13 +101,12 @@ transmap_states(struct ct *t)
|
|||||||
int eat;
|
int eat;
|
||||||
char *emit;
|
char *emit;
|
||||||
char *next;
|
char *next;
|
||||||
char *mapped;
|
|
||||||
} cases[] = {
|
} cases[] = {
|
||||||
{ "", 'k', 1, "", "k", "" },
|
{ "", 'k', 1, "", "k" },
|
||||||
{ "k", 'a', 1, "", "ka", "か" },
|
{ "k", 'a', 1, "", "ka" },
|
||||||
{ "ka", 's', 1, "か", "s", "" },
|
{ "ka", 's', 1, "か", "s" },
|
||||||
{ "ka", 'q', 0, "か", "", "" },
|
{ "ka", 'q', 0, "か", "" },
|
||||||
{ "k", 'q', 0, "k", "", "" },
|
{ "k", 'q', 0, "k", "" },
|
||||||
};
|
};
|
||||||
Emit e;
|
Emit e;
|
||||||
Im state;
|
Im state;
|
||||||
@@ -110,7 +126,6 @@ transmap_states(struct ct *t)
|
|||||||
i, cases[i].eat, e.eat);
|
i, cases[i].eat, e.eat);
|
||||||
checkstr(t, "emit", cases[i].emit, &e.s);
|
checkstr(t, "emit", cases[i].emit, &e.s);
|
||||||
checkstr(t, "next", cases[i].next, &e.next);
|
checkstr(t, "next", cases[i].next, &e.next);
|
||||||
checkstr(t, "mapped", cases[i].mapped, &e.dict);
|
|
||||||
}
|
}
|
||||||
trieclose(fixture);
|
trieclose(fixture);
|
||||||
state.l->map = saved;
|
state.l->map = saved;
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
#define CT_IMPLEMENTATION
|
#define CT_IMPLEMENTATION
|
||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
Channel *drawc;
|
Channel *drawc;
|
||||||
Channel *keyc;
|
Channel *keyc;
|
||||||
Channel *dictreqc;
|
|
||||||
Channel *dictresc;
|
|
||||||
Lang testvi;
|
|
||||||
|
|
||||||
void
|
void
|
||||||
die(char *fmt, ...)
|
die(char *fmt, ...)
|
||||||
@@ -45,32 +44,26 @@ 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
|
||||||
static const struct ct_test tests[] = {
|
static const struct ct_test tests[] = {
|
||||||
{ "str/init-utf8", str_init_utf8 },
|
{ "str/init-utf8", str_init_utf8 },
|
||||||
{ "str/edit-and-alias", str_edit_and_alias },
|
{ "str/edit-and-alias", str_edit_and_alias },
|
||||||
{ "str/utf8-capacity", str_utf8_capacity },
|
{ "str/utf8-capacity", str_utf8_capacity },
|
||||||
{ "str/invalid-full-appends", str_invalid_and_full_appends },
|
{ "str/invalid-full-appends", str_invalid_and_full_appends },
|
||||||
{ "hmap/set-replace-grow", hmap_set_replace_and_grow },
|
|
||||||
{ "hmap/long-utf8-keys", hmap_long_utf8_keys },
|
|
||||||
{ "hmap/binary-invalid-lengths", hmap_binary_keys_and_invalid_lengths },
|
|
||||||
{ "trie/exact-prefix-duplicate", trie_exact_prefix_and_duplicate },
|
{ "trie/exact-prefix-duplicate", trie_exact_prefix_and_duplicate },
|
||||||
{ "trie/optional-invalid-lengths", trie_optional_outputs_and_invalid_lengths },
|
{ "trie/put-and-unloaded", trie_put_and_unloaded },
|
||||||
{ "popup/layout", popup_layout },
|
{ "popup/layout", popup_layout },
|
||||||
{ "font/render", font_render },
|
{ "font/render", font_render },
|
||||||
{ "map/production-lifecycle", production_maps_load },
|
{ "map/production-lifecycle", production_maps_load },
|
||||||
@@ -86,10 +79,15 @@ static const struct ct_test tests[] = {
|
|||||||
{ "engine/backspace-clears-candidates", engine_backspace_clears_candidates },
|
{ "engine/backspace-clears-candidates", engine_backspace_clears_candidates },
|
||||||
{ "engine/selects-visible-candidate", engine_selects_visible_candidate },
|
{ "engine/selects-visible-candidate", engine_selects_visible_candidate },
|
||||||
{ "engine/candidate-shortcut-modifiers", engine_candidate_shortcut_modifiers },
|
{ "engine/candidate-shortcut-modifiers", engine_candidate_shortcut_modifiers },
|
||||||
{ "engine/dictionary-queue-latest", engine_dictionary_queue_latest_wins },
|
{ "engine/candidate-page-metadata", engine_candidate_page_metadata },
|
||||||
|
{ "engine/candidate-page-snapshots", engine_candidate_page_snapshots },
|
||||||
|
{ "engine/candidate-page-movement", engine_candidate_page_movement },
|
||||||
|
{ "engine/candidate-completion", engine_candidate_completion },
|
||||||
{ "engine/active-owner-lifecycle", engine_active_owner_lifecycle },
|
{ "engine/active-owner-lifecycle", engine_active_owner_lifecycle },
|
||||||
{ "engine/active-owner-reset", engine_active_owner_reset },
|
{ "engine/active-owner-reset", engine_active_owner_reset },
|
||||||
{ "engine/active-owner-caret", engine_active_owner_caret },
|
{ "engine/active-owner-caret", engine_active_owner_caret },
|
||||||
|
{ "engine/popup-preedit-capability", engine_popup_preedit_capability },
|
||||||
|
{ "engine/vietnamese-client-preedit", engine_vietnamese_client_preedit },
|
||||||
{ "engine/commit-contract", engine_commit_contract },
|
{ "engine/commit-contract", engine_commit_contract },
|
||||||
{ "engine/language-switch-state", engine_language_switch_state },
|
{ "engine/language-switch-state", engine_language_switch_state },
|
||||||
{ "engine/telex-history-bound", engine_telex_history_bound },
|
{ "engine/telex-history-bound", engine_telex_history_bound },
|
||||||
@@ -104,20 +102,22 @@ static const struct ct_test tests[] = {
|
|||||||
{ "engine/emoji-japanese-multirune", engine_emoji_japanese_and_multirune },
|
{ "engine/emoji-japanese-multirune", engine_emoji_japanese_and_multirune },
|
||||||
{ "engine/emoji-digit-aliases", engine_emoji_digit_aliases },
|
{ "engine/emoji-digit-aliases", engine_emoji_digit_aliases },
|
||||||
{ "engine/emoji-navigation", engine_emoji_navigation },
|
{ "engine/emoji-navigation", engine_emoji_navigation },
|
||||||
|
{ "engine/search-candidate-keys", engine_search_candidate_keys },
|
||||||
{ "engine/emoji-preedit-languages", engine_emoji_preedit_languages },
|
{ "engine/emoji-preedit-languages", engine_emoji_preedit_languages },
|
||||||
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
|
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
|
||||||
{ "engine/emoji-dictionary-identity", engine_emoji_dictionary_identity },
|
|
||||||
{ "engine/hanja-search", engine_hanja_search },
|
{ "engine/hanja-search", engine_hanja_search },
|
||||||
{ "engine/hanja-unknown-cancel", engine_hanja_unknown_and_cancel },
|
{ "engine/hanja-unknown-cancel", engine_hanja_unknown_and_cancel },
|
||||||
|
{ "engine/hanja-reaches-back", engine_hanja_reaches_back },
|
||||||
|
{ "engine/hanja-word-prefix", engine_hanja_word_prefix },
|
||||||
{ "engine/hanja-korean-keys", engine_hanja_korean_keys },
|
{ "engine/hanja-korean-keys", engine_hanja_korean_keys },
|
||||||
{ "engine/hanja-backspace", engine_hanja_backspace },
|
{ "engine/hanja-backspace", engine_hanja_backspace },
|
||||||
{ "engine/hanja-input-languages", engine_hanja_input_languages },
|
{ "engine/hanja-input-languages", engine_hanja_input_languages },
|
||||||
{ "engine/randomized-stress", engine_randomized_stress },
|
|
||||||
{ "engine/full-boundary-passthrough", engine_full_boundary_passthrough },
|
{ "engine/full-boundary-passthrough", engine_full_boundary_passthrough },
|
||||||
{ "dict/candidates", dictionary_candidates },
|
{ "dict/candidates", dictionary_candidates },
|
||||||
{ "dict/misses-clear-result", dictionary_misses_clear_result },
|
{ "dict/misses", dictionary_misses },
|
||||||
{ "dict/emoji-identity", dictionary_emoji_identity },
|
{ "dict/prefix", dictionary_prefix },
|
||||||
{ "ipc/masks-modifiers", ipc_masks_modifiers },
|
{ "ipc/masks-modifiers", ipc_masks_modifiers },
|
||||||
|
{ "ipc/control-caret-frames", ipc_control_and_caret_frames },
|
||||||
{ "ipc/runtime-path", ipc_runtime_path },
|
{ "ipc/runtime-path", ipc_runtime_path },
|
||||||
{ "ipc/response-pack-boundaries", ipc_response_pack_boundaries },
|
{ "ipc/response-pack-boundaries", ipc_response_pack_boundaries },
|
||||||
{ "ipc/response-empty-preedit", ipc_response_empty_and_preedit },
|
{ "ipc/response-empty-preedit", ipc_response_empty_and_preedit },
|
||||||
@@ -125,11 +125,38 @@ static const struct ct_test tests[] = {
|
|||||||
{ "ipc/response-fragmented-truncated", ipc_response_fragmented_and_truncated },
|
{ "ipc/response-fragmented-truncated", ipc_response_fragmented_and_truncated },
|
||||||
{ "ipc/broken-peer-send", ipc_broken_peer_send },
|
{ "ipc/broken-peer-send", ipc_broken_peer_send },
|
||||||
{ "server/connection-ownership", server_connection_ownership },
|
{ "server/connection-ownership", server_connection_ownership },
|
||||||
|
{ "server/extension-stream", server_extension_stream },
|
||||||
|
{ "server/rejects-unknown-extension", server_rejects_unknown_extension },
|
||||||
{ "ibus/machine-id-fallback", ibus_machine_id_fallback },
|
{ "ibus/machine-id-fallback", ibus_machine_id_fallback },
|
||||||
{ "ibus/startup-requires-ownership", ibus_startup_requires_ownership },
|
{ "ibus/capability-policy", ibus_capability_policy },
|
||||||
|
{ "ibus/private-input-policy", ibus_private_input_policy },
|
||||||
{ "ibus/context-lifecycle", ibus_context_lifecycle },
|
{ "ibus/context-lifecycle", ibus_context_lifecycle },
|
||||||
{ "ibus/active-release-lifecycle", ibus_active_release_lifecycle },
|
{ "ibus/active-release-lifecycle", ibus_active_release_lifecycle },
|
||||||
|
{ "compose/sequences", compose_sequences },
|
||||||
|
{ "xim/keymap-lookup", xim_keymap_lookup },
|
||||||
|
{ "xim/compound-text", xim_compound_text },
|
||||||
|
{ "xim/adapter-key-contract", xim_adapter_key_contract },
|
||||||
|
{ "xim/adapter-release-lifecycle", xim_adapter_release_lifecycle },
|
||||||
|
{ "xim/adapter-free-waits-release", xim_adapter_free_waits_for_release },
|
||||||
|
{ "xim/styles", xim_adapter_styles },
|
||||||
|
{ "xim/placement", xim_adapter_placement },
|
||||||
|
{ "xim/placement-updates", xim_adapter_placement_updates },
|
||||||
|
{ "xim/callback-replacement", xim_adapter_callback_replacement },
|
||||||
|
{ "xim/callback-unicode", xim_adapter_callback_unicode },
|
||||||
|
{ "xim/callback-cleanup", xim_adapter_callback_cleanup },
|
||||||
|
{ "xim/callback-transfer", xim_adapter_callback_transfer },
|
||||||
|
{ "xim/callback-owner-loss", xim_adapter_callback_owner_loss },
|
||||||
|
{ "xim/commit-encoding", xim_adapter_commit_encoding },
|
||||||
|
{ "wl/modifier-mask", wl_modifier_mask },
|
||||||
|
{ "wl/forwarded-keys", wl_forwarded_keys },
|
||||||
|
{ "wl/surrounding-text", wl_surrounding_text },
|
||||||
|
{ "wl/repeat-ends", wl_repeat_ends },
|
||||||
};
|
};
|
||||||
|
#else
|
||||||
|
static const struct ct_test tests[] = {
|
||||||
|
{ "engine/randomized-stress", engine_randomized_stress },
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
void
|
void
|
||||||
threadmain(int argc, char **argv)
|
threadmain(int argc, char **argv)
|
||||||
@@ -138,19 +165,13 @@ threadmain(int argc, char **argv)
|
|||||||
|
|
||||||
drawc = chancreate(sizeof(Drawcmd), 4);
|
drawc = chancreate(sizeof(Drawcmd), 4);
|
||||||
keyc = chancreate(sizeof(Keyreq), 0);
|
keyc = chancreate(sizeof(Keyreq), 0);
|
||||||
dictreqc = chancreate(sizeof(Dictreq), 64);
|
|
||||||
dictresc = chancreate(sizeof(Dictres), 0);
|
|
||||||
testmapinit();
|
testmapinit();
|
||||||
status = CT_RUN_ARGS(tests, argc, argv);
|
status = CT_RUN_ARGS(tests, argc, argv);
|
||||||
for(i = 0; i < nlang; i++){
|
for(i = 0; i < nlang; i++){
|
||||||
trieclose(langs[i].map);
|
trieclose(langs[i].map);
|
||||||
langs[i].map = nil;
|
langs[i].map = nil;
|
||||||
}
|
}
|
||||||
trieclose(testvi.map);
|
|
||||||
testvi.map = nil;
|
|
||||||
chanfree(drawc);
|
chanfree(drawc);
|
||||||
chanfree(keyc);
|
chanfree(keyc);
|
||||||
chanfree(dictreqc);
|
|
||||||
chanfree(dictresc);
|
|
||||||
threadexitsall(status == 0 ? nil : "tests failed");
|
threadexitsall(status == 0 ? nil : "tests failed");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,14 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
typevi(struct ct *t, char *keys, char *want)
|
typevi(struct ct *t, char *keys, char *want)
|
||||||
{
|
{
|
||||||
Emit e;
|
Str out, raw;
|
||||||
Im state;
|
|
||||||
Str out, shown;
|
|
||||||
char *p;
|
|
||||||
|
|
||||||
memset(&state, 0, sizeof state);
|
raw = mkstr(keys);
|
||||||
state.l = &testvi;
|
transstr(getlang(LangVI), nil, &raw, &out);
|
||||||
sclear(&out);
|
|
||||||
for(p = keys; *p != '\0'; p++){
|
|
||||||
e = transvi(&state, (uchar)*p);
|
|
||||||
sappend(&out, &e.s);
|
|
||||||
state.pre = e.next;
|
|
||||||
if(!e.eat)
|
|
||||||
sputr(&out, (uchar)*p);
|
|
||||||
}
|
|
||||||
shown = shownpre(&state);
|
|
||||||
sappend(&out, &shown);
|
|
||||||
checkstr(t, keys, want, &out);
|
checkstr(t, keys, want, &out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +35,11 @@ vietnamese_transitions(struct ct *t)
|
|||||||
{ "gias", "giá" },
|
{ "gias", "giá" },
|
||||||
{ "Gias", "Giá" },
|
{ "Gias", "Giá" },
|
||||||
{ "gif", "gì" },
|
{ "gif", "gì" },
|
||||||
|
{ "cuar", "của" },
|
||||||
|
{ "vowis", "với" },
|
||||||
|
{ "cuwar", "cửa" },
|
||||||
|
{ "nguyeenx", "nguyễn" },
|
||||||
|
{ "dduwowngf", "đường" },
|
||||||
{ "\\s", "s" },
|
{ "\\s", "s" },
|
||||||
{ "a\\s", "as" },
|
{ "a\\s", "as" },
|
||||||
};
|
};
|
||||||
@@ -73,12 +67,12 @@ vietnamese_backspace(struct ct *t)
|
|||||||
|
|
||||||
for(i = 0; i < nelem(cases); i++){
|
for(i = 0; i < nelem(cases); i++){
|
||||||
memset(&state, 0, sizeof state);
|
memset(&state, 0, sizeof state);
|
||||||
state.l = &testvi;
|
state.l = getlang(LangVI);
|
||||||
state.pre = mkstr(cases[i].pre);
|
state.pre = mkstr(cases[i].pre);
|
||||||
state.raw = mkstr(cases[i].raw);
|
state.raw = mkstr(cases[i].raw);
|
||||||
backvi(&state);
|
backvi(&state);
|
||||||
checkstr(t, cases[i].raw, cases[i].rawafter, &state.raw);
|
checkstr(t, cases[i].raw, cases[i].rawafter, &state.raw);
|
||||||
shown = shownpre(&state);
|
impre(&state, &shown);
|
||||||
checkstr(t, cases[i].raw, cases[i].after, &shown);
|
checkstr(t, cases[i].raw, cases[i].after, &shown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
165
tests/wl_adapter_test.c
Normal file
165
tests/wl_adapter_test.c
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#include <wayland-client.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
#include "vkv1.h"
|
||||||
|
|
||||||
|
/* The virtual keyboard is the only wire call the tested code makes. */
|
||||||
|
static int nkeysent;
|
||||||
|
static void wirekey(struct zwp_virtual_keyboard_v1*, uint32_t, uint32_t,
|
||||||
|
uint32_t);
|
||||||
|
|
||||||
|
#define zwp_virtual_keyboard_v1_key wirekey
|
||||||
|
|
||||||
|
#include "wl.c"
|
||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
static void
|
||||||
|
wirekey(struct zwp_virtual_keyboard_v1 *vkb, uint32_t time, uint32_t code,
|
||||||
|
uint32_t state)
|
||||||
|
{
|
||||||
|
USED(vkb);
|
||||||
|
USED(time);
|
||||||
|
USED(code);
|
||||||
|
USED(state);
|
||||||
|
nkeysent++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only the modifiers are read from it, so nothing else need be here. */
|
||||||
|
static char keymaptext[] =
|
||||||
|
"xkb_keymap {\n"
|
||||||
|
"xkb_keycodes { <AD01> = 24; };\n"
|
||||||
|
"xkb_types { type \"ONE_LEVEL\" {\n"
|
||||||
|
" modifiers = none; map[none] = 1; level_name[1] = \"Any\"; }; };\n"
|
||||||
|
"xkb_compatibility { };\n"
|
||||||
|
"xkb_symbols { key <AD01> { [ q ] }; };\n"
|
||||||
|
"};\n";
|
||||||
|
|
||||||
|
void
|
||||||
|
wl_modifier_mask(struct ct *t)
|
||||||
|
{
|
||||||
|
struct xkb_context *ctx;
|
||||||
|
struct xkb_keymap *km;
|
||||||
|
xkb_mod_mask_t caps, ctrl, shift;
|
||||||
|
|
||||||
|
ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||||
|
km = xkb_keymap_new_from_string(ctx, keymaptext,
|
||||||
|
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||||
|
CT_CHECK(t, km != nil);
|
||||||
|
if(km != nil){
|
||||||
|
kstate = xkb_state_new(km);
|
||||||
|
shift = (xkb_mod_mask_t)1 <<
|
||||||
|
xkb_keymap_mod_get_index(km, XKB_MOD_NAME_SHIFT);
|
||||||
|
ctrl = (xkb_mod_mask_t)1 <<
|
||||||
|
xkb_keymap_mod_get_index(km, XKB_MOD_NAME_CTRL);
|
||||||
|
caps = (xkb_mod_mask_t)1 <<
|
||||||
|
xkb_keymap_mod_get_index(km, XKB_MOD_NAME_CAPS);
|
||||||
|
xkb_state_update_mask(kstate, shift, 0, 0, 0, 0, 0);
|
||||||
|
CT_EQ_UINT(t, Mshift, modmask());
|
||||||
|
xkb_state_update_mask(kstate, shift|ctrl, 0, 0, 0, 0, 0);
|
||||||
|
CT_EQ_UINT(t, Mshift|Mctrl, modmask());
|
||||||
|
/* Caps Lock is not Shift; a Korean key must not shift under it. */
|
||||||
|
xkb_state_update_mask(kstate, 0, 0, caps, 0, 0, 0);
|
||||||
|
CT_EQ_UINT(t, 0, modmask());
|
||||||
|
xkb_state_unref(kstate);
|
||||||
|
kstate = nil;
|
||||||
|
xkb_keymap_unref(km);
|
||||||
|
}
|
||||||
|
xkb_context_unref(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wl_forwarded_keys(struct ct *t)
|
||||||
|
{
|
||||||
|
haskeymap = 1;
|
||||||
|
memset(sent, 0, sizeof sent);
|
||||||
|
nkeysent = 0;
|
||||||
|
|
||||||
|
forward(30, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
CT_CHECK(t, issent(30));
|
||||||
|
forward(30, WL_KEYBOARD_KEY_STATE_RELEASED);
|
||||||
|
CT_CHECK(t, !issent(30));
|
||||||
|
CT_EQ_INT(t, 2, nkeysent);
|
||||||
|
|
||||||
|
/* A code the bitmap cannot hold is not passed on at all. */
|
||||||
|
nkeysent = 0;
|
||||||
|
forward(Maxcode, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
CT_EQ_INT(t, 0, nkeysent);
|
||||||
|
|
||||||
|
/* Whatever is still down is released, once each and no more. */
|
||||||
|
forward(1, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
forward(200, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
nkeysent = 0;
|
||||||
|
releasekeys();
|
||||||
|
CT_EQ_INT(t, 2, nkeysent);
|
||||||
|
CT_CHECK(t, !issent(1));
|
||||||
|
CT_CHECK(t, !issent(200));
|
||||||
|
releasekeys();
|
||||||
|
CT_EQ_INT(t, 2, nkeysent);
|
||||||
|
|
||||||
|
/* Before a keymap the virtual keyboard must not be touched. */
|
||||||
|
haskeymap = 0;
|
||||||
|
nkeysent = 0;
|
||||||
|
forward(30, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
CT_EQ_INT(t, 0, nkeysent);
|
||||||
|
CT_CHECK(t, !issent(30));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Arming a repeat needs the engine, which is not here; ending one does
|
||||||
|
* not, and a repeat that outlives its key is the worst this can do.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* The text before the cursor is what a reading may reach into, and a
|
||||||
|
* take-back is measured in the bytes those runes hold.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
wl_surrounding_text(struct ct *t)
|
||||||
|
{
|
||||||
|
sclear(&pendingsurround);
|
||||||
|
imsurrounding(nil, nil, "가나다라", 9, 9);
|
||||||
|
checkstr(t, "the text before the cursor", "가나다", &pendingsurround);
|
||||||
|
surround = pendingsurround;
|
||||||
|
CT_EQ_INT(t, 3, backbytes(1));
|
||||||
|
CT_EQ_INT(t, 6, backbytes(2));
|
||||||
|
CT_EQ_INT(t, 9, backbytes(9));
|
||||||
|
CT_EQ_INT(t, 0, backbytes(0));
|
||||||
|
/* A cursor past the end of the buffer is the whole of it. */
|
||||||
|
imsurrounding(nil, nil, "ab", 99, 99);
|
||||||
|
checkstr(t, "a cursor past the end", "ab", &pendingsurround);
|
||||||
|
/* An activation starts with none, whatever the last one had. */
|
||||||
|
imactivate(nil, nil);
|
||||||
|
CT_EQ_INT(t, 0, pendingsurround.n);
|
||||||
|
sclear(&surround);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wl_repeat_ends(struct ct *t)
|
||||||
|
{
|
||||||
|
haskeymap = 1;
|
||||||
|
memset(sent, 0, sizeof sent);
|
||||||
|
|
||||||
|
/* Not a rate or a delay any seat would pick: the body was empty
|
||||||
|
* once, and a stored constant would pass for the real thing. */
|
||||||
|
grabrepeat(nil, nil, 33, 410);
|
||||||
|
CT_EQ_INT(t, 33, reprate);
|
||||||
|
CT_EQ_INT(t, 410, repdelay);
|
||||||
|
|
||||||
|
/* The key's own release ends it; another key's release does not. */
|
||||||
|
repcode = 14;
|
||||||
|
repdue = 1;
|
||||||
|
grabkey(nil, nil, 0, 0, 15, WL_KEYBOARD_KEY_STATE_RELEASED);
|
||||||
|
CT_CHECK(t, repdue != 0);
|
||||||
|
grabkey(nil, nil, 0, 0, 14, WL_KEYBOARD_KEY_STATE_RELEASED);
|
||||||
|
CT_EQ_INT(t, 0, repdue);
|
||||||
|
|
||||||
|
/* So does a seat that has stopped repeating anything. */
|
||||||
|
repdue = 1;
|
||||||
|
grabrepeat(nil, nil, 0, 410);
|
||||||
|
CT_EQ_INT(t, 0, repdue);
|
||||||
|
|
||||||
|
/* A due repeat with no text input to serve is dropped, not sent. */
|
||||||
|
grabrepeat(nil, nil, 33, 410);
|
||||||
|
active = 0;
|
||||||
|
repdue = 1;
|
||||||
|
repeat();
|
||||||
|
CT_EQ_INT(t, 0, repdue);
|
||||||
|
}
|
||||||
1160
tests/xim_adapter_test.c
Normal file
1160
tests/xim_adapter_test.c
Normal file
File diff suppressed because it is too large
Load Diff
620
tests/xim_live_test.c
Normal file
620
tests/xim_live_test.c
Normal file
@@ -0,0 +1,620 @@
|
|||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
#include <X11/keysym.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <locale.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "ipc.h"
|
||||||
|
#include "live.h"
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Eventtimeout = 4000,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct Run Run;
|
||||||
|
typedef struct Prelog Prelog;
|
||||||
|
|
||||||
|
struct Run
|
||||||
|
{
|
||||||
|
Live l;
|
||||||
|
Daemon xvfb;
|
||||||
|
Daemon daemon;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Prelog
|
||||||
|
{
|
||||||
|
int start;
|
||||||
|
int nonempty;
|
||||||
|
int empty;
|
||||||
|
int done;
|
||||||
|
int doneafterempty;
|
||||||
|
int badorder;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int xerror;
|
||||||
|
|
||||||
|
static int
|
||||||
|
waitsocket(Run *r)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
deadline = nowms() + Starttimeout;
|
||||||
|
while(leftms(deadline) > 0){
|
||||||
|
if(lstat(r->l.socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
||||||
|
(st.st_mode & 0777) == 0600)
|
||||||
|
return 1;
|
||||||
|
if(!daemonalive(&r->daemon))
|
||||||
|
return fail("daemon exited before creating its IPC socket");
|
||||||
|
pausems(20);
|
||||||
|
}
|
||||||
|
return fail("timed out waiting for daemon IPC socket");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
start(Run *r, char *daemon, char *mapdir, char *xvfb)
|
||||||
|
{
|
||||||
|
memset(r, 0, sizeof *r);
|
||||||
|
daemoninit(&r->xvfb, "Xvfb");
|
||||||
|
daemoninit(&r->daemon, "daemon");
|
||||||
|
if(!livesetup(&r->l, "xim") || !startxvfb(&r->l, &r->xvfb, xvfb))
|
||||||
|
return 0;
|
||||||
|
if(setenv("DISPLAY", r->l.display, 1) < 0 ||
|
||||||
|
setenv("XMODIFIERS", "@im=strans", 1) < 0)
|
||||||
|
return fail("set private environment: %s", strerror(errno));
|
||||||
|
if(!startdaemon(&r->l, &r->daemon, daemon, mapdir))
|
||||||
|
return 0;
|
||||||
|
return waitsocket(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
cleanup(Run *r, int noisy)
|
||||||
|
{
|
||||||
|
stopdaemon(&r->daemon);
|
||||||
|
stopdaemon(&r->xvfb);
|
||||||
|
if(noisy){
|
||||||
|
showerrors(&r->daemon);
|
||||||
|
showerrors(&r->xvfb);
|
||||||
|
}
|
||||||
|
closeerrors(&r->daemon);
|
||||||
|
closeerrors(&r->xvfb);
|
||||||
|
liveclean(&r->l);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
xerr(Display *dpy, XErrorEvent *e)
|
||||||
|
{
|
||||||
|
char msg[128];
|
||||||
|
|
||||||
|
xerror++;
|
||||||
|
XGetErrorText(dpy, e->error_code, msg, sizeof msg);
|
||||||
|
fail("X error: %s (request %d.%d)", msg, e->request_code, e->minor_code);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
pumpinput(Display *dpy, XIC ic, char *commit, size_t cap, int timeout)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
XEvent ev;
|
||||||
|
KeySym sym;
|
||||||
|
Status status;
|
||||||
|
char buf[Ipcfieldmax+1];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
XFlush(dpy);
|
||||||
|
if(XPending(dpy) == 0){
|
||||||
|
pfd.fd = ConnectionNumber(dpy);
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
pfd.revents = 0;
|
||||||
|
while(poll(&pfd, 1, timeout) < 0 && errno == EINTR)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
while(XPending(dpy) != 0){
|
||||||
|
XNextEvent(dpy, &ev);
|
||||||
|
if(XFilterEvent(&ev, None) || ic == NULL || ev.type != KeyPress)
|
||||||
|
continue;
|
||||||
|
n = Xutf8LookupString(ic, &ev.xkey, buf, sizeof buf - 1,
|
||||||
|
&sym, &status);
|
||||||
|
if(n < 0 || (status == XLookupChars &&
|
||||||
|
((size_t)n >= cap || commit == NULL)))
|
||||||
|
return 0;
|
||||||
|
if(status == XLookupChars && n != 0){
|
||||||
|
memcpy(commit, buf, n);
|
||||||
|
commit[n] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pump(Display *dpy, int timeout)
|
||||||
|
{
|
||||||
|
pumpinput(dpy, NULL, NULL, 0, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
prestart(XIC ic, XPointer data, XPointer call)
|
||||||
|
{
|
||||||
|
Prelog *p;
|
||||||
|
|
||||||
|
(void)ic;
|
||||||
|
(void)call;
|
||||||
|
p = (Prelog*)data;
|
||||||
|
if(p->start != p->done)
|
||||||
|
p->badorder++;
|
||||||
|
p->start++;
|
||||||
|
return Ipcfieldmax;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
predraw(XIM im, XPointer data, XPointer call)
|
||||||
|
{
|
||||||
|
Prelog *p;
|
||||||
|
XIMPreeditDrawCallbackStruct *draw;
|
||||||
|
|
||||||
|
(void)im;
|
||||||
|
p = (Prelog*)data;
|
||||||
|
draw = (XIMPreeditDrawCallbackStruct*)call;
|
||||||
|
if(draw != NULL && draw->text != NULL && draw->text->length != 0){
|
||||||
|
if(p->start != p->done + 1)
|
||||||
|
p->badorder++;
|
||||||
|
p->nonempty++;
|
||||||
|
}else{
|
||||||
|
if(p->start != p->done + 1)
|
||||||
|
p->badorder++;
|
||||||
|
p->empty++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
predone(XIM im, XPointer data, XPointer call)
|
||||||
|
{
|
||||||
|
Prelog *p;
|
||||||
|
|
||||||
|
(void)im;
|
||||||
|
(void)call;
|
||||||
|
p = (Prelog*)data;
|
||||||
|
if(p->empty > p->done)
|
||||||
|
p->doneafterempty++;
|
||||||
|
else
|
||||||
|
p->badorder++;
|
||||||
|
p->done++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
precaret(XIM im, XPointer data, XPointer call)
|
||||||
|
{
|
||||||
|
(void)im;
|
||||||
|
(void)data;
|
||||||
|
(void)call;
|
||||||
|
}
|
||||||
|
|
||||||
|
static XIC
|
||||||
|
callbackic(XIM im, Window win, Prelog *log)
|
||||||
|
{
|
||||||
|
union {
|
||||||
|
int (*start)(XIC, XPointer, XPointer);
|
||||||
|
XIMProc xim;
|
||||||
|
} fn;
|
||||||
|
XIMCallback start, draw, done, caret;
|
||||||
|
XVaNestedList pre;
|
||||||
|
XIC ic;
|
||||||
|
|
||||||
|
start.client_data = (XPointer)log;
|
||||||
|
fn.start = prestart;
|
||||||
|
start.callback = fn.xim;
|
||||||
|
draw.client_data = (XPointer)log;
|
||||||
|
draw.callback = predraw;
|
||||||
|
done.client_data = (XPointer)log;
|
||||||
|
done.callback = predone;
|
||||||
|
caret.client_data = (XPointer)log;
|
||||||
|
caret.callback = precaret;
|
||||||
|
pre = XVaCreateNestedList(0,
|
||||||
|
XNPreeditStartCallback, &start,
|
||||||
|
XNPreeditDrawCallback, &draw,
|
||||||
|
XNPreeditDoneCallback, &done,
|
||||||
|
XNPreeditCaretCallback, &caret,
|
||||||
|
NULL);
|
||||||
|
if(pre == NULL)
|
||||||
|
return NULL;
|
||||||
|
ic = XCreateIC(im,
|
||||||
|
XNInputStyle, XIMPreeditCallbacks|XIMStatusNothing,
|
||||||
|
XNClientWindow, win,
|
||||||
|
XNFocusWindow, win,
|
||||||
|
XNPreeditAttributes, pre,
|
||||||
|
NULL);
|
||||||
|
XFree(pre);
|
||||||
|
return ic;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sendkey(Display *dpy, XIC ic, Window win, KeySym sym, unsigned int state,
|
||||||
|
char *commit, size_t cap)
|
||||||
|
{
|
||||||
|
XKeyPressedEvent key;
|
||||||
|
XEvent ev;
|
||||||
|
KeySym got;
|
||||||
|
Status status;
|
||||||
|
char buf[Ipcfieldmax+1];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if(cap != 0)
|
||||||
|
commit[0] = '\0';
|
||||||
|
memset(&key, 0, sizeof key);
|
||||||
|
key.type = KeyPress;
|
||||||
|
key.display = dpy;
|
||||||
|
key.window = win;
|
||||||
|
key.root = DefaultRootWindow(dpy);
|
||||||
|
key.time = CurrentTime;
|
||||||
|
key.x = key.y = key.x_root = key.y_root = 1;
|
||||||
|
key.same_screen = True;
|
||||||
|
key.keycode = XKeysymToKeycode(dpy, sym);
|
||||||
|
key.state = state;
|
||||||
|
memset(&ev, 0, sizeof ev);
|
||||||
|
ev.xkey = key;
|
||||||
|
if(XFilterEvent(&ev, None)){
|
||||||
|
return pumpinput(dpy, ic, commit, cap, 20);
|
||||||
|
}
|
||||||
|
n = Xutf8LookupString(ic, &key, buf, sizeof buf - 1, &got, &status);
|
||||||
|
if(n < 0)
|
||||||
|
return fail("Xutf8LookupString failed for keysym %#lx",
|
||||||
|
(unsigned long)sym);
|
||||||
|
if(status == XLookupChars && n != 0){
|
||||||
|
if((size_t)n >= cap)
|
||||||
|
return fail("commit buffer overflow");
|
||||||
|
memcpy(commit, buf, n);
|
||||||
|
commit[n] = '\0';
|
||||||
|
}
|
||||||
|
return pumpinput(dpy, ic, commit, cap, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
waitcommit(Display *dpy, XIC ic, char *commit, size_t cap)
|
||||||
|
{
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
deadline = nowms() + Eventtimeout;
|
||||||
|
while(leftms(deadline) > 0){
|
||||||
|
if(!pumpinput(dpy, ic, commit, cap, leftms(deadline)))
|
||||||
|
return fail("read committed XIM text");
|
||||||
|
if(commit[0] != '\0')
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
waitpreedit(Display *dpy, Prelog *p)
|
||||||
|
{
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
deadline = nowms() + Eventtimeout;
|
||||||
|
while(leftms(deadline) > 0){
|
||||||
|
pump(dpy, leftms(deadline));
|
||||||
|
if(p->empty != 0 || p->done != 0 || p->badorder != 0)
|
||||||
|
return -1;
|
||||||
|
if(p->start != 0 && p->nonempty != 0)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static XIM
|
||||||
|
openim(Display *dpy)
|
||||||
|
{
|
||||||
|
XIM im;
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
deadline = nowms() + Starttimeout;
|
||||||
|
do{
|
||||||
|
im = XOpenIM(dpy, NULL, "strans", "Strans");
|
||||||
|
if(im != NULL)
|
||||||
|
return im;
|
||||||
|
pump(dpy, 20);
|
||||||
|
}while(leftms(deadline) > 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
testcallbacks(Display *dpy, XIM im, Window win)
|
||||||
|
{
|
||||||
|
char commit[Ipcfieldmax+1], *reset;
|
||||||
|
Prelog log;
|
||||||
|
XIC ic;
|
||||||
|
|
||||||
|
memset(&log, 0, sizeof log);
|
||||||
|
ic = callbackic(im, win, &log);
|
||||||
|
if(ic == NULL)
|
||||||
|
return fail("create PreeditCallbacks input context");
|
||||||
|
XSetICFocus(ic);
|
||||||
|
if(!sendkey(dpy, ic, win, XK_s, ControlMask, commit, sizeof commit) ||
|
||||||
|
!sendkey(dpy, ic, win, XK_r, 0, commit, sizeof commit)){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(waitpreedit(dpy, &log) != 1){
|
||||||
|
int start, draw, empty, done;
|
||||||
|
|
||||||
|
start = log.start;
|
||||||
|
draw = log.nonempty;
|
||||||
|
empty = log.empty;
|
||||||
|
done = log.done;
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return fail("invalid callback preedit: start=%d draw=%d empty=%d done=%d",
|
||||||
|
start, draw, empty, done);
|
||||||
|
}
|
||||||
|
pump(dpy, 50);
|
||||||
|
if(log.start != 1 || log.nonempty < 1 || log.done != 0 ||
|
||||||
|
log.empty != 0 || log.badorder != 0){
|
||||||
|
int start, draw, empty, done, ordered, bad;
|
||||||
|
|
||||||
|
start = log.start;
|
||||||
|
draw = log.nonempty;
|
||||||
|
empty = log.empty;
|
||||||
|
done = log.done;
|
||||||
|
ordered = log.doneafterempty;
|
||||||
|
bad = log.badorder;
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return fail("bad initial callback sequence: start=%d draw=%d empty=%d done=%d ordered=%d bad=%d",
|
||||||
|
start, draw, empty, done, ordered, bad);
|
||||||
|
}
|
||||||
|
reset = Xutf8ResetIC(ic);
|
||||||
|
if(reset == NULL || strcmp(reset, "ㄱ") != 0){
|
||||||
|
if(reset != NULL)
|
||||||
|
XFree(reset);
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return fail("ResetIC did not return pending preedit");
|
||||||
|
}
|
||||||
|
XFree(reset);
|
||||||
|
if(!sendkey(dpy, ic, win, XK_r, 0, commit, sizeof commit) ||
|
||||||
|
!sendkey(dpy, ic, win, XK_k, 0, commit, sizeof commit) ||
|
||||||
|
!sendkey(dpy, ic, win, XK_Return, 0, commit, sizeof commit)){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(commit[0] == '\0' && !waitcommit(dpy, ic, commit, sizeof commit)){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return fail("input context did not compose after ResetIC");
|
||||||
|
}
|
||||||
|
if(strcmp(commit, "가") != 0){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return fail("post-reset composition did not contain 가");
|
||||||
|
}
|
||||||
|
XUnsetICFocus(ic);
|
||||||
|
XDestroyIC(ic);
|
||||||
|
pump(dpy, 100);
|
||||||
|
if(log.badorder != 0)
|
||||||
|
return fail("bad callback order after ResetIC");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The popup is the one viewable override-redirect window on the private
|
||||||
|
* display; created before the client window, it must still stack above it.
|
||||||
|
* The order and the map states have to be one observation: the daemon
|
||||||
|
* raises and maps between two ungrabbed round trips, and a stale order read
|
||||||
|
* against a fresh map state says the popup is below when it is not.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
popupabove(Display *dpy, Window client)
|
||||||
|
{
|
||||||
|
XWindowAttributes wa;
|
||||||
|
Window root, parent, *kids;
|
||||||
|
unsigned i, n;
|
||||||
|
int64_t deadline;
|
||||||
|
int above;
|
||||||
|
|
||||||
|
deadline = nowms() + Eventtimeout;
|
||||||
|
for(;;){
|
||||||
|
above = 0;
|
||||||
|
root = DefaultRootWindow(dpy);
|
||||||
|
XGrabServer(dpy);
|
||||||
|
if(!XQueryTree(dpy, root, &root, &parent, &kids, &n)){
|
||||||
|
XUngrabServer(dpy);
|
||||||
|
return fail("query the window tree");
|
||||||
|
}
|
||||||
|
for(i = 0; i < n; i++){
|
||||||
|
if(kids[i] == client)
|
||||||
|
above = 1;
|
||||||
|
else if(XGetWindowAttributes(dpy, kids[i], &wa) &&
|
||||||
|
wa.override_redirect && wa.map_state == IsViewable)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
XFree(kids);
|
||||||
|
XUngrabServer(dpy);
|
||||||
|
if(i < n)
|
||||||
|
return above || fail("popup is stacked below the client");
|
||||||
|
if(leftms(deadline) == 0)
|
||||||
|
return fail("popup did not appear");
|
||||||
|
pump(dpy, 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
testnothing(Display *dpy, XIM im, Window win)
|
||||||
|
{
|
||||||
|
char commit[Ipcfieldmax+1];
|
||||||
|
XIC ic;
|
||||||
|
|
||||||
|
ic = XCreateIC(im,
|
||||||
|
XNInputStyle, XIMPreeditNothing|XIMStatusNothing,
|
||||||
|
XNClientWindow, win,
|
||||||
|
XNFocusWindow, win,
|
||||||
|
NULL);
|
||||||
|
if(ic == NULL)
|
||||||
|
return fail("create PreeditNothing input context");
|
||||||
|
XSetICFocus(ic);
|
||||||
|
if(!sendkey(dpy, ic, win, XK_s, ControlMask, commit, sizeof commit)){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
pump(dpy, 100);
|
||||||
|
if(!sendkey(dpy, ic, win, XK_r, 0, commit, sizeof commit) ||
|
||||||
|
!popupabove(dpy, win) ||
|
||||||
|
!sendkey(dpy, ic, win, XK_k, 0, commit, sizeof commit) ||
|
||||||
|
!sendkey(dpy, ic, win, XK_Return, 0, commit, sizeof commit)){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(commit[0] == '\0' &&
|
||||||
|
!waitcommit(dpy, ic, commit, sizeof commit)){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return fail("PreeditNothing composition produced no commit");
|
||||||
|
}
|
||||||
|
if(strcmp(commit, "\352\260\200") != 0){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return fail("PreeditNothing commit did not contain 가");
|
||||||
|
}
|
||||||
|
XUnsetICFocus(ic);
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
testposition(Display *dpy, XIM im, Window client, Window focus)
|
||||||
|
{
|
||||||
|
char commit[Ipcfieldmax+1];
|
||||||
|
char **missing, *def;
|
||||||
|
int nmissing;
|
||||||
|
XPoint spot;
|
||||||
|
XVaNestedList pre;
|
||||||
|
XFontSet fontset;
|
||||||
|
XIC ic;
|
||||||
|
|
||||||
|
missing = NULL;
|
||||||
|
nmissing = 0;
|
||||||
|
def = NULL;
|
||||||
|
fontset = XCreateFontSet(dpy, "fixed", &missing, &nmissing, &def);
|
||||||
|
if(missing != NULL)
|
||||||
|
XFreeStringList(missing);
|
||||||
|
if(fontset == NULL)
|
||||||
|
return fail("create XIM position font set");
|
||||||
|
spot.x = 7;
|
||||||
|
spot.y = 11;
|
||||||
|
pre = XVaCreateNestedList(0, XNSpotLocation, &spot,
|
||||||
|
XNFontSet, fontset, NULL);
|
||||||
|
if(pre == NULL){
|
||||||
|
XFreeFontSet(dpy, fontset);
|
||||||
|
return fail("create XNSpotLocation list");
|
||||||
|
}
|
||||||
|
ic = XCreateIC(im,
|
||||||
|
XNInputStyle, XIMPreeditPosition|XIMStatusNothing,
|
||||||
|
XNClientWindow, client,
|
||||||
|
XNFocusWindow, focus,
|
||||||
|
XNPreeditAttributes, pre,
|
||||||
|
NULL);
|
||||||
|
XFree(pre);
|
||||||
|
if(ic == NULL){
|
||||||
|
XFreeFontSet(dpy, fontset);
|
||||||
|
return fail("create nested PreeditPosition input context");
|
||||||
|
}
|
||||||
|
XSetICFocus(ic);
|
||||||
|
if(!sendkey(dpy, ic, focus, XK_s, ControlMask, commit, sizeof commit) ||
|
||||||
|
!sendkey(dpy, ic, focus, XK_r, 0, commit, sizeof commit)){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
XFreeFontSet(dpy, fontset);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
spot.x = 13;
|
||||||
|
spot.y = 17;
|
||||||
|
pre = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
|
||||||
|
if(pre == NULL || XSetICValues(ic, XNPreeditAttributes, pre, NULL) != NULL){
|
||||||
|
if(pre != NULL)
|
||||||
|
XFree(pre);
|
||||||
|
XDestroyIC(ic);
|
||||||
|
XFreeFontSet(dpy, fontset);
|
||||||
|
return fail("update nested XNSpotLocation");
|
||||||
|
}
|
||||||
|
XFree(pre);
|
||||||
|
if(!sendkey(dpy, ic, focus, XK_Escape, 0, commit, sizeof commit)){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
XFreeFontSet(dpy, fontset);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
XUnsetICFocus(ic);
|
||||||
|
XDestroyIC(ic);
|
||||||
|
XFreeFontSet(dpy, fontset);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
Display *dpy;
|
||||||
|
Window client, focus, root;
|
||||||
|
XIM im;
|
||||||
|
Run run;
|
||||||
|
int ok;
|
||||||
|
char *xvfb;
|
||||||
|
|
||||||
|
testname = "xim_live_test";
|
||||||
|
if(argc != 3 && argc != 4){
|
||||||
|
fprintf(stderr, "usage: xim_live_test daemon mapdir [Xvfb]\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
xvfb = argc == 4 ? argv[3] : "Xvfb";
|
||||||
|
ok = start(&run, argv[1], argv[2], xvfb);
|
||||||
|
if(!ok){
|
||||||
|
cleanup(&run, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if(setlocale(LC_CTYPE, "C.UTF-8") == NULL || !XSupportsLocale() ||
|
||||||
|
XSetLocaleModifiers("@im=strans") == NULL){
|
||||||
|
fail("initialize UTF-8 X locale for strans");
|
||||||
|
cleanup(&run, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
XSetErrorHandler(xerr);
|
||||||
|
dpy = XOpenDisplay(run.l.display);
|
||||||
|
if(dpy == NULL){
|
||||||
|
fail("open private display %s", run.l.display);
|
||||||
|
cleanup(&run, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
im = openim(dpy);
|
||||||
|
if(im == NULL){
|
||||||
|
fail("open strans XIM on private display %s", run.l.display);
|
||||||
|
XCloseDisplay(dpy);
|
||||||
|
cleanup(&run, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
root = DefaultRootWindow(dpy);
|
||||||
|
client = XCreateSimpleWindow(dpy, root, 41, 53, 240, 100, 0, 0, 0);
|
||||||
|
focus = XCreateSimpleWindow(dpy, client, 17, 19, 160, 50, 0, 0, 0);
|
||||||
|
XSelectInput(dpy, client, KeyPressMask|StructureNotifyMask);
|
||||||
|
XSelectInput(dpy, focus, KeyPressMask|StructureNotifyMask);
|
||||||
|
XMapWindow(dpy, client);
|
||||||
|
XMapWindow(dpy, focus);
|
||||||
|
XSync(dpy, False);
|
||||||
|
ok = testcallbacks(dpy, im, client) &&
|
||||||
|
testnothing(dpy, im, client) &&
|
||||||
|
testposition(dpy, im, client, focus);
|
||||||
|
XDestroyWindow(dpy, focus);
|
||||||
|
XDestroyWindow(dpy, client);
|
||||||
|
XCloseIM(im);
|
||||||
|
XSync(dpy, False);
|
||||||
|
XCloseDisplay(dpy);
|
||||||
|
if(xerror != 0){
|
||||||
|
fail("observed %d X protocol errors", xerror);
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
cleanup(&run, !ok);
|
||||||
|
if(!ok)
|
||||||
|
return 1;
|
||||||
|
printf("xim_live_test: ok\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
#include <assert.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <xcb-imdkit/encoding.h>
|
|
||||||
#include <xkbcommon/xkbcommon-keysyms.h>
|
|
||||||
|
|
||||||
uint32_t keymaplookup(const uint32_t*, int, uint16_t);
|
|
||||||
char *ximcompound(const char*, size_t, size_t*);
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
ShiftMask = 1<<0,
|
|
||||||
LockMask = 1<<1,
|
|
||||||
Group1 = 1<<13,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
checktext(const char *s)
|
|
||||||
{
|
|
||||||
char *ct, *utf8;
|
|
||||||
size_t clen, len, ulen;
|
|
||||||
|
|
||||||
len = strlen(s);
|
|
||||||
ct = ximcompound(s, len, &clen);
|
|
||||||
assert(ct != NULL);
|
|
||||||
utf8 = xcb_compound_text_to_utf8(ct, clen, &ulen);
|
|
||||||
assert(utf8 != NULL);
|
|
||||||
assert(ulen == len);
|
|
||||||
assert(memcmp(utf8, s, len) == 0);
|
|
||||||
free(utf8);
|
|
||||||
free(ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
checkutf8run(void)
|
|
||||||
{
|
|
||||||
static const char s[] = "😀";
|
|
||||||
char *ct;
|
|
||||||
size_t n;
|
|
||||||
|
|
||||||
ct = ximcompound(s, sizeof s - 1, &n);
|
|
||||||
assert(ct != NULL);
|
|
||||||
assert(n >= 6);
|
|
||||||
assert(memcmp(ct, "\033%G", 3) == 0);
|
|
||||||
assert(memcmp(ct+n-3, "\033%@", 3) == 0);
|
|
||||||
free(ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(void)
|
|
||||||
{
|
|
||||||
static const uint32_t letters[] = {
|
|
||||||
XKB_KEY_a, XKB_KEY_A, XKB_KEY_Cyrillic_ef, XKB_KEY_Cyrillic_EF,
|
|
||||||
};
|
|
||||||
static const uint32_t inferred[] = { XKB_KEY_a, XKB_KEY_NoSymbol };
|
|
||||||
static const uint32_t punctuation[] = { XKB_KEY_1, XKB_KEY_exclam };
|
|
||||||
static const uint32_t missing[] = {
|
|
||||||
XKB_KEY_a, XKB_KEY_A, XKB_KEY_NoSymbol, XKB_KEY_NoSymbol,
|
|
||||||
};
|
|
||||||
|
|
||||||
xcb_compound_text_init();
|
|
||||||
|
|
||||||
assert(keymaplookup(letters, 4, 0) == XKB_KEY_a);
|
|
||||||
assert(keymaplookup(letters, 4, ShiftMask) == XKB_KEY_A);
|
|
||||||
assert(keymaplookup(letters, 4, LockMask) == XKB_KEY_A);
|
|
||||||
assert(keymaplookup(letters, 4, LockMask|ShiftMask) == XKB_KEY_a);
|
|
||||||
assert(keymaplookup(letters, 4, Group1) == XKB_KEY_Cyrillic_ef);
|
|
||||||
assert(keymaplookup(letters, 4, Group1|ShiftMask) == XKB_KEY_Cyrillic_EF);
|
|
||||||
assert(keymaplookup(inferred, 2, ShiftMask) == XKB_KEY_A);
|
|
||||||
assert(keymaplookup(punctuation, 2, LockMask) == XKB_KEY_1);
|
|
||||||
assert(keymaplookup(punctuation, 2, LockMask|ShiftMask) == XKB_KEY_exclam);
|
|
||||||
assert(keymaplookup(missing, 4, Group1) == XKB_KEY_a);
|
|
||||||
assert(keymaplookup(NULL, 0, 0) == XKB_KEY_NoSymbol);
|
|
||||||
checktext("한글");
|
|
||||||
checktext("😀");
|
|
||||||
checktext("❤️");
|
|
||||||
checktext("A😀한");
|
|
||||||
checkutf8run();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
199
trie.c
199
trie.c
@@ -1,12 +1,19 @@
|
|||||||
|
#include <errno.h>
|
||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A trie over the runes of a key. Maps need prefix matches while
|
||||||
|
* composing; dictionaries need exact matches; both are small enough for
|
||||||
|
* one structure. Node 0 is the root.
|
||||||
|
*/
|
||||||
|
|
||||||
static int
|
static int
|
||||||
newnode(Trie *t)
|
newnode(Trie *t)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if(t->n >= t->cap){
|
if(t->n == t->cap){
|
||||||
t->cap *= 2;
|
t->cap *= 2;
|
||||||
t->nodes = erealloc(t->nodes, t->cap * sizeof(Tnode));
|
t->nodes = erealloc(t->nodes, t->cap * sizeof(Tnode));
|
||||||
}
|
}
|
||||||
@@ -17,42 +24,76 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
Trie*
|
||||||
insert(Trie *t, char *key, int klen, char *val, int vlen)
|
trienew(void)
|
||||||
{
|
{
|
||||||
int ni, ci;
|
Trie *t;
|
||||||
int i;
|
|
||||||
|
|
||||||
ni = t->root;
|
t = emalloc(sizeof(*t));
|
||||||
for(i = 0; i < klen; i++){
|
t->cap = 1024;
|
||||||
ci = find(t, ni, key[i]);
|
t->nodes = emalloc(t->cap * sizeof(Tnode));
|
||||||
|
t->n = 0;
|
||||||
|
newnode(t);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
trieput(Trie *t, char *key, int klen, char *val, int vlen)
|
||||||
|
{
|
||||||
|
Str k;
|
||||||
|
int ci, i, ni, same;
|
||||||
|
|
||||||
|
if(!sinit(&k, key, klen))
|
||||||
|
return;
|
||||||
|
ni = 0;
|
||||||
|
same = 1;
|
||||||
|
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);
|
||||||
@@ -60,47 +101,64 @@ insert(Trie *t, char *key, int klen, char *val, int vlen)
|
|||||||
t->nodes[ni].vlen = vlen;
|
t->nodes[ni].vlen = vlen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char*
|
||||||
|
readline(Biobuf *b, char *path)
|
||||||
|
{
|
||||||
|
char *line;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
line = Brdstr(b, '\n', 1);
|
||||||
|
if(errno != 0)
|
||||||
|
die("can't read %s: %r", path);
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A file holds "key<tab>value" lines; ';' starts a comment. Keys and each
|
||||||
|
* space-separated word of a value must fit a Str, which is what lookups
|
||||||
|
* hand back.
|
||||||
|
*/
|
||||||
Trie*
|
Trie*
|
||||||
trieopen(char *path)
|
trieopen(char *path)
|
||||||
{
|
{
|
||||||
Trie *t;
|
Trie *t;
|
||||||
Biobuf *b;
|
Biobuf *b;
|
||||||
char *line, *tab, *key, *val;
|
Str s;
|
||||||
int klen, vlen;
|
char *e, *line, *p, *tab;
|
||||||
|
int len, lineno;
|
||||||
|
|
||||||
b = Bopen(path, OREAD);
|
b = Bopen(path, OREAD);
|
||||||
if(b == nil)
|
if(b == nil)
|
||||||
die("can't open: %s", path);
|
die("can't open %s: %r", path);
|
||||||
t = emalloc(sizeof(*t));
|
t = trienew();
|
||||||
t->cap = 1024;
|
for(lineno = 1; (line = readline(b, path)) != nil; lineno++){
|
||||||
t->nodes = emalloc(t->cap * sizeof(Tnode));
|
len = Blinelen(b);
|
||||||
t->n = 0;
|
if(memchr(line, '\0', len) != nil)
|
||||||
t->root = newnode(t);
|
die("%s:%d: NUL byte", path, lineno);
|
||||||
while((line = Brdstr(b, '\n', 1)) != nil){
|
if(len > 0 && line[len-1] == '\r')
|
||||||
vlen = strlen(line);
|
line[--len] = '\0';
|
||||||
if(vlen > 0 && line[vlen-1] == '\r')
|
if(len == 0 || line[0] == ';'){
|
||||||
line[--vlen] = '\0';
|
|
||||||
if(line[0] == '\0' || line[0] == ';'){
|
|
||||||
free(line);
|
free(line);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tab = strchr(line, '\t');
|
tab = memchr(line, '\t', len);
|
||||||
if(tab == nil || tab == line || tab[1] == '\0' ||
|
if(tab == nil || tab == line || tab == line+len-1 ||
|
||||||
strchr(tab+1, '\t') != nil)
|
memchr(tab+1, '\t', line+len-(tab+1)) != nil)
|
||||||
die("malformed map: %s", path);
|
die("%s:%d: expected key, tab, value", path, lineno);
|
||||||
*tab = '\0';
|
if(!sinit(&s, line, tab-line))
|
||||||
key = line;
|
die("%s:%d: invalid or oversized key", path, lineno);
|
||||||
klen = tab - line;
|
for(p = tab+1; p < line+len; p = e+1){
|
||||||
if(utflen(key) > Maxrunes)
|
e = memchr(p, ' ', line+len-p);
|
||||||
die("map key too long: %s", path);
|
if(e == nil)
|
||||||
val = tab + 1;
|
e = line+len;
|
||||||
vlen = strlen(val);
|
if(!sinit(&s, p, e-p))
|
||||||
if(utflen(val) > Maxrunes)
|
die("%s:%d: invalid or oversized value", path, lineno);
|
||||||
die("map value too long: %s", path);
|
}
|
||||||
insert(t, key, klen, val, vlen);
|
trieput(t, line, tab-line, tab+1, line+len-(tab+1));
|
||||||
free(line);
|
free(line);
|
||||||
}
|
}
|
||||||
Bterm(b);
|
if(Bterm(b) < 0)
|
||||||
|
die("can't close %s: %r", path);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,46 +175,33 @@ trieclose(Trie *t)
|
|||||||
free(t);
|
free(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
char*
|
/* The node key leads to, or -1. A nil trie is an unloaded map: every key misses. */
|
||||||
trieget(Trie *t, char *key, int klen, int *vlen)
|
int
|
||||||
|
trienode(Trie *t, Str *key)
|
||||||
{
|
{
|
||||||
int ni;
|
int i, ni;
|
||||||
int i;
|
|
||||||
|
|
||||||
if(t == nil || klen < 0 || (klen > 0 && key == nil))
|
if(t == nil)
|
||||||
return nil;
|
return -1;
|
||||||
ni = t->root;
|
ni = 0;
|
||||||
for(i = 0; i < klen; i++){
|
for(i = 0; i < key->n && ni >= 0; i++)
|
||||||
ni = find(t, ni, key[i]);
|
ni = find(t, ni, key->r[i]);
|
||||||
if(ni < 0)
|
return ni;
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
if(t->nodes[ni].val == nil)
|
|
||||||
return nil;
|
|
||||||
if(vlen != nil)
|
|
||||||
*vlen = t->nodes[ni].vlen;
|
|
||||||
return t->nodes[ni].val;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
trielookup(Trie *t, char *key, int klen, char **val, int *vlen)
|
trielookup(Trie *t, Str *key, char **val, int *vlen)
|
||||||
{
|
{
|
||||||
int ni;
|
int ni;
|
||||||
int i;
|
|
||||||
|
|
||||||
if(t == nil || klen < 0 || (klen > 0 && key == nil))
|
*val = nil;
|
||||||
return 0;
|
*vlen = 0;
|
||||||
ni = t->root;
|
ni = trienode(t, key);
|
||||||
for(i = 0; i < klen; i++){
|
if(ni < 0)
|
||||||
ni = find(t, ni, key[i]);
|
return TrieMiss;
|
||||||
if(ni < 0)
|
if(t->nodes[ni].val == nil)
|
||||||
return 0;
|
return TriePrefix;
|
||||||
}
|
*val = t->nodes[ni].val;
|
||||||
if(t->nodes[ni].val != nil){
|
*vlen = t->nodes[ni].vlen;
|
||||||
if(val != nil)
|
return TrieExact;
|
||||||
*val = t->nodes[ni].val;
|
|
||||||
if(vlen != nil)
|
|
||||||
*vlen = t->nodes[ni].vlen;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|||||||
69
vi.c
69
vi.c
@@ -31,12 +31,6 @@ static struct {
|
|||||||
{L'Y', {L'Ý', L'Ỳ', L'Ỷ', L'Ỹ', L'Ỵ'}},
|
{L'Y', {L'Ý', L'Ỳ', L'Ỷ', L'Ỹ', L'Ỵ'}},
|
||||||
};
|
};
|
||||||
|
|
||||||
static int
|
|
||||||
istone(Rune c)
|
|
||||||
{
|
|
||||||
return c == 's' || c == 'f' || c == 'r' || c == 'x' || c == 'j';
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
toneidx(Rune c)
|
toneidx(Rune c)
|
||||||
{
|
{
|
||||||
@@ -114,20 +108,44 @@ onsetglide(Str *m)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Emit
|
/*
|
||||||
transvi(Im *im, Rune c)
|
* The pending text is the key sequence itself, read through the map for
|
||||||
|
* display; raw keeps every key of it so Backspace can replay one fewer.
|
||||||
|
*/
|
||||||
|
static Emit
|
||||||
|
history(Im *im, Rune c, Emit e)
|
||||||
{
|
{
|
||||||
Emit e, mappedkey;
|
Str mapped;
|
||||||
|
|
||||||
|
e.raw = im->raw;
|
||||||
|
if(e.s.n > 0 || !e.eat)
|
||||||
|
sclear(&e.raw);
|
||||||
|
if(!e.eat || e.next.n == 0)
|
||||||
|
return e;
|
||||||
|
if(e.raw.n < Maxrunes){
|
||||||
|
sputr(&e.raw, c);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
/* The history is full: flush the composed text as it stands. */
|
||||||
|
if(!mapget(im->l->map, &e.next, &mapped))
|
||||||
|
mapped = e.next;
|
||||||
|
sappend(&e.s, &mapped);
|
||||||
|
sclear(&e.next);
|
||||||
|
sclear(&e.raw);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Emit
|
||||||
|
tone(Im *im, Rune c)
|
||||||
|
{
|
||||||
|
Emit e;
|
||||||
Str mapped, pre;
|
Str mapped, pre;
|
||||||
int i, tidx, vi, last, penult, glide;
|
int i, tidx, vi, last, penult, glide;
|
||||||
Rune v, b1, b2;
|
Rune v, b1, b2;
|
||||||
|
|
||||||
if(!istone(c) && c != 'z')
|
e = transmap(im, c);
|
||||||
return transmap(im, c);
|
if(e.eat)
|
||||||
mappedkey = transmap(im, c);
|
return e;
|
||||||
if(mappedkey.eat)
|
|
||||||
return mappedkey;
|
|
||||||
|
|
||||||
memset(&e, 0, sizeof e);
|
memset(&e, 0, sizeof e);
|
||||||
if(im->pre.n == 0)
|
if(im->pre.n == 0)
|
||||||
return e;
|
return e;
|
||||||
@@ -175,17 +193,24 @@ transvi(Im *im, Rune c)
|
|||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(c == 'z')
|
tidx = toneidx(c);
|
||||||
|
if(tidx < 0)
|
||||||
mapped.r[vi] = removetone(mapped.r[vi]);
|
mapped.r[vi] = removetone(mapped.r[vi]);
|
||||||
else{
|
else
|
||||||
tidx = toneidx(c);
|
|
||||||
mapped.r[vi] = applytone(mapped.r[vi], tidx);
|
mapped.r[vi] = applytone(mapped.r[vi], tidx);
|
||||||
}
|
|
||||||
e.eat = 1;
|
e.eat = 1;
|
||||||
e.next = mapped;
|
e.next = mapped;
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Emit
|
||||||
|
transvi(Im *im, Rune c)
|
||||||
|
{
|
||||||
|
if(toneidx(c) < 0 && c != 'z')
|
||||||
|
return history(im, c, transmap(im, c));
|
||||||
|
return history(im, c, tone(im, c));
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
backvi(Im *im)
|
backvi(Im *im)
|
||||||
{
|
{
|
||||||
@@ -200,10 +225,10 @@ backvi(Im *im)
|
|||||||
raw = im->raw;
|
raw = im->raw;
|
||||||
spopr(&raw);
|
spopr(&raw);
|
||||||
sclear(&im->pre);
|
sclear(&im->pre);
|
||||||
|
sclear(&im->raw);
|
||||||
for(i = 0; i < raw.n; i++){
|
for(i = 0; i < raw.n; i++){
|
||||||
e = transvi(im, raw.r[i]);
|
e = transvi(im, raw.r[i]);
|
||||||
sclear(&im->pre);
|
im->pre = e.next;
|
||||||
sappend(&im->pre, &e.next);
|
im->raw = e.raw;
|
||||||
}
|
}
|
||||||
im->raw = raw;
|
|
||||||
}
|
}
|
||||||
|
|||||||
371
win.c
371
win.c
@@ -1,32 +1,19 @@
|
|||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
|
#include <xcb/xcb_aux.h>
|
||||||
|
#include <xcb/randr.h>
|
||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
enum {
|
|
||||||
Asciitofull = 0xFEE0,
|
|
||||||
};
|
|
||||||
|
|
||||||
extern char **fontfiles;
|
|
||||||
extern int nfontfiles;
|
|
||||||
|
|
||||||
static xcb_connection_t *conn;
|
static xcb_connection_t *conn;
|
||||||
static xcb_screen_t *scr;
|
static xcb_screen_t *scr;
|
||||||
static xcb_window_t win;
|
static xcb_window_t win;
|
||||||
static xcb_gcontext_t gc;
|
static xcb_gcontext_t gc;
|
||||||
static xcb_pixmap_t pix;
|
static xcb_pixmap_t pix;
|
||||||
static u32int *img;
|
static u32int *img;
|
||||||
static int depth;
|
static xcb_atom_t currentdesktop;
|
||||||
|
static xcb_atom_t workarea;
|
||||||
static xcb_screen_t*
|
static int hasrandr, imgh, imgw;
|
||||||
getscr(xcb_connection_t *c, int n)
|
static int shown, ptrx, ptry; /* the popup, and where the pointer was as it came up */
|
||||||
{
|
|
||||||
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)
|
||||||
@@ -46,6 +33,7 @@ getvisual(xcb_screen_t *s)
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* putimage writes native-endian x8r8g8b8; the root must take that. */
|
||||||
static int
|
static int
|
||||||
validformat(xcb_connection_t *c, xcb_screen_t *s)
|
validformat(xcb_connection_t *c, xcb_screen_t *s)
|
||||||
{
|
{
|
||||||
@@ -57,9 +45,8 @@ validformat(xcb_connection_t *c, xcb_screen_t *s)
|
|||||||
|
|
||||||
setup = xcb_get_setup(c);
|
setup = xcb_get_setup(c);
|
||||||
v = getvisual(s);
|
v = getvisual(s);
|
||||||
if(setup == nil || v == nil || s->root_depth != 24 ||
|
if(v == nil || s->root_depth != 24 ||
|
||||||
v->_class != XCB_VISUAL_CLASS_TRUE_COLOR ||
|
v->_class != XCB_VISUAL_CLASS_TRUE_COLOR ||
|
||||||
v->bits_per_rgb_value != 8 ||
|
|
||||||
v->red_mask != 0xff0000 || v->green_mask != 0x00ff00 ||
|
v->red_mask != 0xff0000 || v->green_mask != 0x00ff00 ||
|
||||||
v->blue_mask != 0x0000ff)
|
v->blue_mask != 0x0000ff)
|
||||||
return 0;
|
return 0;
|
||||||
@@ -76,177 +63,298 @@ validformat(xcb_connection_t *c, xcb_screen_t *s)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static xcb_atom_t
|
||||||
|
getatom(char *name)
|
||||||
|
{
|
||||||
|
xcb_intern_atom_cookie_t cookie;
|
||||||
|
xcb_intern_atom_reply_t *reply;
|
||||||
|
xcb_atom_t atom;
|
||||||
|
|
||||||
|
cookie = xcb_intern_atom(conn, 0, strlen(name), name);
|
||||||
|
reply = xcb_intern_atom_reply(conn, cookie, nil);
|
||||||
|
atom = reply == nil ? XCB_ATOM_NONE : reply->atom;
|
||||||
|
free(reply);
|
||||||
|
return atom;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
getcardinals(xcb_atom_t atom, u32int off, int n, u32int *v)
|
||||||
|
{
|
||||||
|
xcb_get_property_cookie_t cookie;
|
||||||
|
xcb_get_property_reply_t *reply;
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
if(atom == XCB_ATOM_NONE)
|
||||||
|
return 0;
|
||||||
|
cookie = xcb_get_property(conn, 0, scr->root, atom,
|
||||||
|
XCB_ATOM_CARDINAL, off, n);
|
||||||
|
reply = xcb_get_property_reply(conn, cookie, nil);
|
||||||
|
ok = reply != nil && reply->type == XCB_ATOM_CARDINAL &&
|
||||||
|
reply->format == 32 &&
|
||||||
|
xcb_get_property_value_length(reply) == n*(int)sizeof v[0];
|
||||||
|
if(ok)
|
||||||
|
memmove(v, xcb_get_property_value(reply), n*sizeof v[0]);
|
||||||
|
free(reply);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
getworkarea(Area *a)
|
||||||
|
{
|
||||||
|
u32int desktop, v[4];
|
||||||
|
|
||||||
|
if(!getcardinals(currentdesktop, 0, 1, &desktop) ||
|
||||||
|
desktop > 0x3fffffff ||
|
||||||
|
!getcardinals(workarea, desktop*4, 4, v) ||
|
||||||
|
v[0] > 0x7fffffff || v[1] > 0x7fffffff ||
|
||||||
|
v[2] == 0 || v[2] > 0x7fffffff ||
|
||||||
|
v[3] == 0 || v[3] > 0x7fffffff)
|
||||||
|
return 0;
|
||||||
|
a->x = v[0];
|
||||||
|
a->y = v[1];
|
||||||
|
a->w = v[2];
|
||||||
|
a->h = v[3];
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
getrootarea(Area *a)
|
||||||
|
{
|
||||||
|
xcb_get_geometry_cookie_t cookie;
|
||||||
|
xcb_get_geometry_reply_t *reply;
|
||||||
|
|
||||||
|
a->x = 0;
|
||||||
|
a->y = 0;
|
||||||
|
a->w = scr->width_in_pixels;
|
||||||
|
a->h = scr->height_in_pixels;
|
||||||
|
cookie = xcb_get_geometry(conn, scr->root);
|
||||||
|
reply = xcb_get_geometry_reply(conn, cookie, nil);
|
||||||
|
if(reply != nil && reply->width > 0 && reply->height > 0){
|
||||||
|
a->w = reply->width;
|
||||||
|
a->h = reply->height;
|
||||||
|
}
|
||||||
|
free(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The area the popup may use: the RandR monitor under (x, y), or the
|
||||||
|
* root, cut down to the EWMH work area. */
|
||||||
|
static void
|
||||||
|
popupwork(int x, int y, Area *out)
|
||||||
|
{
|
||||||
|
xcb_randr_get_monitors_cookie_t cookie;
|
||||||
|
xcb_randr_get_monitors_reply_t *reply;
|
||||||
|
xcb_randr_monitor_info_iterator_t it;
|
||||||
|
Area root, net, *mon;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
mon = nil;
|
||||||
|
n = 0;
|
||||||
|
reply = nil;
|
||||||
|
if(hasrandr){
|
||||||
|
cookie = xcb_randr_get_monitors(conn, scr->root, 1);
|
||||||
|
reply = xcb_randr_get_monitors_reply(conn, cookie, nil);
|
||||||
|
}
|
||||||
|
if(reply != nil && xcb_randr_get_monitors_monitors_length(reply) > 0){
|
||||||
|
mon = emalloc(xcb_randr_get_monitors_monitors_length(reply) *
|
||||||
|
sizeof mon[0]);
|
||||||
|
it = xcb_randr_get_monitors_monitors_iterator(reply);
|
||||||
|
for(; it.rem; xcb_randr_monitor_info_next(&it)){
|
||||||
|
if(it.data->width == 0 || it.data->height == 0)
|
||||||
|
continue;
|
||||||
|
mon[n].x = it.data->x;
|
||||||
|
mon[n].y = it.data->y;
|
||||||
|
mon[n].w = it.data->width;
|
||||||
|
mon[n].h = it.data->height;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(n == 0){
|
||||||
|
free(mon);
|
||||||
|
getrootarea(&root);
|
||||||
|
mon = &root;
|
||||||
|
n = 1;
|
||||||
|
}
|
||||||
|
popuparea(mon, n, getworkarea(&net) ? &net : nil, x, y, out);
|
||||||
|
if(mon != &root)
|
||||||
|
free(mon);
|
||||||
|
free(reply);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
wincleanup(void)
|
wincleanup(void)
|
||||||
{
|
{
|
||||||
|
textclose();
|
||||||
free(img);
|
free(img);
|
||||||
img = nil;
|
xcb_disconnect(conn);
|
||||||
if(conn != nil){
|
|
||||||
xcb_disconnect(conn);
|
|
||||||
conn = nil;
|
|
||||||
}
|
|
||||||
scr = nil;
|
|
||||||
win = 0;
|
|
||||||
gc = 0;
|
|
||||||
pix = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
wininit(void)
|
wininit(void)
|
||||||
{
|
{
|
||||||
int n;
|
int n;
|
||||||
u32int mask, vals[4];
|
u32int mask, vals[5];
|
||||||
xcb_intern_atom_cookie_t c1, c2;
|
xcb_atom_t type, tooltip;
|
||||||
xcb_intern_atom_reply_t *r1, *r2;
|
xcb_randr_query_version_cookie_t rc;
|
||||||
|
xcb_randr_query_version_reply_t *rr;
|
||||||
|
const xcb_query_extension_reply_t *rext;
|
||||||
|
|
||||||
conn = xcb_connect(nil, &n);
|
conn = xcb_connect(nil, &n);
|
||||||
if(conn == nil || xcb_connection_has_error(conn)){
|
if(xcb_connection_has_error(conn)){
|
||||||
fprint(2, "strans: popup disabled: cannot connect to X display\n");
|
fprint(2, "strans: popup disabled: cannot connect to X display\n");
|
||||||
wincleanup();
|
wincleanup();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
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();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
depth = scr->root_depth;
|
rext = xcb_get_extension_data(conn, &xcb_randr_id);
|
||||||
|
if(rext != nil && rext->present){
|
||||||
|
rc = xcb_randr_query_version(conn, 1, 5);
|
||||||
|
rr = xcb_randr_query_version_reply(conn, rc, nil);
|
||||||
|
hasrandr = rr != nil && (rr->major_version > 1 ||
|
||||||
|
(rr->major_version == 1 && rr->minor_version >= 5));
|
||||||
|
free(rr);
|
||||||
|
}
|
||||||
|
currentdesktop = getatom("_NET_CURRENT_DESKTOP");
|
||||||
|
workarea = getatom("_NET_WORKAREA");
|
||||||
win = xcb_generate_id(conn);
|
win = xcb_generate_id(conn);
|
||||||
|
/* 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);
|
||||||
c1 = xcb_intern_atom(conn, 0,
|
type = getatom("_NET_WM_WINDOW_TYPE");
|
||||||
strlen("_NET_WM_WINDOW_TYPE"), "_NET_WM_WINDOW_TYPE");
|
tooltip = getatom("_NET_WM_WINDOW_TYPE_TOOLTIP");
|
||||||
c2 = xcb_intern_atom(conn, 0,
|
if(type != XCB_ATOM_NONE && tooltip != XCB_ATOM_NONE)
|
||||||
strlen("_NET_WM_WINDOW_TYPE_TOOLTIP"),
|
|
||||||
"_NET_WM_WINDOW_TYPE_TOOLTIP");
|
|
||||||
r1 = xcb_intern_atom_reply(conn, c1, nil);
|
|
||||||
r2 = xcb_intern_atom_reply(conn, c2, nil);
|
|
||||||
if(r1 != nil && r2 != nil)
|
|
||||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE,
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE,
|
||||||
win, r1->atom, XCB_ATOM_ATOM, 32, 1, &r2->atom);
|
win, type, XCB_ATOM_ATOM, 32, 1, &tooltip);
|
||||||
free(r1);
|
|
||||||
free(r2);
|
|
||||||
gc = xcb_generate_id(conn);
|
gc = xcb_generate_id(conn);
|
||||||
xcb_create_gc(conn, gc, win, 0, nil);
|
xcb_create_gc(conn, gc, win, 0, nil);
|
||||||
pix = xcb_generate_id(conn);
|
textinit();
|
||||||
xcb_create_pixmap(conn, depth, pix, win, Imgw, Imgh);
|
|
||||||
mask = XCB_CW_BACK_PIXMAP;
|
|
||||||
xcb_change_window_attributes(conn, win, mask, &pix);
|
|
||||||
img = emalloc(Imgw * Imgh * sizeof(img[0]));
|
|
||||||
if(!fontinit(fontfiles, nfontfiles)){
|
|
||||||
fprint(2, "strans: popup disabled: no usable fonts\n");
|
|
||||||
wincleanup();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
/* The image and its retained pixmap only ever grow. */
|
||||||
drawstr(u32int *buf, int x, int y, Rune *r, int n, int maxw, int maxh)
|
static int
|
||||||
|
resizebacking(int w, int h)
|
||||||
{
|
{
|
||||||
while(n-- > 0){
|
xcb_generic_error_t *err;
|
||||||
if(*r != 0xfe0e && *r != 0xfe0f){
|
xcb_pixmap_t old, new;
|
||||||
putfont(buf, maxw, maxh, x, y, *r);
|
xcb_void_cookie_t cookie;
|
||||||
x += Fontsz;
|
int nh, nw;
|
||||||
}
|
|
||||||
r++;
|
if(w <= imgw && h <= imgh)
|
||||||
|
return 1;
|
||||||
|
nw = max(w, imgw);
|
||||||
|
nh = max(h, imgh);
|
||||||
|
img = erealloc(img, (ulong)nw * nh * sizeof img[0]);
|
||||||
|
new = xcb_generate_id(conn);
|
||||||
|
cookie = xcb_create_pixmap_checked(conn, scr->root_depth, new, win,
|
||||||
|
nw, nh);
|
||||||
|
err = xcb_request_check(conn, cookie);
|
||||||
|
if(err != nil){
|
||||||
|
free(err);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
cookie = xcb_change_window_attributes_checked(conn, win,
|
||||||
|
XCB_CW_BACK_PIXMAP, &new);
|
||||||
static void
|
err = xcb_request_check(conn, cookie);
|
||||||
fill(u32int *buf, int n, u32int color)
|
if(err != nil){
|
||||||
{
|
free(err);
|
||||||
int i;
|
xcb_free_pixmap(conn, new);
|
||||||
|
return 0;
|
||||||
for(i = 0; i < n; i++)
|
|
||||||
buf[i] = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
drawkouho(Drawcmd *dc, int n, int w, int h)
|
|
||||||
{
|
|
||||||
int npre, sely, y, i;
|
|
||||||
Str *s;
|
|
||||||
|
|
||||||
fill(img, w * h, Colbg);
|
|
||||||
npre = dc->pre.n != 0;
|
|
||||||
if(npre)
|
|
||||||
drawstr(img, 0, 0, dc->pre.r, dc->pre.n, w, h);
|
|
||||||
if(dc->sel >= 0 && dc->sel < n){
|
|
||||||
sely = (npre + dc->sel) * Fontsz;
|
|
||||||
fill(img + sely * w, Fontsz * w, Colsel);
|
|
||||||
}
|
|
||||||
for(i = 0, y = npre * Fontsz; i < n; i++, y += Fontsz){
|
|
||||||
s = &dc->kouho[i];
|
|
||||||
putfont(img, w, h, 0, y, '1' + i + Asciitofull);
|
|
||||||
drawstr(img, 2*Fontsz, y, s->r, s->n, w, h);
|
|
||||||
}
|
}
|
||||||
|
old = pix;
|
||||||
|
pix = new;
|
||||||
|
imgw = nw;
|
||||||
|
imgh = nh;
|
||||||
|
if(old != 0)
|
||||||
|
xcb_free_pixmap(conn, old);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
putimage(int w, int h)
|
putimage(int w, int h)
|
||||||
{
|
{
|
||||||
xcb_put_image(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc,
|
xcb_put_image(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc,
|
||||||
w, h, 0, 0, 0, depth, w * h * 4, (u8int*)img);
|
w, h, 0, 0, 0, scr->root_depth, w * h * 4, (u8int*)img);
|
||||||
/* The retained background pixmap lets the server repaint exposures. */
|
/* The retained background pixmap lets the server repaint exposures. */
|
||||||
xcb_clear_area(conn, 0, win, 0, 0, w, h);
|
xcb_clear_area(conn, 0, win, 0, 0, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
int npre, px, py, w, h, i, n, maxw;
|
Area area;
|
||||||
u32int vals[4];
|
Popup p;
|
||||||
|
int ax, ay, x, y;
|
||||||
|
u32int vals[5];
|
||||||
xcb_query_pointer_reply_t *ptr;
|
xcb_query_pointer_reply_t *ptr;
|
||||||
xcb_query_pointer_cookie_t cookie;
|
xcb_query_pointer_cookie_t cookie;
|
||||||
|
|
||||||
n = min(max(dc->nkouho, 0), Maxdisp);
|
if(dc->nkouho == 0 && dc->pre.n == 0){
|
||||||
npre = dc->pre.n != 0;
|
winhide();
|
||||||
if(n == 0 && npre == 0){
|
return;
|
||||||
return winhide();
|
|
||||||
}
|
}
|
||||||
maxw = popupcells(dc->pre.r, dc->pre.n);
|
if(!dc->caret.valid && !shown){
|
||||||
for(i = 0; i < n; i++)
|
|
||||||
maxw = max(maxw, popupcells(dc->kouho[i].r, dc->kouho[i].n));
|
|
||||||
vals[3] = h = (n + npre) * Fontsz;
|
|
||||||
vals[2] = w = (maxw + 3) * Fontsz;
|
|
||||||
px = py = 0;
|
|
||||||
if(!dc->caret.valid){
|
|
||||||
cookie = xcb_query_pointer(conn, scr->root);
|
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);
|
||||||
}
|
}
|
||||||
popupposition(&dc->caret, px, py, scr->width_in_pixels,
|
ax = dc->caret.valid ? dc->caret.x : ptrx;
|
||||||
scr->height_in_pixels, w, h, &px, &py);
|
ay = dc->caret.valid ? dc->caret.y : ptry;
|
||||||
vals[1] = py;
|
popupwork(ax, ay, &area);
|
||||||
vals[0] = px;
|
popuplayout(dc, area.w, area.h, &p);
|
||||||
|
if(p.w <= 0 || p.h <= 0){
|
||||||
|
winhide();
|
||||||
|
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. */
|
||||||
|
vals[0] = x;
|
||||||
|
vals[1] = y;
|
||||||
|
vals[2] = p.w;
|
||||||
|
vals[3] = p.h;
|
||||||
|
vals[4] = XCB_STACK_MODE_ABOVE;
|
||||||
xcb_configure_window(conn, win,
|
xcb_configure_window(conn, win,
|
||||||
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
||||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
|
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
|
||||||
vals);
|
XCB_CONFIG_WINDOW_STACK_MODE, vals);
|
||||||
|
popupdraw(img, dc, &p);
|
||||||
|
putimage(p.w, p.h);
|
||||||
xcb_map_window(conn, win);
|
xcb_map_window(conn, win);
|
||||||
drawkouho(dc, n, w, h);
|
xcb_flush(conn);
|
||||||
putimage(w, h);
|
shown = 1;
|
||||||
return xcb_flush(conn) > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -257,14 +365,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))
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
winshow(&dc);
|
||||||
;
|
|
||||||
if(dc.nkouho == 0 && dc.pre.n == 0){
|
|
||||||
if(!winhide())
|
|
||||||
break;
|
|
||||||
}else if(!winshow(&dc))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
wincleanup();
|
wincleanup();
|
||||||
}
|
}
|
||||||
|
|||||||
798
wl.c
Normal file
798
wl.c
Normal file
@@ -0,0 +1,798 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
#include "imv2.h"
|
||||||
|
#include "vkv1.h"
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Maxcode = 0x300, /* KEY_MAX+1: what evdev can send */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* What popuplayout may use. The compositor puts the popup at the text
|
||||||
|
* cursor and constrains it there, so this need only be wide enough for
|
||||||
|
* the layout's own width and tall enough for every row it can show.
|
||||||
|
*/
|
||||||
|
#define Popupw PopupBasew
|
||||||
|
#define Popuph ((Maxdisp + 2)*Fontsz + 2*PopupPad + PopupSep)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One of two shm buffers, busy from attach until the compositor releases
|
||||||
|
* it. win.c's single grow-only image would be redrawn while the
|
||||||
|
* compositor was still reading it.
|
||||||
|
*/
|
||||||
|
typedef struct Buf Buf;
|
||||||
|
struct Buf
|
||||||
|
{
|
||||||
|
struct wl_buffer *b;
|
||||||
|
u32int *img;
|
||||||
|
int size;
|
||||||
|
int w;
|
||||||
|
int h;
|
||||||
|
int busy;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct wl_display *display;
|
||||||
|
static struct wl_seat *seat;
|
||||||
|
static struct wl_compositor *comp;
|
||||||
|
static struct wl_shm *shm;
|
||||||
|
static struct wl_surface *surface;
|
||||||
|
static struct zwp_input_popup_surface_v2 *popsurf;
|
||||||
|
static struct zwp_input_method_manager_v2 *immgr;
|
||||||
|
static struct zwp_virtual_keyboard_manager_v1 *vkmgr;
|
||||||
|
static struct zwp_input_method_v2 *im;
|
||||||
|
static struct zwp_virtual_keyboard_v1 *vk;
|
||||||
|
static struct zwp_input_method_keyboard_grab_v2 *grab;
|
||||||
|
static struct xkb_context *xkbctx;
|
||||||
|
static struct xkb_state *kstate;
|
||||||
|
static Channel *replyc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The protocol gives one input context per seat, so the engine knows us
|
||||||
|
* by one address; nil would mean no owner at all.
|
||||||
|
*/
|
||||||
|
static int context;
|
||||||
|
|
||||||
|
static u32int serial; /* done events counted; the serial commit takes */
|
||||||
|
static int active; /* a text input is being served */
|
||||||
|
static int pendingactive;
|
||||||
|
static int activated; /* an activate arrived since the last done */
|
||||||
|
static u32int purpose, pendingpurpose;
|
||||||
|
static Str surround, pendingsurround; /* the client's text before the cursor */
|
||||||
|
static u32int sent[Maxcode/32]; /* keycodes passed on and still down */
|
||||||
|
static u32int lasttime; /* the compositor's clock, for a release of our own */
|
||||||
|
static int reprate; /* repeats a second the seat asks for; 0 is none */
|
||||||
|
static int repdelay; /* ms a key is held before the first repeat */
|
||||||
|
static u32int repcode; /* the key repeating */
|
||||||
|
static vlong repdue; /* when its next repeat falls due; 0 if none */
|
||||||
|
static int engaged; /* the engine owes this context a release */
|
||||||
|
static int preshown; /* our preedit is on the client's screen */
|
||||||
|
static int haskeymap;
|
||||||
|
static int gone; /* unavailable: the seat is another input method's */
|
||||||
|
static Buf bufs[2];
|
||||||
|
static int shown; /* a buffer is attached and the popup is up */
|
||||||
|
static Drawcmd held; /* a picture that came while both buffers were busy */
|
||||||
|
static int helddraw;
|
||||||
|
|
||||||
|
static void grabkeyboard(void);
|
||||||
|
static void popup(Drawcmd*);
|
||||||
|
|
||||||
|
static void
|
||||||
|
wllog(char *msg)
|
||||||
|
{
|
||||||
|
fprint(2, "strans: wl: %s\n", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A repeat deadline needs a clock that cannot step; nsec() is not one. */
|
||||||
|
static vlong
|
||||||
|
nowms(void)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return (vlong)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
hidden(void)
|
||||||
|
{
|
||||||
|
return purpose == Purposepassword || purpose == Purposepin;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sendrequest(int op, u32int ks, u32int mod, Keyres *res)
|
||||||
|
{
|
||||||
|
Keyreq kr;
|
||||||
|
|
||||||
|
memset(&kr, 0, sizeof kr);
|
||||||
|
kr.owner = &context;
|
||||||
|
kr.clientpre = 1; /* set_preedit_string draws it; the popup does not */
|
||||||
|
kr.op = op;
|
||||||
|
kr.ks = ks;
|
||||||
|
kr.mod = mod;
|
||||||
|
kr.surround = surround;
|
||||||
|
kr.reply = replyc;
|
||||||
|
chansend(keyc, &kr);
|
||||||
|
chanrecv(replyc, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The bytes the last n runes of the client's text take. */
|
||||||
|
static int
|
||||||
|
backbytes(int n)
|
||||||
|
{
|
||||||
|
int i, nb;
|
||||||
|
|
||||||
|
nb = 0;
|
||||||
|
for(i = max(surround.n - n, 0); i < surround.n; i++)
|
||||||
|
nb += runelen(surround.r[i]);
|
||||||
|
return nb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The engine's modifier bits, read from the grab's own keyboard state. */
|
||||||
|
static u32int
|
||||||
|
modmask(void)
|
||||||
|
{
|
||||||
|
static char *name[] = {
|
||||||
|
XKB_MOD_NAME_SHIFT, XKB_MOD_NAME_CTRL,
|
||||||
|
XKB_MOD_NAME_ALT, XKB_MOD_NAME_LOGO,
|
||||||
|
};
|
||||||
|
static u32int bit[] = {Mshift, Mctrl, Malt, Msuper};
|
||||||
|
u32int m;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
m = 0;
|
||||||
|
for(i = 0; i < nelem(name); i++)
|
||||||
|
if(xkb_state_mod_name_is_active(kstate, name[i],
|
||||||
|
XKB_STATE_MODS_EFFECTIVE) > 0)
|
||||||
|
m |= bit[i];
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
issent(u32int code)
|
||||||
|
{
|
||||||
|
return code < Maxcode && (sent[code/32] & 1<<(code%32)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A key the engine did not take goes to the client as a key, not as
|
||||||
|
* text: eaten = 0 means it is not ours, and committing it would lie to a
|
||||||
|
* terminal and to anything with a keybinding.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
forward(u32int code, u32int state)
|
||||||
|
{
|
||||||
|
if(!haskeymap || code >= Maxcode)
|
||||||
|
return;
|
||||||
|
zwp_virtual_keyboard_v1_key(vk, lasttime, code, state);
|
||||||
|
if(state == WL_KEYBOARD_KEY_STATE_PRESSED)
|
||||||
|
sent[code/32] |= 1<<(code%32);
|
||||||
|
else
|
||||||
|
sent[code/32] &= ~(1<<(code%32));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Whatever we passed on is still down; the client must not keep it. */
|
||||||
|
static void
|
||||||
|
releasekeys(void)
|
||||||
|
{
|
||||||
|
u32int code;
|
||||||
|
|
||||||
|
for(code = 0; code < Maxcode; code++)
|
||||||
|
if(issent(code))
|
||||||
|
forward(code, WL_KEYBOARD_KEY_STATE_RELEASED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The engine's text, then the composed text, in one commit_string: the
|
||||||
|
* compositor keeps only the last one sent before a commit. A commit is
|
||||||
|
* an event and goes only when there is something, while a preedit is
|
||||||
|
* state and goes every time, empty to take the last one down.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
answer(Keyres *res, char *tail)
|
||||||
|
{
|
||||||
|
char utf[2*Maxutf];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
/* The compositor takes the text back before it inserts ours. */
|
||||||
|
if(res->del > 0)
|
||||||
|
zwp_input_method_v2_delete_surrounding_text(im,
|
||||||
|
backbytes(res->del), 0);
|
||||||
|
n = stoutf(&res->commit, utf, sizeof utf);
|
||||||
|
if(tail[0] != '\0')
|
||||||
|
n += snprint(utf+n, sizeof utf - n, "%s", tail);
|
||||||
|
if(n > 0)
|
||||||
|
zwp_input_method_v2_commit_string(im, utf);
|
||||||
|
n = stoutf(&res->preedit, utf, sizeof utf);
|
||||||
|
if(n > 0 || preshown) /* an empty one only to take the last down */
|
||||||
|
zwp_input_method_v2_set_preedit_string(im, utf, n, n);
|
||||||
|
zwp_input_method_v2_commit(im, serial);
|
||||||
|
preshown = n > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
bufrelease(void *p, struct wl_buffer*)
|
||||||
|
{
|
||||||
|
((Buf*)p)->busy = 0;
|
||||||
|
if(helddraw){
|
||||||
|
helddraw = 0;
|
||||||
|
popup(&held);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_buffer_listener buflisten = {bufrelease};
|
||||||
|
|
||||||
|
static void
|
||||||
|
bufclear(Buf *p)
|
||||||
|
{
|
||||||
|
if(p->b != nil)
|
||||||
|
wl_buffer_destroy(p->b);
|
||||||
|
if(p->img != nil)
|
||||||
|
munmap(p->img, p->size);
|
||||||
|
memset(p, 0, sizeof *p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* An anonymous file of pixels, shared with the compositor. */
|
||||||
|
static int
|
||||||
|
shmfd(int size)
|
||||||
|
{
|
||||||
|
char path[] = "/dev/shm/strans-XXXXXX";
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = mkstemp(path);
|
||||||
|
if(fd < 0)
|
||||||
|
return -1;
|
||||||
|
unlink(path);
|
||||||
|
if(ftruncate(fd, size) < 0){
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A free buffer of the wanted size. Sizes differ from draw to draw, so
|
||||||
|
* a free one of the wrong size is remade; both busy means the compositor
|
||||||
|
* is still reading them and the picture has to wait.
|
||||||
|
*/
|
||||||
|
static Buf*
|
||||||
|
getbuf(int w, int h)
|
||||||
|
{
|
||||||
|
struct wl_shm_pool *pool;
|
||||||
|
Buf *p;
|
||||||
|
int fd, i, size;
|
||||||
|
|
||||||
|
for(i = 0; i < nelem(bufs); i++)
|
||||||
|
if(!bufs[i].busy && bufs[i].w == w && bufs[i].h == h)
|
||||||
|
return &bufs[i];
|
||||||
|
for(i = 0; i < nelem(bufs); i++)
|
||||||
|
if(!bufs[i].busy)
|
||||||
|
break;
|
||||||
|
if(i == nelem(bufs))
|
||||||
|
return nil;
|
||||||
|
p = &bufs[i];
|
||||||
|
bufclear(p);
|
||||||
|
size = w * h * sizeof p->img[0];
|
||||||
|
fd = shmfd(size);
|
||||||
|
if(fd < 0)
|
||||||
|
return nil;
|
||||||
|
p->img = mmap(nil, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
|
||||||
|
if(p->img == MAP_FAILED){
|
||||||
|
p->img = nil;
|
||||||
|
close(fd);
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
pool = wl_shm_create_pool(shm, fd, size);
|
||||||
|
p->b = wl_shm_pool_create_buffer(pool, 0, w, h,
|
||||||
|
w * sizeof p->img[0], WL_SHM_FORMAT_XRGB8888);
|
||||||
|
wl_shm_pool_destroy(pool);
|
||||||
|
close(fd);
|
||||||
|
wl_buffer_add_listener(p->b, &buflisten, p);
|
||||||
|
p->size = size;
|
||||||
|
p->w = w;
|
||||||
|
p->h = h;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unmapping takes the popup down; the compositor puts it back at the
|
||||||
|
* next activate with whatever buffer is still attached. */
|
||||||
|
static void
|
||||||
|
hidepopup(void)
|
||||||
|
{
|
||||||
|
helddraw = 0; /* a picture still waiting for a buffer is stale now */
|
||||||
|
if(!shown)
|
||||||
|
return;
|
||||||
|
wl_surface_attach(surface, nil, 0, 0);
|
||||||
|
wl_surface_commit(surface);
|
||||||
|
shown = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
popup(Drawcmd *dc)
|
||||||
|
{
|
||||||
|
Popup p;
|
||||||
|
Buf *b;
|
||||||
|
|
||||||
|
popuplayout(dc, Popupw, Popuph, &p);
|
||||||
|
if(p.w <= 0 || p.h <= 0){
|
||||||
|
hidepopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* A buffer that is not a multiple of the scale is an invalid_size
|
||||||
|
* error at attach, which would kill the connection. */
|
||||||
|
p.w = (p.w + popupscale - 1) / popupscale * popupscale;
|
||||||
|
p.h = (p.h + popupscale - 1) / popupscale * popupscale;
|
||||||
|
b = getbuf(p.w, p.h);
|
||||||
|
if(b == nil){
|
||||||
|
held = *dc; /* the engine will not send it twice */
|
||||||
|
helddraw = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
popupdraw(b->img, dc, &p);
|
||||||
|
wl_surface_attach(surface, b->b, 0, 0);
|
||||||
|
wl_surface_set_buffer_scale(surface, popupscale);
|
||||||
|
wl_surface_damage_buffer(surface, 0, 0, p.w, p.h);
|
||||||
|
wl_surface_commit(surface);
|
||||||
|
b->busy = 1;
|
||||||
|
shown = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* drawc is empty or holds exactly what the engine last published, so one
|
||||||
|
* take is enough. lastdraw follows the engine and not our surface, so
|
||||||
|
* take it wherever the two can part -- but show it only while the engine
|
||||||
|
* is ours: what it drew for another frontend is not ours to put up.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
takedraw(void)
|
||||||
|
{
|
||||||
|
Drawcmd dc;
|
||||||
|
|
||||||
|
if(channbrecv(drawc, &dc) > 0 && engaged)
|
||||||
|
popup(&dc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The compositor places the popup; where the text is does not concern us. */
|
||||||
|
static void
|
||||||
|
poprect(void*, struct zwp_input_popup_surface_v2*, int, int, int, int)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zwp_input_popup_surface_v2_listener poplisten = {poprect};
|
||||||
|
|
||||||
|
static void
|
||||||
|
releasegrab(void)
|
||||||
|
{
|
||||||
|
if(grab == nil)
|
||||||
|
return;
|
||||||
|
zwp_input_method_keyboard_grab_v2_release(grab);
|
||||||
|
grab = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The context is going: give the client its keys back. The pending text
|
||||||
|
* goes nowhere either way -- the compositor drops a commit once the text
|
||||||
|
* input has its leave, and a client that disabled its own text input has
|
||||||
|
* stopped listening, so it throws a relayed commit away. XIM and IBus
|
||||||
|
* hand the text back here because their focus-out is a round trip the
|
||||||
|
* client still waits on; a deactivate is not.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
leave(void)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
|
||||||
|
repdue = 0;
|
||||||
|
releasekeys();
|
||||||
|
composedrop(Composewl, &context);
|
||||||
|
if(engaged){
|
||||||
|
sendrequest(Keyrelease, 0, 0, &res);
|
||||||
|
engaged = 0;
|
||||||
|
takedraw();
|
||||||
|
}
|
||||||
|
hidepopup();
|
||||||
|
preshown = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A purpose that turned secret with text pending gets the text first. */
|
||||||
|
static void
|
||||||
|
flushpending(void)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
|
||||||
|
if(!engaged)
|
||||||
|
return;
|
||||||
|
sendrequest(Keyreset, 0, 0, &res);
|
||||||
|
answer(&res, "");
|
||||||
|
takedraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A purpose belongs to the activation: the compositor sends a content type
|
||||||
|
* only for a client that asked for one, so a client that asks for none has
|
||||||
|
* none, whatever the last one wanted.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
imactivate(void*, struct zwp_input_method_v2*)
|
||||||
|
{
|
||||||
|
pendingactive = 1;
|
||||||
|
activated = 1;
|
||||||
|
pendingpurpose = 0;
|
||||||
|
sclear(&pendingsurround);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
imdeactivate(void*, struct zwp_input_method_v2*)
|
||||||
|
{
|
||||||
|
pendingactive = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The text around the cursor, of which a reading can use what comes
|
||||||
|
* before it. A text input that sends none leaves this empty, and then
|
||||||
|
* nothing is ever reached back into or taken away.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
imsurrounding(void*, struct zwp_input_method_v2*, const char *text,
|
||||||
|
u32int cursor, u32int)
|
||||||
|
{
|
||||||
|
u32int n;
|
||||||
|
|
||||||
|
n = strlen(text);
|
||||||
|
stail(&pendingsurround, (char*)text, cursor < n ? cursor : n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
imcause(void*, struct zwp_input_method_v2*, u32int)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
imcontent(void*, struct zwp_input_method_v2*, u32int, u32int what)
|
||||||
|
{
|
||||||
|
pendingpurpose = what;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* activate, deactivate and content_type are pending until done, and the
|
||||||
|
* count of done events is the serial a commit must carry: wlroots throws
|
||||||
|
* away a whole commit whose serial does not match, without a word.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
imdone(void*, struct zwp_input_method_v2*)
|
||||||
|
{
|
||||||
|
int washidden;
|
||||||
|
|
||||||
|
serial++;
|
||||||
|
washidden = hidden();
|
||||||
|
purpose = pendingpurpose;
|
||||||
|
surround = pendingsurround;
|
||||||
|
if(activated || (active && !pendingactive))
|
||||||
|
leave();
|
||||||
|
else if(active && !washidden && hidden())
|
||||||
|
flushpending();
|
||||||
|
if(activated && pendingactive)
|
||||||
|
grabkeyboard();
|
||||||
|
else if(!pendingactive)
|
||||||
|
releasegrab();
|
||||||
|
active = pendingactive;
|
||||||
|
activated = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
imgone(void*, struct zwp_input_method_v2*)
|
||||||
|
{
|
||||||
|
gone = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zwp_input_method_v2_listener imlisten = {
|
||||||
|
imactivate, imdeactivate, imsurrounding, imcause, imcontent, imdone,
|
||||||
|
imgone,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The same fd goes to the virtual keyboard, so a key we pass on means
|
||||||
|
* there what it meant here.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
grabkeymap(void*, struct zwp_input_method_keyboard_grab_v2*, u32int format,
|
||||||
|
int fd, u32int size)
|
||||||
|
{
|
||||||
|
struct xkb_keymap *keymap;
|
||||||
|
struct xkb_state *state;
|
||||||
|
char *s;
|
||||||
|
|
||||||
|
if(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1){
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s = mmap(nil, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||||
|
if(s == MAP_FAILED){
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
keymap = xkb_keymap_new_from_string(xkbctx, s,
|
||||||
|
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||||
|
munmap(s, size);
|
||||||
|
state = keymap != nil ? xkb_state_new(keymap) : nil;
|
||||||
|
xkb_keymap_unref(keymap);
|
||||||
|
if(state == nil){
|
||||||
|
wllog("cannot read the keyboard mapping");
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xkb_state_unref(kstate);
|
||||||
|
kstate = state;
|
||||||
|
zwp_virtual_keyboard_v1_keymap(vk, format, fd, size);
|
||||||
|
close(fd);
|
||||||
|
haskeymap = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A press, from the keyboard or from the repeat deadline. Returns whether
|
||||||
|
* the engine took it: what it did not take has gone on to the client as a
|
||||||
|
* key, and a key at the client is the client's own to repeat.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
presskey(u32int code)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
char text[Maxutf];
|
||||||
|
u32int key, sym;
|
||||||
|
|
||||||
|
sym = xkb_state_key_get_one_sym(kstate, code + 8);
|
||||||
|
if(composekey(Composewl, &context, sym, text, sizeof text))
|
||||||
|
return 0; /* a sequence in the making */
|
||||||
|
key = ipckeysym(sym, xkb_keysym_to_utf32(sym));
|
||||||
|
if(keymeaningful(key))
|
||||||
|
engaged = 1;
|
||||||
|
if(text[0] != '\0')
|
||||||
|
/* Composed text follows whatever was pending. */
|
||||||
|
sendrequest(Keyreset, 0, 0, &res);
|
||||||
|
else
|
||||||
|
sendrequest(Keypress, key, modmask(), &res);
|
||||||
|
answer(&res, text);
|
||||||
|
takedraw();
|
||||||
|
if(text[0] == '\0' && !res.eaten)
|
||||||
|
forward(code, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
/* A finished sequence is one act; only a key is held down. */
|
||||||
|
return text[0] == '\0' && res.eaten;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
grabkey(void*, struct zwp_input_method_keyboard_grab_v2*, u32int,
|
||||||
|
u32int time, u32int code, u32int state)
|
||||||
|
{
|
||||||
|
lasttime = time;
|
||||||
|
if(state != WL_KEYBOARD_KEY_STATE_PRESSED){
|
||||||
|
if(code == repcode)
|
||||||
|
repdue = 0;
|
||||||
|
if(issent(code))
|
||||||
|
forward(code, state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
repdue = 0; /* one key repeats at a time, and this is the newest */
|
||||||
|
if(!active || hidden() || kstate == nil){
|
||||||
|
forward(code, state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!presskey(code) || reprate <= 0 ||
|
||||||
|
!xkb_keymap_key_repeats(xkb_state_get_keymap(kstate), code + 8))
|
||||||
|
return;
|
||||||
|
repcode = code;
|
||||||
|
repdue = nowms() + repdelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Our own lookup must see Shift, and so must the client: wlroots derives
|
||||||
|
* no state from a virtual keyboard's keys, so a modifier forwarded as a
|
||||||
|
* keycode arrives bare and Ctrl+C reaches the client as c.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
grabmodifiers(void*, struct zwp_input_method_keyboard_grab_v2*, u32int,
|
||||||
|
u32int depressed, u32int latched, u32int locked, u32int group)
|
||||||
|
{
|
||||||
|
if(kstate != nil)
|
||||||
|
xkb_state_update_mask(kstate, depressed, latched, locked,
|
||||||
|
0, 0, group);
|
||||||
|
if(haskeymap)
|
||||||
|
zwp_virtual_keyboard_v1_modifiers(vk, depressed, latched,
|
||||||
|
locked, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
grabrepeat(void*, struct zwp_input_method_keyboard_grab_v2*, int rate,
|
||||||
|
int delay)
|
||||||
|
{
|
||||||
|
reprate = rate;
|
||||||
|
repdelay = delay;
|
||||||
|
if(rate <= 0)
|
||||||
|
repdue = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zwp_input_method_keyboard_grab_v2_listener grablisten = {
|
||||||
|
grabkeymap, grabkey, grabmodifiers, grabrepeat,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sway hands keys to the grab holder whenever the grab object exists,
|
||||||
|
* without asking whether the input method is active, so a grab held
|
||||||
|
* while inactive takes every key in the session. Two activates can also
|
||||||
|
* arrive without a deactivate between them, and a second grab_keyboard
|
||||||
|
* then fails and leaves a dead object: release before grabbing again.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
grabkeyboard(void)
|
||||||
|
{
|
||||||
|
releasegrab();
|
||||||
|
grab = zwp_input_method_v2_grab_keyboard(im);
|
||||||
|
zwp_input_method_keyboard_grab_v2_add_listener(grab, &grablisten, nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
regglobal(void*, struct wl_registry *r, u32int name, const char *iface, u32int)
|
||||||
|
{
|
||||||
|
if(strcmp(iface, wl_seat_interface.name) == 0){
|
||||||
|
if(seat == nil) /* the first seat is ours */
|
||||||
|
seat = wl_registry_bind(r, name, &wl_seat_interface, 1);
|
||||||
|
}else if(strcmp(iface, wl_compositor_interface.name) == 0)
|
||||||
|
/* 4 for set_buffer_scale and damage_buffer */
|
||||||
|
comp = wl_registry_bind(r, name, &wl_compositor_interface, 4);
|
||||||
|
else if(strcmp(iface, wl_shm_interface.name) == 0)
|
||||||
|
shm = wl_registry_bind(r, name, &wl_shm_interface, 1);
|
||||||
|
else if(strcmp(iface, zwp_input_method_manager_v2_interface.name) == 0)
|
||||||
|
immgr = wl_registry_bind(r, name,
|
||||||
|
&zwp_input_method_manager_v2_interface, 1);
|
||||||
|
else if(strcmp(iface, zwp_virtual_keyboard_manager_v1_interface.name) == 0)
|
||||||
|
vkmgr = wl_registry_bind(r, name,
|
||||||
|
&zwp_virtual_keyboard_manager_v1_interface, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
regremove(void*, struct wl_registry*, u32int)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_registry_listener reglisten = {regglobal, regremove};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Another frontend may have taken the engine; then our preedit and our
|
||||||
|
* popup are both stale. Nothing later will say so: the engine's own
|
||||||
|
* pictures go to whoever took it, and it settles back at blank.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
checkowner(void)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
|
||||||
|
if(!preshown && !shown)
|
||||||
|
return;
|
||||||
|
sendrequest(Keycap, 0, 0, &res);
|
||||||
|
if(res.eaten)
|
||||||
|
return;
|
||||||
|
hidepopup();
|
||||||
|
if(preshown){
|
||||||
|
zwp_input_method_v2_set_preedit_string(im, "", 0, 0);
|
||||||
|
zwp_input_method_v2_commit(im, serial);
|
||||||
|
preshown = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One repeat, once the key has been held long enough. Under the grab the
|
||||||
|
* compositor feeds the client nothing while the key is down, so what it
|
||||||
|
* would have repeated there is ours to make -- but only while the engine
|
||||||
|
* wants the key. Backspace stops being ours once the preedit is empty,
|
||||||
|
* and by then presskey has passed it on, so the key is held down at the
|
||||||
|
* client and the client repeats it from there.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
repeat(void)
|
||||||
|
{
|
||||||
|
if(nowms() < repdue)
|
||||||
|
return;
|
||||||
|
if(!active || hidden() || kstate == nil || !presskey(repcode)){
|
||||||
|
repdue = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
repdue = nowms() + 1000/reprate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Whether this session has the protocol, not whether it is Wayland:
|
||||||
|
* GNOME and KWin set WAYLAND_DISPLAY and have no manager, and XIM is the
|
||||||
|
* only path left to them.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
wlinit(void)
|
||||||
|
{
|
||||||
|
struct wl_registry *reg;
|
||||||
|
|
||||||
|
display = wl_display_connect(nil);
|
||||||
|
if(display == nil)
|
||||||
|
return 0;
|
||||||
|
reg = wl_display_get_registry(display);
|
||||||
|
wl_registry_add_listener(reg, ®listen, nil);
|
||||||
|
wl_display_roundtrip(display);
|
||||||
|
/* Without the virtual keyboard every key we do not eat is lost. */
|
||||||
|
if(seat != nil && immgr != nil && vkmgr != nil && comp != nil &&
|
||||||
|
shm != nil)
|
||||||
|
return 1;
|
||||||
|
wl_display_disconnect(display);
|
||||||
|
display = nil;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One process touches libwayland: it reads events, dispatches them and
|
||||||
|
* answers the engine, in order. dispatch reads only when poll says
|
||||||
|
* there is something, so it never blocks and the owner check still runs.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
wlthread(void*)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
int ms, n;
|
||||||
|
|
||||||
|
threadsetname("wl");
|
||||||
|
replyc = chancreate(sizeof(Keyres), 0);
|
||||||
|
xkbctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||||
|
if(xkbctx == nil){
|
||||||
|
wllog("cannot make an xkb context");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
textinit();
|
||||||
|
im = zwp_input_method_manager_v2_get_input_method(immgr, seat);
|
||||||
|
zwp_input_method_v2_add_listener(im, &imlisten, nil);
|
||||||
|
vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(vkmgr, seat);
|
||||||
|
surface = wl_compositor_create_surface(comp);
|
||||||
|
popsurf = zwp_input_method_v2_get_input_popup_surface(im, surface);
|
||||||
|
zwp_input_popup_surface_v2_add_listener(popsurf, &poplisten, nil);
|
||||||
|
pfd.fd = wl_display_get_fd(display);
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
for(;;){
|
||||||
|
if(wl_display_flush(display) < 0 && errno != EAGAIN)
|
||||||
|
break;
|
||||||
|
pfd.revents = 0;
|
||||||
|
if(repdue != 0){
|
||||||
|
ms = repdue - nowms();
|
||||||
|
if(ms < 0)
|
||||||
|
ms = 0;
|
||||||
|
}else
|
||||||
|
ms = preshown || shown ? Ownerpoll : -1;
|
||||||
|
n = poll(&pfd, 1, ms);
|
||||||
|
if(n < 0 && errno != EINTR)
|
||||||
|
break;
|
||||||
|
if(pfd.revents & POLLIN){
|
||||||
|
if(wl_display_dispatch(display) < 0)
|
||||||
|
break;
|
||||||
|
}else if(wl_display_dispatch_pending(display) < 0)
|
||||||
|
break;
|
||||||
|
if(gone){
|
||||||
|
wllog("another input method already has the seat");
|
||||||
|
releasegrab();
|
||||||
|
leave();
|
||||||
|
wl_display_flush(display);
|
||||||
|
bufclear(&bufs[0]);
|
||||||
|
bufclear(&bufs[1]);
|
||||||
|
textclose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* A repeating key's own press keeps the engine ours, so
|
||||||
|
* while one repeats the owner poll has nothing to find. */
|
||||||
|
if(repdue != 0)
|
||||||
|
repeat();
|
||||||
|
else
|
||||||
|
checkowner();
|
||||||
|
}
|
||||||
|
if(wl_display_get_error(display) != 0)
|
||||||
|
die("wl: compositor disconnected");
|
||||||
|
die("wl: frontend stopped");
|
||||||
|
}
|
||||||
578
xim.c
Normal file
578
xim.c
Normal file
@@ -0,0 +1,578 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <locale.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
#include <xcb/xcb_aux.h>
|
||||||
|
#include <xcb/xkb.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
#include <xkbcommon/xkbcommon-names.h>
|
||||||
|
#include <xkbcommon/xkbcommon-x11.h>
|
||||||
|
#include <xcb-imdkit/imdkit.h>
|
||||||
|
#include <xcb-imdkit/encoding.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One XIM input context. Only PreeditCallbacks clients draw the preedit
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
typedef struct Ic Ic;
|
||||||
|
struct Ic
|
||||||
|
{
|
||||||
|
Ic *next;
|
||||||
|
xcb_im_input_context_t *xic;
|
||||||
|
xcb_im_client_t *client;
|
||||||
|
int engaged;
|
||||||
|
int clientpre;
|
||||||
|
int prestarted;
|
||||||
|
int nprerune;
|
||||||
|
Caret caret;
|
||||||
|
};
|
||||||
|
|
||||||
|
static xcb_connection_t *conn;
|
||||||
|
static xcb_im_t *xim;
|
||||||
|
static struct xkb_state *kstate;
|
||||||
|
static uint8_t xkbevent;
|
||||||
|
static xcb_window_t rootwin;
|
||||||
|
static Ic *ics;
|
||||||
|
static Ic *preowner;
|
||||||
|
static Channel *replyc;
|
||||||
|
|
||||||
|
static char *encs[] = {"COMPOUND_TEXT"};
|
||||||
|
static u32int styles[] = {
|
||||||
|
XCB_IM_PreeditPosition | XCB_IM_StatusNothing,
|
||||||
|
XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing,
|
||||||
|
XCB_IM_PreeditNothing | XCB_IM_StatusNothing,
|
||||||
|
XCB_IM_PreeditPosition | XCB_IM_StatusNone,
|
||||||
|
XCB_IM_PreeditCallbacks | XCB_IM_StatusNone,
|
||||||
|
XCB_IM_PreeditNothing | XCB_IM_StatusNone,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
ximlog(char *msg)
|
||||||
|
{
|
||||||
|
fprint(2, "strans: xim: %s\n", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reads the server's keymap; XKB-aware clients get XKB events, not MappingNotify. */
|
||||||
|
static int
|
||||||
|
kinit(void)
|
||||||
|
{
|
||||||
|
struct xkb_context *context;
|
||||||
|
struct xkb_keymap *keymap;
|
||||||
|
struct xkb_state *state;
|
||||||
|
int32_t device;
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
keymap = nil;
|
||||||
|
state = nil;
|
||||||
|
ok = -1;
|
||||||
|
context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||||
|
if(context == nil || !xkb_x11_setup_xkb_extension(conn,
|
||||||
|
XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION,
|
||||||
|
XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, nil, nil, &xkbevent, nil))
|
||||||
|
goto out;
|
||||||
|
device = xkb_x11_get_core_keyboard_device_id(conn);
|
||||||
|
if(device < 0)
|
||||||
|
goto out;
|
||||||
|
keymap = xkb_x11_keymap_new_from_device(context, conn, device,
|
||||||
|
XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||||
|
if(keymap == nil)
|
||||||
|
goto out;
|
||||||
|
state = xkb_state_new(keymap);
|
||||||
|
if(state == nil)
|
||||||
|
goto out;
|
||||||
|
xkb_state_unref(kstate);
|
||||||
|
kstate = state;
|
||||||
|
state = nil;
|
||||||
|
ok = 0;
|
||||||
|
out:
|
||||||
|
xkb_state_unref(state);
|
||||||
|
xkb_keymap_unref(keymap);
|
||||||
|
xkb_context_unref(context);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
kwatch(void)
|
||||||
|
{
|
||||||
|
u16int events, parts;
|
||||||
|
|
||||||
|
events = XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
|
||||||
|
XCB_XKB_EVENT_TYPE_MAP_NOTIFY;
|
||||||
|
parts = XCB_XKB_MAP_PART_KEY_TYPES | XCB_XKB_MAP_PART_KEY_SYMS |
|
||||||
|
XCB_XKB_MAP_PART_MODIFIER_MAP |
|
||||||
|
XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
|
||||||
|
XCB_XKB_MAP_PART_KEY_ACTIONS | XCB_XKB_MAP_PART_VIRTUAL_MODS |
|
||||||
|
XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP;
|
||||||
|
xcb_xkb_select_events(conn, XCB_XKB_ID_USE_CORE_KBD, events, 0,
|
||||||
|
events, parts, parts, nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
translate(xcb_window_t win, int x, int y, Caret *caret)
|
||||||
|
{
|
||||||
|
xcb_translate_coordinates_cookie_t cookie;
|
||||||
|
xcb_translate_coordinates_reply_t *reply;
|
||||||
|
|
||||||
|
if(win == XCB_NONE)
|
||||||
|
return 0;
|
||||||
|
cookie = xcb_translate_coordinates(conn, win, rootwin, x, y);
|
||||||
|
reply = xcb_translate_coordinates_reply(conn, cookie, nil);
|
||||||
|
if(reply == nil || !reply->same_screen){
|
||||||
|
free(reply);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
caret->valid = 1;
|
||||||
|
caret->x = reply->dst_x;
|
||||||
|
caret->y = reply->dst_y;
|
||||||
|
caret->h = 0;
|
||||||
|
free(reply);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
placebottom(Ic *state, xcb_window_t win)
|
||||||
|
{
|
||||||
|
xcb_get_geometry_cookie_t gcookie;
|
||||||
|
xcb_get_geometry_reply_t *geometry;
|
||||||
|
int height;
|
||||||
|
|
||||||
|
if(win == XCB_NONE)
|
||||||
|
return 0;
|
||||||
|
gcookie = xcb_get_geometry(conn, win);
|
||||||
|
geometry = xcb_get_geometry_reply(conn, gcookie, nil);
|
||||||
|
if(geometry == nil)
|
||||||
|
return 0;
|
||||||
|
height = geometry->height;
|
||||||
|
free(geometry);
|
||||||
|
if(!translate(win, 0, 0, &state->caret))
|
||||||
|
return 0;
|
||||||
|
state->caret.y += height;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The popup goes at the client's spot location when it sends one, else
|
||||||
|
* under the focus window, else under the client window. An unset focus
|
||||||
|
* window means the client window, as the XIM spec says.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
place(Ic *state)
|
||||||
|
{
|
||||||
|
const xcb_im_preedit_attr_t *attr;
|
||||||
|
xcb_window_t clientwin, focuswin;
|
||||||
|
|
||||||
|
memset(&state->caret, 0, sizeof state->caret);
|
||||||
|
clientwin = xcb_im_input_context_get_client_window(state->xic);
|
||||||
|
focuswin = xcb_im_input_context_get_focus_window(state->xic);
|
||||||
|
if(focuswin == XCB_NONE)
|
||||||
|
focuswin = clientwin;
|
||||||
|
if(xcb_im_input_context_get_preedit_attr_mask(state->xic) &
|
||||||
|
XCB_XIM_XNSpotLocation_MASK){
|
||||||
|
attr = xcb_im_input_context_get_preedit_attr(state->xic);
|
||||||
|
if(translate(focuswin, attr->spot_location.x,
|
||||||
|
attr->spot_location.y, &state->caret)){
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(placebottom(state, focuswin))
|
||||||
|
return;
|
||||||
|
if(clientwin != focuswin)
|
||||||
|
placebottom(state, clientwin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* XIM text goes out as COMPOUND_TEXT; imdkit wraps UTF-8 in ESC%G. */
|
||||||
|
static void
|
||||||
|
commit(Ic *state, char *s, int nbyte)
|
||||||
|
{
|
||||||
|
char *wire;
|
||||||
|
size_t nwire;
|
||||||
|
|
||||||
|
if(nbyte == 0)
|
||||||
|
return;
|
||||||
|
wire = xcb_utf8_to_compound_text(s, nbyte, &nwire);
|
||||||
|
if(wire == nil)
|
||||||
|
return;
|
||||||
|
xcb_im_commit_string(xim, state->xic, XCB_XIM_LOOKUP_CHARS,
|
||||||
|
wire, (u32int)nwire, 0);
|
||||||
|
free(wire);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clearpreedit(Ic *state)
|
||||||
|
{
|
||||||
|
xcb_im_preedit_draw_fr_t frame;
|
||||||
|
|
||||||
|
if(!state->prestarted)
|
||||||
|
return;
|
||||||
|
memset(&frame, 0, sizeof frame);
|
||||||
|
frame.chg_length = state->nprerune;
|
||||||
|
frame.status = 1;
|
||||||
|
xcb_im_preedit_draw_callback(xim, state->xic, &frame);
|
||||||
|
xcb_im_preedit_done_callback(xim, state->xic);
|
||||||
|
state->prestarted = 0;
|
||||||
|
state->nprerune = 0;
|
||||||
|
if(preowner == state)
|
||||||
|
preowner = nil;
|
||||||
|
xcb_flush(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
updatepreedit(Ic *state, Str *pre)
|
||||||
|
{
|
||||||
|
xcb_im_preedit_draw_fr_t frame;
|
||||||
|
u32int feedback[Maxrunes];
|
||||||
|
char utf[Maxutf], *wire;
|
||||||
|
size_t nwire;
|
||||||
|
int i, nbyte;
|
||||||
|
|
||||||
|
if(!state->clientpre)
|
||||||
|
return;
|
||||||
|
if(pre->n == 0){
|
||||||
|
clearpreedit(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nbyte = stoutf(pre, utf, sizeof utf);
|
||||||
|
wire = xcb_utf8_to_compound_text(utf, nbyte, &nwire);
|
||||||
|
if(wire == nil)
|
||||||
|
return;
|
||||||
|
for(i = 0; i < pre->n; i++)
|
||||||
|
feedback[i] = XCB_XIM_UNDERLINE;
|
||||||
|
if(!state->prestarted){
|
||||||
|
xcb_im_preedit_start_callback(xim, state->xic);
|
||||||
|
state->prestarted = 1;
|
||||||
|
}
|
||||||
|
memset(&frame, 0, sizeof frame);
|
||||||
|
frame.caret = pre->n;
|
||||||
|
frame.chg_length = state->nprerune;
|
||||||
|
frame.length_of_preedit_string = (u16int)nwire;
|
||||||
|
frame.preedit_string = (uchar*)wire;
|
||||||
|
frame.feedback_array.size = pre->n;
|
||||||
|
frame.feedback_array.items = feedback;
|
||||||
|
xcb_im_preedit_draw_callback(xim, state->xic, &frame);
|
||||||
|
state->nprerune = pre->n;
|
||||||
|
preowner = state;
|
||||||
|
free(wire);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sendrequest(Ic *state, int op, u32int key, u32int mod, Keyres *res)
|
||||||
|
{
|
||||||
|
Keyreq kr;
|
||||||
|
|
||||||
|
memset(&kr, 0, sizeof kr);
|
||||||
|
kr.owner = state;
|
||||||
|
kr.clientpre = state->clientpre;
|
||||||
|
kr.op = op;
|
||||||
|
kr.ks = key;
|
||||||
|
kr.mod = ipcmod(mod);
|
||||||
|
kr.caret = state->caret;
|
||||||
|
kr.reply = replyc;
|
||||||
|
chansend(keyc, &kr);
|
||||||
|
chanrecv(replyc, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Another frontend may have taken the engine; then our preedit is stale. */
|
||||||
|
static void
|
||||||
|
checkpreowner(void)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
|
||||||
|
if(preowner == nil)
|
||||||
|
return;
|
||||||
|
sendrequest(preowner, Keycap, 0, 0, &res);
|
||||||
|
if(!res.eaten)
|
||||||
|
clearpreedit(preowner);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
keypress(Ic *state, u32int key, u32int mod, Keyres *res)
|
||||||
|
{
|
||||||
|
if(keymeaningful(key)){
|
||||||
|
if(preowner != nil && preowner != state)
|
||||||
|
clearpreedit(preowner);
|
||||||
|
state->engaged = 1;
|
||||||
|
}
|
||||||
|
sendrequest(state, Keypress, key, mod, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Losing the engine commits the pending text; the release hands it back. */
|
||||||
|
static void
|
||||||
|
release(Ic *state)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
char buf[Maxutf];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if(!state->engaged){
|
||||||
|
clearpreedit(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendrequest(state, Keyrelease, 0, 0, &res);
|
||||||
|
state->engaged = 0;
|
||||||
|
n = stoutf(&res.commit, buf, sizeof buf);
|
||||||
|
commit(state, buf, n);
|
||||||
|
clearpreedit(state);
|
||||||
|
xcb_flush(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* XIM reset hands the pending text back to the client as committed text. */
|
||||||
|
static void
|
||||||
|
resetic(Ic *state, xcb_im_reset_ic_reply_fr_t *reply)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
char utf[Maxutf], *wire;
|
||||||
|
size_t nwire;
|
||||||
|
int nbyte;
|
||||||
|
|
||||||
|
if(!state->engaged){
|
||||||
|
clearpreedit(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendrequest(state, Keyreset, 0, 0, &res);
|
||||||
|
clearpreedit(state);
|
||||||
|
if(reply == nil || res.commit.n == 0)
|
||||||
|
return;
|
||||||
|
nbyte = stoutf(&res.commit, utf, sizeof utf);
|
||||||
|
wire = xcb_utf8_to_compound_text(utf, nbyte, &nwire);
|
||||||
|
if(wire == nil)
|
||||||
|
return;
|
||||||
|
reply->committed_string = (uchar*)wire;
|
||||||
|
reply->byte_length_of_committed_string = nwire;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
kpress(Ic *state, xcb_key_press_event_t *ev)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
char buf[Maxutf], text[Maxutf];
|
||||||
|
u32int key, sym;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
sym = keymaplookup(kstate, ev->detail, ev->state);
|
||||||
|
if(composekey(Composexim, state, sym, text, sizeof text))
|
||||||
|
return;
|
||||||
|
key = ipckeysym(sym, xkb_keysym_to_utf32(sym));
|
||||||
|
if(keymeaningful(key))
|
||||||
|
place(state);
|
||||||
|
if(text[0] != '\0')
|
||||||
|
/* Composed text follows whatever was pending. */
|
||||||
|
sendrequest(state, Keyreset, 0, 0, &res);
|
||||||
|
else
|
||||||
|
keypress(state, key, ev->state, &res);
|
||||||
|
n = stoutf(&res.commit, buf, sizeof buf);
|
||||||
|
commit(state, buf, n);
|
||||||
|
updatepreedit(state, &res.preedit);
|
||||||
|
if(text[0] != '\0')
|
||||||
|
commit(state, text, strlen(text));
|
||||||
|
else if(!res.eaten)
|
||||||
|
xcb_im_forward_event(xim, state->xic, ev);
|
||||||
|
xcb_flush(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
icunlink(Ic *state)
|
||||||
|
{
|
||||||
|
Ic **p;
|
||||||
|
|
||||||
|
for(p = &ics; *p != nil; p = &(*p)->next)
|
||||||
|
if(*p == state){
|
||||||
|
*p = state->next;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
icfree(void *p)
|
||||||
|
{
|
||||||
|
Ic *state;
|
||||||
|
|
||||||
|
state = p;
|
||||||
|
/* The owner remains valid until imthread acknowledges its release. */
|
||||||
|
release(state);
|
||||||
|
composedrop(Composexim, state);
|
||||||
|
icunlink(state);
|
||||||
|
free(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
iccreate(xcb_im_client_t *client, xcb_im_input_context_t *ic)
|
||||||
|
{
|
||||||
|
Ic *state;
|
||||||
|
|
||||||
|
state = emalloc(sizeof(Ic));
|
||||||
|
state->xic = ic;
|
||||||
|
state->client = client;
|
||||||
|
if(xcb_im_input_context_get_input_style(ic) & XCB_IM_PreeditCallbacks)
|
||||||
|
state->clientpre = 1;
|
||||||
|
state->next = ics;
|
||||||
|
ics = state;
|
||||||
|
xcb_im_input_context_set_data(ic, state, icfree);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
callback(xcb_im_t *im, xcb_im_client_t *client, xcb_im_input_context_t *ic,
|
||||||
|
const xcb_im_packet_header_fr_t *hdr, void *frame, void *arg, void *user)
|
||||||
|
{
|
||||||
|
xcb_key_press_event_t *ev;
|
||||||
|
Keyres res;
|
||||||
|
Ic *state;
|
||||||
|
|
||||||
|
USED(im);
|
||||||
|
USED(frame);
|
||||||
|
USED(user);
|
||||||
|
if(hdr->major_opcode == XCB_XIM_DISCONNECT ||
|
||||||
|
hdr->major_opcode == XCB_XIM_CLOSE){
|
||||||
|
for(state = ics; state != nil; state = state->next)
|
||||||
|
if(state->client == client)
|
||||||
|
release(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(hdr->major_opcode == XCB_XIM_CREATE_IC){
|
||||||
|
iccreate(client, ic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(ic == nil)
|
||||||
|
return;
|
||||||
|
state = xcb_im_input_context_get_data(ic);
|
||||||
|
switch(hdr->major_opcode){
|
||||||
|
case XCB_XIM_FORWARD_EVENT:
|
||||||
|
ev = arg;
|
||||||
|
if(ev != nil && (ev->response_type & ~0x80) == XCB_KEY_PRESS)
|
||||||
|
kpress(state, ev);
|
||||||
|
break;
|
||||||
|
case XCB_XIM_SET_IC_VALUES:
|
||||||
|
case XCB_XIM_SET_IC_FOCUS:
|
||||||
|
place(state);
|
||||||
|
if(state->engaged)
|
||||||
|
sendrequest(state, Keycaret, 0, 0, &res);
|
||||||
|
break;
|
||||||
|
case XCB_XIM_DESTROY_IC:
|
||||||
|
case XCB_XIM_UNSET_IC_FOCUS:
|
||||||
|
release(state);
|
||||||
|
break;
|
||||||
|
case XCB_XIM_RESET_IC:
|
||||||
|
resetic(state, arg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
ximinit(void)
|
||||||
|
{
|
||||||
|
xcb_screen_t *screen;
|
||||||
|
xcb_window_t win;
|
||||||
|
xcb_im_styles_t st;
|
||||||
|
xcb_im_encodings_t enc;
|
||||||
|
int scr;
|
||||||
|
|
||||||
|
replyc = chancreate(sizeof(Keyres), 0);
|
||||||
|
st.nStyles = nelem(styles);
|
||||||
|
st.styles = styles;
|
||||||
|
enc.nEncodings = nelem(encs);
|
||||||
|
enc.encodings = encs;
|
||||||
|
xcb_compound_text_init();
|
||||||
|
conn = xcb_connect(nil, &scr);
|
||||||
|
if(xcb_connection_has_error(conn)){
|
||||||
|
ximlog("cannot connect to X server");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
screen = xcb_aux_get_screen(conn, scr);
|
||||||
|
if(screen == nil){
|
||||||
|
ximlog("cannot find X screen");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
rootwin = screen->root;
|
||||||
|
if(kinit() < 0){
|
||||||
|
ximlog("cannot read keyboard mapping");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
kwatch();
|
||||||
|
win = xcb_generate_id(conn);
|
||||||
|
xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, screen->root,
|
||||||
|
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||||
|
screen->root_visual, 0, nil);
|
||||||
|
xim = xcb_im_create(conn, scr, win, "strans",
|
||||||
|
XCB_IM_ALL_LOCALES, &st, nil, nil, &enc,
|
||||||
|
XCB_EVENT_MASK_KEY_PRESS, callback, nil);
|
||||||
|
if(xim == nil){
|
||||||
|
ximlog("cannot create XIM server");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if(!xcb_im_open_im(xim)){
|
||||||
|
ximlog("cannot claim XIM selection");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ximthread(void *arg)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
xcb_generic_event_t *ev;
|
||||||
|
uint8_t type;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
USED(arg);
|
||||||
|
threadsetname("xim");
|
||||||
|
if(ximinit() < 0)
|
||||||
|
die("xim: initialization failed");
|
||||||
|
pfd.fd = xcb_get_file_descriptor(conn);
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
for(;;){
|
||||||
|
pfd.revents = 0;
|
||||||
|
n = poll(&pfd, 1, preowner != nil ? Ownerpoll : -1);
|
||||||
|
if(n < 0 && errno != EINTR)
|
||||||
|
break;
|
||||||
|
while((ev = xcb_poll_for_event(conn)) != nil){
|
||||||
|
type = ev->response_type & ~0x80;
|
||||||
|
if(type == xkbevent){
|
||||||
|
if(kinit() < 0)
|
||||||
|
ximlog("cannot refresh keyboard mapping");
|
||||||
|
}else
|
||||||
|
xcb_im_filter_event(xim, ev);
|
||||||
|
free(ev);
|
||||||
|
}
|
||||||
|
checkpreowner();
|
||||||
|
if(pfd.revents & (POLLERR|POLLHUP|POLLNVAL))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(xcb_connection_has_error(conn))
|
||||||
|
die("xim: X server disconnected");
|
||||||
|
die("xim: frontend stopped");
|
||||||
|
}
|
||||||
27
xim/Makefile
27
xim/Makefile
@@ -1,27 +0,0 @@
|
|||||||
CC = cc
|
|
||||||
XIM_CFLAGS = $(shell pkg-config --cflags xcb-imdkit xkbcommon)
|
|
||||||
XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon)
|
|
||||||
CFLAGS = -Wall -Wextra -O2 -I.. $(XIM_CFLAGS)
|
|
||||||
PROG = strans-xim
|
|
||||||
|
|
||||||
SRCS = $(wildcard *.c)
|
|
||||||
OBJS = $(SRCS:.c=.o)
|
|
||||||
TEST = xim_test
|
|
||||||
|
|
||||||
all: $(PROG)
|
|
||||||
|
|
||||||
$(PROG): $(OBJS) ../ipc.c ../ipc.h
|
|
||||||
$(CC) $(CFLAGS) -o $@ $(OBJS) ../ipc.c $(XIM_LIBS)
|
|
||||||
|
|
||||||
$(OBJS): ../ipc.h
|
|
||||||
|
|
||||||
$(TEST): ../tests/xim_test.c keymap.c ximtext.c
|
|
||||||
$(CC) $(CFLAGS) -o $@ ../tests/xim_test.c keymap.c ximtext.c $(XIM_LIBS)
|
|
||||||
|
|
||||||
check: $(TEST)
|
|
||||||
./$(TEST)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(OBJS) $(PROG) $(TEST)
|
|
||||||
|
|
||||||
.PHONY: all check clean
|
|
||||||
37
xim/keymap.c
37
xim/keymap.c
@@ -1,37 +0,0 @@
|
|||||||
#include <stdint.h>
|
|
||||||
#include <xkbcommon/xkbcommon.h>
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
ShiftMask = 1<<0,
|
|
||||||
LockMask = 1<<1,
|
|
||||||
GroupShift = 13,
|
|
||||||
GroupMask = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
uint32_t
|
|
||||||
keymaplookup(const uint32_t *syms, int nsyms, uint16_t state)
|
|
||||||
{
|
|
||||||
uint32_t lo, hi, lower, upper;
|
|
||||||
int col, group, shift;
|
|
||||||
|
|
||||||
if(syms == NULL || nsyms <= 0)
|
|
||||||
return XKB_KEY_NoSymbol;
|
|
||||||
group = (state >> GroupShift) & GroupMask;
|
|
||||||
col = group * 2;
|
|
||||||
if(col >= nsyms || syms[col] == XKB_KEY_NoSymbol)
|
|
||||||
col = 0;
|
|
||||||
lo = syms[col];
|
|
||||||
if(lo == XKB_KEY_NoSymbol)
|
|
||||||
return XKB_KEY_NoSymbol;
|
|
||||||
hi = col + 1 < nsyms ? syms[col + 1] : XKB_KEY_NoSymbol;
|
|
||||||
lower = xkb_keysym_to_lower(lo);
|
|
||||||
upper = xkb_keysym_to_upper(lo);
|
|
||||||
if(hi == XKB_KEY_NoSymbol)
|
|
||||||
hi = lower != upper ? upper : lo;
|
|
||||||
shift = (state & ShiftMask) != 0;
|
|
||||||
/* Lock reverses Shift only for keys with an alphabetic case pair. */
|
|
||||||
if((state & LockMask) && lower != upper)
|
|
||||||
shift = !shift;
|
|
||||||
return shift ? hi : lo;
|
|
||||||
}
|
|
||||||
298
xim/main.c
298
xim/main.c
@@ -1,298 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xkbcommon/xkbcommon.h>
|
|
||||||
#include <xcb-imdkit/imdkit.h>
|
|
||||||
#include <xcb-imdkit/encoding.h>
|
|
||||||
#include "ipc.h"
|
|
||||||
|
|
||||||
uint32_t keymaplookup(const uint32_t*, int, uint16_t);
|
|
||||||
char *ximcompound(const char*, size_t, size_t*);
|
|
||||||
|
|
||||||
typedef struct Ic Ic;
|
|
||||||
struct Ic
|
|
||||||
{
|
|
||||||
int fd;
|
|
||||||
};
|
|
||||||
|
|
||||||
static xcb_connection_t *conn;
|
|
||||||
static xcb_im_t *xim;
|
|
||||||
static xcb_keysym_t *kmap;
|
|
||||||
static uint8_t minkc, maxkc;
|
|
||||||
static uint8_t symsper;
|
|
||||||
|
|
||||||
static char *encs[] = {"COMPOUND_TEXT"};
|
|
||||||
static uint32_t styles[] = {
|
|
||||||
XCB_IM_PreeditNothing | XCB_IM_StatusNothing,
|
|
||||||
XCB_IM_PreeditNone | XCB_IM_StatusNone,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
die(char *msg)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "strans-xim: %s\n", msg);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
kinit(void)
|
|
||||||
{
|
|
||||||
xcb_get_keyboard_mapping_cookie_t c;
|
|
||||||
xcb_get_keyboard_mapping_reply_t *r;
|
|
||||||
const xcb_setup_t *setup;
|
|
||||||
xcb_keysym_t *syms;
|
|
||||||
int n;
|
|
||||||
|
|
||||||
setup = xcb_get_setup(conn);
|
|
||||||
if(setup == NULL)
|
|
||||||
die("xcb_get_setup failed");
|
|
||||||
minkc = setup->min_keycode;
|
|
||||||
maxkc = setup->max_keycode;
|
|
||||||
c = xcb_get_keyboard_mapping(conn, minkc, maxkc - minkc + 1);
|
|
||||||
r = xcb_get_keyboard_mapping_reply(conn, c, NULL);
|
|
||||||
if(r == NULL)
|
|
||||||
die("keyboard mapping failed");
|
|
||||||
symsper = r->keysyms_per_keycode;
|
|
||||||
n = xcb_get_keyboard_mapping_keysyms_length(r);
|
|
||||||
syms = malloc(n * sizeof(xcb_keysym_t));
|
|
||||||
if(syms == NULL)
|
|
||||||
die("malloc failed");
|
|
||||||
memcpy(syms, xcb_get_keyboard_mapping_keysyms(r),
|
|
||||||
n * sizeof(xcb_keysym_t));
|
|
||||||
free(kmap);
|
|
||||||
kmap = syms;
|
|
||||||
free(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint32_t
|
|
||||||
kget(uint8_t kc, uint16_t state)
|
|
||||||
{
|
|
||||||
if(kmap == NULL || kc < minkc || kc > maxkc)
|
|
||||||
return 0;
|
|
||||||
return keymaplookup(kmap + (kc - minkc) * symsper, symsper, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static xcb_screen_t*
|
|
||||||
getscreen(int scr)
|
|
||||||
{
|
|
||||||
xcb_screen_iterator_t iter;
|
|
||||||
const xcb_setup_t *setup;
|
|
||||||
|
|
||||||
setup = xcb_get_setup(conn);
|
|
||||||
if(setup == NULL)
|
|
||||||
die("xcb_get_setup failed");
|
|
||||||
iter = xcb_setup_roots_iterator(setup);
|
|
||||||
for(; iter.rem; scr--, xcb_screen_next(&iter))
|
|
||||||
if(scr == 0)
|
|
||||||
return iter.data;
|
|
||||||
die("no screen");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
commit(xcb_im_input_context_t *ic, char *s, int len)
|
|
||||||
{
|
|
||||||
char *ct;
|
|
||||||
size_t clen;
|
|
||||||
|
|
||||||
if(len == 0)
|
|
||||||
return;
|
|
||||||
ct = ximcompound(s, len, &clen);
|
|
||||||
if(ct == NULL)
|
|
||||||
return;
|
|
||||||
xcb_im_commit_string(xim, ic, XCB_XIM_LOOKUP_CHARS, ct, clen, 0);
|
|
||||||
xcb_flush(conn);
|
|
||||||
free(ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
srvclose(Ic *state)
|
|
||||||
{
|
|
||||||
if(state == NULL || state->fd < 0)
|
|
||||||
return;
|
|
||||||
close(state->fd);
|
|
||||||
state->fd = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
srvconnect(Ic *state)
|
|
||||||
{
|
|
||||||
if(state->fd >= 0)
|
|
||||||
return 0;
|
|
||||||
state->fd = ipcconnect();
|
|
||||||
if(state->fd < 0)
|
|
||||||
return -1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
readresp(Ic *state, xcb_im_input_context_t *ic)
|
|
||||||
{
|
|
||||||
char buf[Ipcfieldmax+1];
|
|
||||||
Ipcresp resp;
|
|
||||||
|
|
||||||
if(ipcreadresp(state->fd, 0, buf, sizeof buf, NULL, 0, &resp) < 0)
|
|
||||||
return -1;
|
|
||||||
if(resp.ncommit > 0)
|
|
||||||
commit(ic, buf, resp.ncommit);
|
|
||||||
return resp.eaten;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
kpress(Ic *state, xcb_im_input_context_t *ic, xcb_key_press_event_t *ev)
|
|
||||||
{
|
|
||||||
unsigned char buf[Ipcreqsz];
|
|
||||||
uint32_t key, rune;
|
|
||||||
int eaten;
|
|
||||||
|
|
||||||
key = kget(ev->detail, ev->state);
|
|
||||||
rune = xkb_keysym_to_utf32(key);
|
|
||||||
if(rune >= ' ' && rune != 0x7f)
|
|
||||||
key = rune;
|
|
||||||
else if(key >= 0xff00 && key <= 0xffff)
|
|
||||||
key = Kspec + (key - 0xff00);
|
|
||||||
else
|
|
||||||
key = rune;
|
|
||||||
ipcpackreq(buf, 0, ev->state, key);
|
|
||||||
eaten = 0;
|
|
||||||
if(srvconnect(state) == 0){
|
|
||||||
if(ipcsend(state->fd, buf, sizeof buf) < 0 ||
|
|
||||||
(eaten = readresp(state, ic)) < 0){
|
|
||||||
srvclose(state);
|
|
||||||
eaten = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(eaten == 0)
|
|
||||||
xcb_im_forward_event(xim, ic, ev);
|
|
||||||
xcb_flush(conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
reset(Ic *state, xcb_im_input_context_t *ic, int release)
|
|
||||||
{
|
|
||||||
unsigned char buf[Ipcreqsz];
|
|
||||||
|
|
||||||
if(state == NULL || state->fd < 0)
|
|
||||||
return;
|
|
||||||
ipcpackreset(buf, 0);
|
|
||||||
if(ipcsend(state->fd, buf, sizeof buf) < 0 ||
|
|
||||||
readresp(state, ic) < 0)
|
|
||||||
release = 1;
|
|
||||||
if(release)
|
|
||||||
srvclose(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
icfree(void *p)
|
|
||||||
{
|
|
||||||
Ic *state;
|
|
||||||
|
|
||||||
state = p;
|
|
||||||
/* imdkit frees this data on context destruction and client loss. */
|
|
||||||
srvclose(state);
|
|
||||||
free(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
iccreate(xcb_im_input_context_t *ic)
|
|
||||||
{
|
|
||||||
Ic *state;
|
|
||||||
|
|
||||||
if(ic == NULL || xcb_im_input_context_get_data(ic) != NULL)
|
|
||||||
return;
|
|
||||||
state = calloc(1, sizeof *state);
|
|
||||||
if(state == NULL)
|
|
||||||
return;
|
|
||||||
state->fd = -1;
|
|
||||||
xcb_im_input_context_set_data(ic, state, icfree);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
callback(xcb_im_t *im, xcb_im_client_t *client, xcb_im_input_context_t *ic,
|
|
||||||
const xcb_im_packet_header_fr_t *hdr, void *frame, void *arg, void *user)
|
|
||||||
{
|
|
||||||
xcb_key_press_event_t *ev;
|
|
||||||
Ic *state;
|
|
||||||
|
|
||||||
(void)im;
|
|
||||||
(void)client;
|
|
||||||
(void)frame;
|
|
||||||
(void)user;
|
|
||||||
|
|
||||||
if(hdr->major_opcode == XCB_XIM_CREATE_IC){
|
|
||||||
iccreate(ic);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(ic == NULL)
|
|
||||||
return;
|
|
||||||
state = xcb_im_input_context_get_data(ic);
|
|
||||||
if(state == NULL)
|
|
||||||
return;
|
|
||||||
switch(hdr->major_opcode){
|
|
||||||
case XCB_XIM_FORWARD_EVENT:
|
|
||||||
ev = arg;
|
|
||||||
if(ev != NULL && (ev->response_type & ~0x80) == XCB_KEY_PRESS)
|
|
||||||
kpress(state, ic, ev);
|
|
||||||
break;
|
|
||||||
case XCB_XIM_RESET_IC:
|
|
||||||
/* ResetIC ends this context's ownership, like focus loss. */
|
|
||||||
reset(state, ic, 1);
|
|
||||||
break;
|
|
||||||
case XCB_XIM_UNSET_IC_FOCUS:
|
|
||||||
reset(state, ic, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
ximinit(void)
|
|
||||||
{
|
|
||||||
xcb_screen_t *screen;
|
|
||||||
xcb_window_t win;
|
|
||||||
xcb_im_styles_t st;
|
|
||||||
xcb_im_encodings_t enc;
|
|
||||||
int scr;
|
|
||||||
|
|
||||||
st.nStyles = 2;
|
|
||||||
st.styles = styles;
|
|
||||||
enc.nEncodings = 1;
|
|
||||||
enc.encodings = encs;
|
|
||||||
xcb_compound_text_init();
|
|
||||||
conn = xcb_connect(NULL, &scr);
|
|
||||||
if(conn == NULL || xcb_connection_has_error(conn))
|
|
||||||
die("xcb_connect failed");
|
|
||||||
screen = getscreen(scr);
|
|
||||||
kinit();
|
|
||||||
win = xcb_generate_id(conn);
|
|
||||||
xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, screen->root,
|
|
||||||
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
|
||||||
screen->root_visual, 0, NULL);
|
|
||||||
xim = xcb_im_create(conn, scr, win, "strans",
|
|
||||||
XCB_IM_ALL_LOCALES, &st, NULL, NULL, &enc,
|
|
||||||
XCB_EVENT_MASK_KEY_PRESS, callback, NULL);
|
|
||||||
if(xim == NULL || !xcb_im_open_im(xim))
|
|
||||||
die("xcb_im failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(void)
|
|
||||||
{
|
|
||||||
xcb_generic_event_t *ev;
|
|
||||||
uint8_t type;
|
|
||||||
|
|
||||||
ximinit();
|
|
||||||
for(;;){
|
|
||||||
ev = xcb_wait_for_event(conn);
|
|
||||||
if(ev == NULL)
|
|
||||||
break;
|
|
||||||
type = ev->response_type & ~0x80;
|
|
||||||
if(type == XCB_MAPPING_NOTIFY)
|
|
||||||
kinit();
|
|
||||||
else
|
|
||||||
xcb_im_filter_event(xim, ev);
|
|
||||||
free(ev);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
#include <stdint.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <xcb-imdkit/encoding.h>
|
|
||||||
|
|
||||||
static char*
|
|
||||||
ximutf8compound(const char *s, size_t len, size_t *outlen)
|
|
||||||
{
|
|
||||||
static const char begin[] = "\033%G";
|
|
||||||
static const char end[] = "\033%@";
|
|
||||||
char *ct;
|
|
||||||
size_t n;
|
|
||||||
|
|
||||||
if(s == NULL || len > SIZE_MAX - (sizeof begin + sizeof end - 1))
|
|
||||||
return NULL;
|
|
||||||
n = len + sizeof begin + sizeof end - 2;
|
|
||||||
ct = malloc(n + 1);
|
|
||||||
if(ct == NULL)
|
|
||||||
return NULL;
|
|
||||||
memcpy(ct, begin, sizeof begin - 1);
|
|
||||||
memcpy(ct + sizeof begin - 1, s, len);
|
|
||||||
memcpy(ct + sizeof begin - 1 + len, end, sizeof end - 1);
|
|
||||||
ct[n] = '\0';
|
|
||||||
if(outlen != NULL)
|
|
||||||
*outlen = n;
|
|
||||||
return ct;
|
|
||||||
}
|
|
||||||
|
|
||||||
char*
|
|
||||||
ximcompound(const char *s, size_t len, size_t *outlen)
|
|
||||||
{
|
|
||||||
char *ct;
|
|
||||||
|
|
||||||
if(s == NULL)
|
|
||||||
return NULL;
|
|
||||||
ct = xcb_utf8_to_compound_text(s, len, outlen);
|
|
||||||
if(ct != NULL)
|
|
||||||
return ct;
|
|
||||||
/* X.Org Compound Text extension for characters in no legacy charset. */
|
|
||||||
return ximutf8compound(s, len, outlen);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user