Compare commits
28 Commits
125e9349c0
...
c4b0141413
| Author | SHA1 | Date | |
|---|---|---|---|
| c4b0141413 | |||
| 46c8ccd779 | |||
| d7115c5eb5 | |||
| f395f29918 | |||
| 6a2b2bccc4 | |||
| 05ff88c74f | |||
| 6c0a533c4e | |||
| e44fc005da | |||
| 469278209f | |||
| 9569c2eaaf | |||
| 335783ebe5 | |||
| 0f8396915b | |||
| 91b3042e66 | |||
| b44663096f | |||
| 685d78bab5 | |||
| f8ebfcdc86 | |||
| f399eb0af6 | |||
| 88d26c598d | |||
| 9f4f9ceb14 | |||
| bb2a970db1 | |||
| 21a94d447c | |||
| 367cbb6cc3 | |||
| b6a9ccd9d9 | |||
| 13f0b436b8 | |||
| c07673ad4c | |||
| e01a991de4 | |||
| fffbb9d8de | |||
| f46d51b539 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -2,6 +2,11 @@
|
|||||||
/strans
|
/strans
|
||||||
/tests/*.o
|
/tests/*.o
|
||||||
/tests/unit_test
|
/tests/unit_test
|
||||||
|
/tests/ibus_live_test
|
||||||
|
/tests/ipc_live_test
|
||||||
|
/tests/daemon_collision_test
|
||||||
|
/tests/daemon_failure_test
|
||||||
|
/tests/daemon_restart_test
|
||||||
/xim/*.o
|
/xim/*.o
|
||||||
/xim/strans-xim
|
/xim/strans-xim
|
||||||
/xim/xim_test
|
/xim/xim_test
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ FROM archlinux:base@sha256:b0deabeb3d283da2c7f7dbf0eea051b7b2cd0554e0b737cc457fd
|
|||||||
|
|
||||||
RUN pacman -Syu --noconfirm --needed \
|
RUN pacman -Syu --noconfirm --needed \
|
||||||
diffutils \
|
diffutils \
|
||||||
|
freetype2 \
|
||||||
gcc \
|
gcc \
|
||||||
gtk3 \
|
gtk3 \
|
||||||
libxcb \
|
libxcb \
|
||||||
@@ -10,13 +11,15 @@ RUN pacman -Syu --noconfirm --needed \
|
|||||||
pkgconf \
|
pkgconf \
|
||||||
plan9port \
|
plan9port \
|
||||||
python \
|
python \
|
||||||
wayland \
|
ttf-dejavu \
|
||||||
|
ttf-jigmo \
|
||||||
which \
|
which \
|
||||||
xcb-imdkit \
|
xcb-imdkit \
|
||||||
dbus \
|
dbus \
|
||||||
&& pacman -Scc --noconfirm
|
&& pacman -Scc --noconfirm
|
||||||
|
|
||||||
RUN printf "egrep='grep -E'\n" > /usr/lib/plan9/config
|
RUN printf "egrep='grep -E'\n" > /usr/lib/plan9/config
|
||||||
|
RUN dbus-uuidgen --ensure=/etc/machine-id
|
||||||
|
|
||||||
ENV PLAN9=/usr/lib/plan9
|
ENV PLAN9=/usr/lib/plan9
|
||||||
ENV PATH=$PATH:/usr/lib/plan9/bin
|
ENV PATH=$PATH:/usr/lib/plan9/bin
|
||||||
|
|||||||
@@ -6,9 +6,7 @@
|
|||||||
- `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.
|
||||||
- Font license texts are stored in `font/` and associated with exact files
|
- License texts and provenance for the historically bundled fonts are retained
|
||||||
by `font/PROVENANCE`.
|
in `font/`; no font binary remains in the source tree.
|
||||||
- `stb_truetype.h` includes its complete dual MIT/Public Domain terms.
|
|
||||||
- Generated Wayland files include their MIT-style notices in each file.
|
|
||||||
|
|
||||||
No repository-wide license has been declared for original strans source.
|
No repository-wide license has been declared for original strans source.
|
||||||
|
|||||||
19
Makefile
19
Makefile
@@ -1,27 +1,26 @@
|
|||||||
CC = 9c
|
CC = 9c
|
||||||
LD = 9l
|
LD = 9l
|
||||||
PKG_CFLAGS = $(shell pkg-config --cflags dbus-1 wayland-client xkbcommon)
|
PKG_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
|
||||||
PKG_LIBS = $(shell pkg-config --libs dbus-1 wayland-client xkbcommon)
|
PKG_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
|
||||||
|
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
|
||||||
|
FT_LIBS = $(shell pkg-config --libs freetype2)
|
||||||
CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS)
|
CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS)
|
||||||
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 = $(wildcard *.c)
|
SRCS = dict.c font.c hash.c ibus.c ipc.c ko.c main.c popup_layout.c \
|
||||||
|
srv.c str.c strans.c trie.c vi.c win.c
|
||||||
OBJS = $(SRCS:.c=.o)
|
OBJS = $(SRCS:.c=.o)
|
||||||
|
|
||||||
all: $(PROG) xim gtk
|
all: $(PROG) xim gtk
|
||||||
|
|
||||||
$(PROG): $(OBJS)
|
$(PROG): $(OBJS)
|
||||||
$(LD) -o $@ $(OBJS) -lthread -lbio -lxcb -lm $(PKG_LIBS)
|
$(LD) -o $@ $(OBJS) -lthread -lbio -lxcb $(PKG_LIBS) $(FT_LIBS)
|
||||||
|
|
||||||
$(OBJS): dat.h fn.h ipc.h
|
$(OBJS): dat.h fn.h ipc.h
|
||||||
# Avoid Arch's GLIBC_2.43 sqrtf optimization for older glibc runtimes.
|
font.o: CFLAGS += $(FT_CFLAGS)
|
||||||
font.o: CFLAGS += -fno-builtin-sqrt
|
|
||||||
font.o: stb_truetype.h
|
|
||||||
wayland.o: input-method-unstable-v2-client-protocol.h \
|
|
||||||
virtual-keyboard-unstable-v1-client-protocol.h
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJS) $(PROG)
|
rm -f $(OBJS) $(PROG)
|
||||||
@@ -57,7 +56,7 @@ docker: docker-image
|
|||||||
check: verify-map test
|
check: verify-map test
|
||||||
$(MAKE) -C xim check
|
$(MAKE) -C xim check
|
||||||
|
|
||||||
test:
|
test: $(PROG)
|
||||||
$(MAKE) -C tests check TESTARGS="$(TESTARGS)"
|
$(MAKE) -C tests check TESTARGS="$(TESTARGS)"
|
||||||
|
|
||||||
verify-map:
|
verify-map:
|
||||||
|
|||||||
76
README.md
76
README.md
@@ -1,10 +1,9 @@
|
|||||||
# strans
|
# strans
|
||||||
|
|
||||||
strans is a small, single-user input method for Korean, Japanese, English,
|
strans is a small, single-user input method for Korean, Japanese, English,
|
||||||
and emoji on X11 and Wayland. It provides one engine shared by XIM, GTK 3,
|
and emoji. It provides one engine shared by IBus, XIM, and GTK 3 frontends.
|
||||||
IBus, and Wayland input-method-v2 frontends. The existing Vietnamese Telex
|
The existing Vietnamese Telex mode remains available for compatibility, but
|
||||||
mode remains available for compatibility, but is not an actively developed
|
is not an actively developed language mode.
|
||||||
language mode.
|
|
||||||
|
|
||||||
## Input behavior
|
## Input behavior
|
||||||
|
|
||||||
@@ -75,7 +74,7 @@ build steps.
|
|||||||
The resulting runtime artifacts are:
|
The resulting runtime artifacts are:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
strans daemon, IBus and Wayland frontends
|
strans daemon and IBus frontend
|
||||||
xim/strans-xim XIM frontend
|
xim/strans-xim XIM frontend
|
||||||
gtk/im-strans.so GTK 3 frontend
|
gtk/im-strans.so GTK 3 frontend
|
||||||
```
|
```
|
||||||
@@ -99,12 +98,41 @@ tab-separated data consumable by the runtime dictionary reader.
|
|||||||
|
|
||||||
## Run and install
|
## Run and install
|
||||||
|
|
||||||
Start the daemon with the installed map and font directories:
|
Install one or more scalable outline fonts, then pass their files in fallback
|
||||||
|
order after the map directory. The daemon accepts at most four font files and
|
||||||
|
does not search the system font database. For example, the Docker-tested Arch
|
||||||
|
packages and paths are:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./strans map font &
|
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
|
||||||
|
scalable outline files may be supplied; their argument order is the glyph
|
||||||
|
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. The
|
||||||
|
launcher never replaces an existing daemon: an occupied per-user socket is a
|
||||||
|
visible startup failure.
|
||||||
|
|
||||||
The IPC socket is placed at `$XDG_RUNTIME_DIR/strans.sock` when
|
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
|
`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
|
fallback `/tmp/strans.<uid>`. A second daemon will not replace a live
|
||||||
@@ -139,10 +167,9 @@ For IBus clients such as kitty:
|
|||||||
GLFW_IM_MODULE=ibus kitty
|
GLFW_IM_MODULE=ibus kitty
|
||||||
```
|
```
|
||||||
|
|
||||||
Wayland needs no environment variable. Start `strans` after a compositor
|
strans provides its own IBus endpoint; neither ibus-daemon nor fcitx is
|
||||||
that supports input-method-v2. Neither ibus-daemon nor fcitx is required.
|
required. Starting without `DISPLAY` is supported: input processing and IBus
|
||||||
Starting without `DISPLAY` is supported: the optional X popup is disabled,
|
continue to run while the optional X popup is disabled.
|
||||||
while input processing and non-X frontends continue to run.
|
|
||||||
|
|
||||||
## Ownership and rendering constraints
|
## Ownership and rendering constraints
|
||||||
|
|
||||||
@@ -152,21 +179,22 @@ composition and transfers ownership. Reset, focus loss, context destruction,
|
|||||||
and disconnect affect the engine only when they come from its active owner;
|
and disconnect affect the engine only when they come from its active owner;
|
||||||
stale events cannot clear a newer owner's preedit.
|
stale events cannot clear a newer owner's preedit.
|
||||||
|
|
||||||
The popup is an optional X window, including when the active frontend is
|
The popup is an optional X window. It uses the explicit font-file order and
|
||||||
Wayland. It uses deterministic font fallback and fixed-width rune cells;
|
fixed-width rune cells; U+FE0E and U+FE0F variation selectors use zero cells.
|
||||||
U+FE0E and U+FE0F variation selectors use zero cells. It does not perform
|
It does not perform OpenType shaping, color-emoji rendering, or general
|
||||||
OpenType shaping, color-emoji rendering, or general grapheme clustering, and
|
grapheme clustering. Multi-rune emoji are committed intact but are drawn as
|
||||||
there is no native Wayland popup renderer. Multi-rune emoji are committed
|
their constituent rune cells under this boundary. A supplied caret rectangle
|
||||||
intact but are drawn as their constituent rune cells under this boundary. A
|
is used when available, with the pointer as fallback. The X renderer accepts
|
||||||
supplied caret rectangle is used when available, with the pointer as fallback.
|
only a 24-depth, 32-bpp TrueColor root format and disables itself cleanly on
|
||||||
The X renderer accepts only a 24-depth, 32-bpp TrueColor root format and
|
other visuals.
|
||||||
disables itself cleanly on other visuals.
|
|
||||||
|
|
||||||
## Data, third-party code, and licensing
|
## Data, third-party code, and licensing
|
||||||
|
|
||||||
Data and third-party provenance is recorded in
|
Data and third-party provenance is recorded in
|
||||||
[`docs/PROVENANCE.md`](docs/PROVENANCE.md), with font-specific details in
|
[`docs/PROVENANCE.md`](docs/PROVENANCE.md), with font-specific details in
|
||||||
[`font/PROVENANCE`](font/PROVENANCE). The historical SKK-derived Kanji
|
[`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
|
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).
|
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 single-character Hanja data derived from libhangul is distributed under
|
||||||
@@ -174,6 +202,6 @@ the BSD 3-Clause license in
|
|||||||
[`LICENSES/BSD-3-Clause-libhangul-hanja.txt`](LICENSES/BSD-3-Clause-libhangul-hanja.txt).
|
[`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
|
The repository does not currently declare a license for the strans project
|
||||||
source as a whole. The licenses of bundled data, fonts, generated protocol
|
source as a whole. The licenses of bundled data, historical font assets, and
|
||||||
code, and third-party components do not by themselves license the original
|
third-party components do not by themselves license the original strans
|
||||||
strans source.
|
source.
|
||||||
|
|||||||
91
bench.sh
91
bench.sh
@@ -2,34 +2,101 @@
|
|||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")" || exit 1
|
||||||
|
|
||||||
|
case ${XDG_RUNTIME_DIR-} in
|
||||||
|
/) ipc=/strans.sock ;;
|
||||||
|
/*/) ipc=${XDG_RUNTIME_DIR}strans.sock ;;
|
||||||
|
/*) ipc=${XDG_RUNTIME_DIR}/strans.sock ;;
|
||||||
|
*) ipc=/tmp/strans.$(id -u) ;;
|
||||||
|
esac
|
||||||
|
if test -n "${XDG_CONFIG_HOME-}"; then
|
||||||
|
busdir=$XDG_CONFIG_HOME/ibus/bus
|
||||||
|
elif test "${HOME+x}" = x; then
|
||||||
|
busdir=$HOME/.config/ibus/bus
|
||||||
|
else
|
||||||
|
echo "bench.sh: HOME or XDG_CONFIG_HOME is required for IBus discovery" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
strans_pid=
|
strans_pid=
|
||||||
perf_pid=
|
perf_pid=
|
||||||
|
|
||||||
cleanup()
|
cleanup()
|
||||||
{
|
{
|
||||||
|
status=$?
|
||||||
trap - 0 1 2 15
|
trap - 0 1 2 15
|
||||||
if test -n "$perf_pid"; then
|
if test -n "$perf_pid"; then
|
||||||
kill -INT "$perf_pid" 2>/dev/null || true
|
kill -INT "$perf_pid" 2>/dev/null || :
|
||||||
wait "$perf_pid" 2>/dev/null || true
|
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 || true
|
kill "$strans_pid" 2>/dev/null || :
|
||||||
wait "$strans_pid" 2>/dev/null || true
|
wait "$strans_pid" 2>/dev/null || :
|
||||||
|
strans_pid=
|
||||||
fi
|
fi
|
||||||
|
exit "$status"
|
||||||
}
|
}
|
||||||
|
|
||||||
trap cleanup 0 1 2 15
|
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
|
||||||
|
}
|
||||||
|
|
||||||
./strans map font &
|
trap cleanup 0
|
||||||
|
trap 'exit 129' 1
|
||||||
|
trap 'exit 130' 2
|
||||||
|
trap 'exit 143' 15
|
||||||
|
|
||||||
|
./strans map "$@" &
|
||||||
strans_pid=$!
|
strans_pid=$!
|
||||||
sleep 1
|
|
||||||
kill -0 "$strans_pid"
|
|
||||||
|
|
||||||
# warm up glyph cache
|
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
|
||||||
|
if wait "$strans_pid"; then
|
||||||
|
status=0
|
||||||
|
else
|
||||||
|
status=$?
|
||||||
|
fi
|
||||||
|
strans_pid=
|
||||||
|
if test "$status" -eq 0; then
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
echo "bench.sh: daemon exited before publishing IPC and IBus endpoints" >&2
|
||||||
|
exit "$status"
|
||||||
|
fi
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
if test "$attempt" -lt 10; then
|
||||||
|
sleep 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if test "$attempt" -eq 10; then
|
||||||
|
echo "bench.sh: daemon did not publish IPC and IBus endpoints within 10 seconds" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# warm up the renderer
|
||||||
./bench/bench bench/bench.keys 100
|
./bench/bench bench/bench.keys 100
|
||||||
echo "cache warmed up"
|
echo "renderer warmed up"
|
||||||
|
|
||||||
perf record -g -o bench/perf.data -p "$strans_pid" &
|
perf record -g -o bench/perf.data -p "$strans_pid" &
|
||||||
perf_pid=$!
|
perf_pid=$!
|
||||||
@@ -39,5 +106,5 @@ kill -0 "$perf_pid"
|
|||||||
./bench/bench bench/bench.keys 100
|
./bench/bench bench/bench.keys 100
|
||||||
|
|
||||||
kill -INT "$perf_pid"
|
kill -INT "$perf_pid"
|
||||||
wait "$perf_pid" 2>/dev/null || true
|
wait "$perf_pid" 2>/dev/null || :
|
||||||
perf_pid=
|
perf_pid=
|
||||||
|
|||||||
17
dat.h
17
dat.h
@@ -18,7 +18,7 @@ enum
|
|||||||
LangVI = 0x16,
|
LangVI = 0x16,
|
||||||
|
|
||||||
Fontsz = 32,
|
Fontsz = 32,
|
||||||
Nglyphs = 0x20000,
|
Maxfonts = 4,
|
||||||
Maxrunes = 64,
|
Maxrunes = 64,
|
||||||
Maxutf = Maxrunes * UTFmax + 1,
|
Maxutf = Maxrunes * UTFmax + 1,
|
||||||
};
|
};
|
||||||
@@ -28,7 +28,6 @@ enum
|
|||||||
Maxclients = 64,
|
Maxclients = 64,
|
||||||
Maxkouho = 32,
|
Maxkouho = 32,
|
||||||
Maxdisp = 9,
|
Maxdisp = 9,
|
||||||
Wlkeymax = 0x300,
|
|
||||||
Imgw = (Maxrunes + 3) * Fontsz,
|
Imgw = (Maxrunes + 3) * Fontsz,
|
||||||
Imgh = (Maxdisp + 1) * Fontsz,
|
Imgh = (Maxdisp + 1) * Fontsz,
|
||||||
|
|
||||||
@@ -44,17 +43,6 @@ struct Str
|
|||||||
int n;
|
int n;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Wlstate Wlstate;
|
|
||||||
struct Wlstate
|
|
||||||
{
|
|
||||||
uchar down[Wlkeymax];
|
|
||||||
u32int repeatkey;
|
|
||||||
uvlong repeatat;
|
|
||||||
int rate;
|
|
||||||
int delay;
|
|
||||||
int repeating;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct Emit Emit;
|
typedef struct Emit Emit;
|
||||||
struct Emit
|
struct Emit
|
||||||
{
|
{
|
||||||
@@ -166,12 +154,11 @@ struct Keyres
|
|||||||
|
|
||||||
struct Keyreq
|
struct Keyreq
|
||||||
{
|
{
|
||||||
uvlong owner;
|
void *owner; /* stable until the context's release is acknowledged */
|
||||||
int op;
|
int op;
|
||||||
u32int ks;
|
u32int ks;
|
||||||
u32int mod;
|
u32int mod;
|
||||||
Caret caret;
|
Caret caret;
|
||||||
int want; /* nonzero: include preedit in reply */
|
|
||||||
Channel *reply;
|
Channel *reply;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ identifiers are:
|
|||||||
- input SHA-256: `dd44dcc856cf542b1022d0f39c2e9b9f8805fdcc5923be80f04849ed97ce0996`
|
- input SHA-256: `dd44dcc856cf542b1022d0f39c2e9b9f8805fdcc5923be80f04849ed97ce0996`
|
||||||
|
|
||||||
`map/libhangul2hanja` keeps only a one-syllable modern Hangul reading paired
|
`map/libhangul2hanja` keeps only a one-syllable modern Hangul reading paired
|
||||||
with one BMP Hanja rune supported by the popup. It writes one
|
with one BMP Hanja rune. It writes one
|
||||||
`Hanja<TAB>Hangul syllable` pair per row in upstream order; word entries are
|
`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
|
not part of this data. `map/mkhanja` groups those rows by reading for the
|
||||||
runtime dictionary without changing candidate order, retaining the first 32
|
runtime dictionary without changing candidate order, retaining the first 32
|
||||||
@@ -46,45 +46,31 @@ new SKK revision.
|
|||||||
|
|
||||||
## Fonts
|
## Fonts
|
||||||
|
|
||||||
The popup bundles Noto Sans, Japanese-region Noto Sans Mono CJK, and Noto
|
No font binaries are bundled. The daemon receives one to four font file paths
|
||||||
Emoji. Exact checksums, versions or source revisions, download locations
|
on its command line and tries them in that order; it does not discover fonts by
|
||||||
where known, fallback order, and license-file names are recorded in
|
family name or directory contents. `font/PROVENANCE` retains the exact
|
||||||
`font/PROVENANCE`. The corresponding OFL 1.1 and Apache 2.0 texts are kept
|
checksums, source revisions, and license records for the three Noto faces that
|
||||||
beside the fonts.
|
were formerly stored in the repository.
|
||||||
|
|
||||||
## stb_truetype
|
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:
|
||||||
|
|
||||||
`stb_truetype.h` is Sean Barrett's stb_truetype version 1.26, dated
|
- `ttf-dejavu` `2.37+18+g9b5d1b2f-8`, providing
|
||||||
August 28, 2021 in its header. Its exact repository checksum is:
|
`/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`).
|
||||||
|
|
||||||
```text
|
Pacman validates the package checksums and repository signatures. DejaVu Sans
|
||||||
ecd30b05e0dd4fea3a13c26810dd9e1992dc379049482c393d5a19e6b5090aab
|
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
|
||||||
The file was present in the repository's first commit,
|
`/usr/share/licenses/ttf-jigmo/LICENSE.txt`; the packages identify their terms
|
||||||
`dcd1147638f908eca7e4b7616815e215022cc99d`. No upstream commit or download
|
as the DejaVu custom license and CC0 1.0, respectively. These packages are
|
||||||
URL was recorded. Its complete dual MIT/Public Domain terms are retained at
|
test fixtures, not names or paths hard-coded into the daemon.
|
||||||
the end of the header.
|
|
||||||
|
|
||||||
## Generated Wayland protocol code
|
|
||||||
|
|
||||||
The four checked-in Wayland protocol C/header files were added in commit
|
|
||||||
`7629ec5fe8976812655c9d12b447a62abc8bb671`:
|
|
||||||
|
|
||||||
- `input-method-unstable-v2-client-protocol.h`
|
|
||||||
- `input-method-unstable-v2-protocol.c`
|
|
||||||
- `virtual-keyboard-unstable-v1-client-protocol.h`
|
|
||||||
- `virtual-keyboard-unstable-v1-protocol.c`
|
|
||||||
|
|
||||||
Their embedded notices identify the protocols as input-method-unstable-v2
|
|
||||||
and virtual-keyboard-unstable-v1 and include the applicable MIT-style license
|
|
||||||
terms. The input-method files report generation by wayland-scanner 1.21.0;
|
|
||||||
the virtual-keyboard files report wayland-scanner 1.23.1. The source XML
|
|
||||||
files and their upstream repository revisions were not committed or recorded
|
|
||||||
in history, so these generated files cannot honestly be regenerated
|
|
||||||
byte-for-byte from repository artifacts. Do not edit them manually. A
|
|
||||||
future regeneration should commit the source XML or record its immutable
|
|
||||||
upstream revision and the scanner command/version.
|
|
||||||
|
|
||||||
## Git submodules
|
## Git submodules
|
||||||
|
|
||||||
|
|||||||
15
fn.h
15
fn.h
@@ -36,24 +36,13 @@ Emit transko(Im*, Rune);
|
|||||||
Emit transvi(Im*, Rune);
|
Emit transvi(Im*, Rune);
|
||||||
void backko(Im*);
|
void backko(Im*);
|
||||||
void backvi(Im*);
|
void backvi(Im*);
|
||||||
void dictsend(Im*, Str*);
|
|
||||||
uvlong ownernew(void);
|
|
||||||
|
|
||||||
int srvreadkey(int, Keyreq*);
|
void srvinit(void);
|
||||||
void srvthread(void*);
|
void srvthread(void*);
|
||||||
void ibusthread(void*);
|
void ibusthread(void*);
|
||||||
void waylandthread(void*);
|
|
||||||
void wlstateinit(Wlstate*);
|
|
||||||
int wlstatepress(Wlstate*, u32int, int, int, uvlong);
|
|
||||||
int wlstaterelease(Wlstate*, u32int);
|
|
||||||
void wlstaterepeatinfo(Wlstate*, int, int);
|
|
||||||
void wlstatecancelrepeat(Wlstate*);
|
|
||||||
void wlstateclear(Wlstate*);
|
|
||||||
int wlstatetimeout(Wlstate*, uvlong);
|
|
||||||
int wlstaterepeat(Wlstate*, uvlong, u32int*);
|
|
||||||
|
|
||||||
void* emalloc(ulong);
|
void* emalloc(ulong);
|
||||||
void* erealloc(void*, ulong);
|
void* erealloc(void*, ulong);
|
||||||
|
|
||||||
int fontinit(char*);
|
int fontinit(char**, int);
|
||||||
void putfont(u32int*, int, int, int, int, Rune);
|
void putfont(u32int*, int, int, int, int, Rune);
|
||||||
|
|||||||
209
font.c
209
font.c
@@ -1,29 +1,18 @@
|
|||||||
#define STB_TRUETYPE_IMPLEMENTATION
|
|
||||||
#include "stb_truetype.h"
|
|
||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
|
#include <ft2build.h>
|
||||||
|
#include FT_FREETYPE_H
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
typedef struct Glyph Glyph;
|
|
||||||
typedef struct Font Font;
|
typedef struct Font Font;
|
||||||
struct Glyph
|
|
||||||
{
|
|
||||||
uchar *bmp;
|
|
||||||
int w, h, ox, oy, base;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Font
|
struct Font
|
||||||
{
|
{
|
||||||
stbtt_fontinfo info;
|
FT_Face face;
|
||||||
uchar *data;
|
|
||||||
float scale;
|
|
||||||
int base;
|
int base;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum { Maxfonts = 4 };
|
static FT_Library lib;
|
||||||
|
|
||||||
static Font fonts[Maxfonts];
|
static Font fonts[Maxfonts];
|
||||||
static int nfonts;
|
static int nfonts;
|
||||||
static Glyph cache[Nglyphs];
|
|
||||||
static u32int blendtab[2][256];
|
static u32int blendtab[2][256];
|
||||||
|
|
||||||
static u32int
|
static u32int
|
||||||
@@ -41,117 +30,139 @@ blend(u32int bg, u32int fg, int a)
|
|||||||
static int
|
static int
|
||||||
loadfont(char *path)
|
loadfont(char *path)
|
||||||
{
|
{
|
||||||
|
FT_Face face;
|
||||||
Font *f;
|
Font *f;
|
||||||
int ascent, descent, gap, fd;
|
vlong height, pixels;
|
||||||
long sz, n;
|
|
||||||
|
|
||||||
fd = open(path, OREAD);
|
if(nfonts >= nelem(fonts))
|
||||||
if(fd < 0){
|
return 0;
|
||||||
fprint(2, "strans: popup: can't open font: %s\n", path);
|
if(FT_New_Face(lib, path, 0, &face) != 0){
|
||||||
|
fprint(2, "strans: popup: can't load font: %s\n", path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
sz = seek(fd, 0, 2);
|
height = (vlong)face->ascender - face->descender;
|
||||||
seek(fd, 0, 0);
|
if(height <= 0 || face->units_per_EM == 0){
|
||||||
if(sz <= 0){
|
fprint(2, "strans: popup: invalid font metrics: %s\n", path);
|
||||||
fprint(2, "strans: popup: empty font: %s\n", path);
|
FT_Done_Face(face);
|
||||||
close(fd);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
f = &fonts[nfonts];
|
f = &fonts[nfonts];
|
||||||
f->data = emalloc(sz);
|
f->face = face;
|
||||||
n = readn(fd, f->data, sz);
|
f->base = (vlong)Fontsz * face->ascender / height;
|
||||||
close(fd);
|
|
||||||
if(n != sz){
|
|
||||||
fprint(2, "strans: popup: can't read font: %s\n", path);
|
|
||||||
free(f->data);
|
|
||||||
f->data = nil;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if(!stbtt_InitFont(&f->info, f->data,
|
|
||||||
stbtt_GetFontOffsetForIndex(f->data, 0))){
|
|
||||||
fprint(2, "strans: popup: can't init font: %s\n", path);
|
|
||||||
free(f->data);
|
|
||||||
f->data = nil;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
f->scale = stbtt_ScaleForPixelHeight(&f->info, Fontsz);
|
|
||||||
stbtt_GetFontVMetrics(&f->info, &ascent, &descent, &gap);
|
|
||||||
(void)descent;
|
|
||||||
(void)gap;
|
|
||||||
f->base = (int)(ascent * f->scale);
|
|
||||||
nfonts++;
|
nfonts++;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
static void
|
||||||
fontinit(char *dir)
|
clearfonts(void)
|
||||||
{
|
{
|
||||||
static char *names[] = {
|
int i;
|
||||||
"NotoSans-Regular.ttf",
|
|
||||||
"NotoSansMonoCJKjp-Regular.otf",
|
|
||||||
"NotoEmoji-Regular.ttf",
|
|
||||||
};
|
|
||||||
int i, a;
|
|
||||||
char path[256];
|
|
||||||
|
|
||||||
|
for(i = 0; i < nfonts; i++){
|
||||||
|
FT_Done_Face(fonts[i].face);
|
||||||
|
fonts[i].face = nil;
|
||||||
|
}
|
||||||
nfonts = 0;
|
nfonts = 0;
|
||||||
memset(cache, 0, sizeof cache);
|
}
|
||||||
for(i = 0; i < nelem(names); i++){
|
|
||||||
if(strlen(dir) + 1 + strlen(names[i]) + 1 > sizeof path){
|
int
|
||||||
fprint(2, "strans: popup: font path is too long\n");
|
fontinit(char **path, int npath)
|
||||||
continue;
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
snprint(path, sizeof path, "%s/%s", dir, names[i]);
|
|
||||||
loadfont(path);
|
|
||||||
}
|
}
|
||||||
for(a = 0; a < 256; a++){
|
for(a = 0; a < 256; a++){
|
||||||
blendtab[0][a] = blend(Colbg, Colfg, a);
|
blendtab[0][a] = blend(Colbg, Colfg, a);
|
||||||
blendtab[1][a] = blend(Colsel, Colfg, a);
|
blendtab[1][a] = blend(Colsel, Colfg, a);
|
||||||
}
|
}
|
||||||
return nfonts != 0;
|
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)
|
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
|
||||||
{
|
{
|
||||||
Glyph *g;
|
FT_UInt g;
|
||||||
int i, j, a, sel, f;
|
int f;
|
||||||
int y0, j0, j1, x0, i0, i1;
|
|
||||||
u32int *p;
|
|
||||||
|
|
||||||
if(r >= Nglyphs)
|
if(buf == nil || w <= 0 || h <= 0)
|
||||||
return;
|
return;
|
||||||
g = &cache[r];
|
for(f = 0; f < nfonts; f++){
|
||||||
if(g->bmp == nil){
|
g = FT_Get_Char_Index(fonts[f].face, r);
|
||||||
for(f = 0; f < nfonts; f++){
|
if(g == 0 || FT_Load_Glyph(fonts[f].face, g, FT_LOAD_DEFAULT) != 0 ||
|
||||||
if(stbtt_FindGlyphIndex(&fonts[f].info, r) == 0)
|
FT_Render_Glyph(fonts[f].face->glyph, FT_RENDER_MODE_NORMAL) != 0)
|
||||||
continue;
|
continue;
|
||||||
g->bmp = stbtt_GetCodepointBitmap(&fonts[f].info,
|
if(drawglyph(&fonts[f], buf, w, h, px, py))
|
||||||
fonts[f].scale, fonts[f].scale, r,
|
|
||||||
&g->w, &g->h, &g->ox, &g->oy);
|
|
||||||
if(g->bmp != nil){
|
|
||||||
g->base = fonts[f].base;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(g->bmp == nil)
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
y0 = py + g->base + g->oy;
|
|
||||||
j0 = y0 < 0 ? -y0 : 0;
|
|
||||||
j1 = y0 + g->h > h ? h - y0 : g->h;
|
|
||||||
x0 = px + g->ox;
|
|
||||||
i0 = x0 < 0 ? -x0 : 0;
|
|
||||||
i1 = x0 + g->w > w ? w - x0 : g->w;
|
|
||||||
for(j = j0; j < j1; j++){
|
|
||||||
for(i = i0; i < i1; i++){
|
|
||||||
a = g->bmp[j * g->w + i];
|
|
||||||
if(a > 0){
|
|
||||||
p = &buf[(y0 + j) * w + x0 + i];
|
|
||||||
sel = (*p == Colsel) ? 1 : 0;
|
|
||||||
*p = blendtab[sel][a];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,9 @@
|
|||||||
Bundled popup fonts
|
Historical popup fonts
|
||||||
===================
|
======================
|
||||||
|
|
||||||
The renderer tries these files in this fixed order:
|
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
|
1. NotoSans-Regular.ttf
|
||||||
2. NotoSansMonoCJKjp-Regular.otf
|
2. NotoSansMonoCJKjp-Regular.otf
|
||||||
@@ -13,25 +15,35 @@ NotoSans-Regular.ttf
|
|||||||
SHA-256: 9cb49a54e520423033f9727be2e53e4805a60656deb09c219740d8e5f3e033ac
|
SHA-256: 9cb49a54e520423033f9727be2e53e4805a60656deb09c219740d8e5f3e033ac
|
||||||
Internal family/version: Noto Sans Regular, version 2.005
|
Internal family/version: Noto Sans Regular, version 2.005
|
||||||
License: SIL Open Font License 1.1 (OFL-NotoSans.txt)
|
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. That commit did
|
This file entered the strans repository in commit 874b941. Its source URL was
|
||||||
not record its download URL, and the current upstream build is not
|
not recorded then; the immutable revision above was recovered later and is
|
||||||
byte-identical. The checksum above identifies the bundled file; no claim is
|
byte-identical to the historical file.
|
||||||
made that the current upstream URL regenerates it.
|
|
||||||
|
|
||||||
NotoSansMonoCJKjp-Regular.otf
|
NotoSansMonoCJKjp-Regular.otf
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
SHA-256: 4d01725be822d144cf9a56ade981e6fb920cd7a610b8fc24cc601a920beea5b9
|
SHA-256: 4d01725be822d144cf9a56ade981e6fb920cd7a610b8fc24cc601a920beea5b9
|
||||||
Release: Noto Sans CJK 2.004, tag Sans2.004
|
Release: Noto Sans CJK 2.004, tag Sans2.004
|
||||||
Source: https://raw.githubusercontent.com/notofonts/noto-cjk/Sans2.004/Sans/Mono/NotoSansMonoCJKjp-Regular.otf
|
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: SIL Open Font License 1.1 (OFL-NotoSansCJK.txt)
|
||||||
License source: https://raw.githubusercontent.com/notofonts/noto-cjk/Sans2.004/LICENSE
|
License source: https://raw.githubusercontent.com/notofonts/noto-cjk/523d033d6cb47f4a80c58a35753646f5c3608a78/LICENSE
|
||||||
|
|
||||||
NotoEmoji-Regular.ttf
|
NotoEmoji-Regular.ttf
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
SHA-256: 415dc6290378574135b64c808dc640c1df7531973290c4970c51fdeb849cb0c5
|
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
|
Source: https://raw.githubusercontent.com/googlei18n/noto-emoji/2f1ffdd6fbbd05d6f382138a3d3adcd89c5ce800/fonts/NotoEmoji-Regular.ttf
|
||||||
License: Apache License 2.0 (Apache-2.0-NotoEmoji.txt)
|
Font license: SIL Open Font License 1.1
|
||||||
License source: https://raw.githubusercontent.com/googlei18n/noto-emoji/2f1ffdd6fbbd05d6f382138a3d3adcd89c5ce800/LICENSE
|
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.
|
||||||
|
|||||||
188
ibus.c
188
ibus.c
@@ -12,30 +12,23 @@
|
|||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Maxwatches = 32,
|
|
||||||
Maxconns = 16,
|
Maxconns = 16,
|
||||||
|
Maxwatches = 2*Maxconns + 1,
|
||||||
Maxcontexts = 64,
|
Maxcontexts = 64,
|
||||||
Relmask = 1<<30,
|
Relmask = 1<<30,
|
||||||
Ibussupermask = 1<<26,
|
Ibussupermask = 1<<26,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Watch Watch;
|
|
||||||
struct Watch
|
|
||||||
{
|
|
||||||
DBusWatch *w;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct Ictx Ictx;
|
typedef struct Ictx Ictx;
|
||||||
struct Ictx
|
struct Ictx
|
||||||
{
|
{
|
||||||
DBusConnection *conn;
|
DBusConnection *conn;
|
||||||
char path[64];
|
char path[64];
|
||||||
uvlong owner;
|
|
||||||
int focused;
|
int focused;
|
||||||
Caret caret;
|
Caret caret;
|
||||||
};
|
};
|
||||||
|
|
||||||
static Watch watches[Maxwatches];
|
static DBusWatch *watches[Maxwatches];
|
||||||
static int nwatches;
|
static int nwatches;
|
||||||
static DBusConnection *conns[Maxconns];
|
static DBusConnection *conns[Maxconns];
|
||||||
static int nconns;
|
static int nconns;
|
||||||
@@ -62,24 +55,37 @@ unlinkaddr(void)
|
|||||||
addrowned = 0;
|
addrowned = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
machineidfiles(char *buf, int sz, char **path, int npath)
|
||||||
|
{
|
||||||
|
int fd, i, n;
|
||||||
|
|
||||||
|
for(i = 0; i < npath; i++){
|
||||||
|
fd = open(path[i], 0);
|
||||||
|
if(fd < 0)
|
||||||
|
continue;
|
||||||
|
n = read(fd, buf, sz - 1);
|
||||||
|
close(fd);
|
||||||
|
if(n <= 0)
|
||||||
|
continue;
|
||||||
|
buf[n] = '\0';
|
||||||
|
while(n > 0 && (buf[n-1] == '\n' || buf[n-1] == '\r'))
|
||||||
|
buf[--n] = '\0';
|
||||||
|
if(n > 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buf[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
machineid(char *buf, int sz)
|
machineid(char *buf, int sz)
|
||||||
{
|
{
|
||||||
int fd, n;
|
char *path[] = {
|
||||||
|
"/etc/machine-id",
|
||||||
|
"/var/lib/dbus/machine-id",
|
||||||
|
};
|
||||||
|
|
||||||
fd = open("/etc/machine-id", 0);
|
machineidfiles(buf, sz, path, nelem(path));
|
||||||
if(fd < 0)
|
|
||||||
fd = open("/var/lib/dbus/machine-id", 0);
|
|
||||||
if(fd < 0){
|
|
||||||
buf[0] = '\0';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
n = read(fd, buf, sz - 1);
|
|
||||||
close(fd);
|
|
||||||
if(n < 0) n = 0;
|
|
||||||
buf[n] = '\0';
|
|
||||||
while(n > 0 && (buf[n-1] == '\n' || buf[n-1] == '\r'))
|
|
||||||
buf[--n] = '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -176,14 +182,15 @@ writeaddr(char *path, char *addr)
|
|||||||
fd = -1;
|
fd = -1;
|
||||||
if(fprintf(fp, "IBUS_ADDRESS=%s\n", addr) < 0 ||
|
if(fprintf(fp, "IBUS_ADDRESS=%s\n", addr) < 0 ||
|
||||||
fprintf(fp, "IBUS_DAEMON_PID=%d\n", (int)getpid()) < 0 ||
|
fprintf(fp, "IBUS_DAEMON_PID=%d\n", (int)getpid()) < 0 ||
|
||||||
fflush(fp) < 0 || fsync(fileno(fp)) < 0)
|
fflush(fp) < 0 || fsync(fileno(fp)) < 0 ||
|
||||||
|
fstat(fileno(fp), &st) < 0)
|
||||||
goto out;
|
goto out;
|
||||||
if(fclose(fp) < 0){
|
if(fclose(fp) < 0){
|
||||||
fp = nil;
|
fp = nil;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
fp = nil;
|
fp = nil;
|
||||||
if(rename(tmp, path) < 0 || lstat(path, &st) < 0)
|
if(rename(tmp, path) < 0)
|
||||||
goto out;
|
goto out;
|
||||||
addrdev = st.st_dev;
|
addrdev = st.st_dev;
|
||||||
addrino = st.st_ino;
|
addrino = st.st_ino;
|
||||||
@@ -206,13 +213,13 @@ addwatch(DBusWatch *w, void *_)
|
|||||||
|
|
||||||
USED(_);
|
USED(_);
|
||||||
for(i = 0; i < nwatches; i++)
|
for(i = 0; i < nwatches; i++)
|
||||||
if(watches[i].w == nil){
|
if(watches[i] == nil){
|
||||||
watches[i].w = w;
|
watches[i] = w;
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
if(nwatches >= Maxwatches)
|
if(nwatches >= Maxwatches)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
watches[nwatches++].w = w;
|
watches[nwatches++] = w;
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,8 +230,8 @@ removewatch(DBusWatch *w, void *_)
|
|||||||
|
|
||||||
USED(_);
|
USED(_);
|
||||||
for(i = 0; i < nwatches; i++)
|
for(i = 0; i < nwatches; i++)
|
||||||
if(watches[i].w == w){
|
if(watches[i] == w){
|
||||||
watches[i].w = nil;
|
watches[i] = nil;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,7 +289,6 @@ newcontext(DBusConnection *conn, const char *path)
|
|||||||
if(contexts[i].conn == nil){
|
if(contexts[i].conn == nil){
|
||||||
memset(&contexts[i], 0, sizeof contexts[i]);
|
memset(&contexts[i], 0, sizeof contexts[i]);
|
||||||
contexts[i].conn = conn;
|
contexts[i].conn = conn;
|
||||||
contexts[i].owner = ownernew();
|
|
||||||
strncpy(contexts[i].path, path, sizeof contexts[i].path);
|
strncpy(contexts[i].path, path, sizeof contexts[i].path);
|
||||||
contexts[i].path[sizeof contexts[i].path-1] = '\0';
|
contexts[i].path[sizeof contexts[i].path-1] = '\0';
|
||||||
return &contexts[i];
|
return &contexts[i];
|
||||||
@@ -296,12 +302,11 @@ sendrequest(Ictx *ctx, int op, u32int ks, u32int mod, Keyres *res)
|
|||||||
Keyreq kr;
|
Keyreq kr;
|
||||||
|
|
||||||
memset(&kr, 0, sizeof kr);
|
memset(&kr, 0, sizeof kr);
|
||||||
kr.owner = ctx->owner;
|
kr.owner = ctx;
|
||||||
kr.op = op;
|
kr.op = op;
|
||||||
kr.ks = ks;
|
kr.ks = ks;
|
||||||
kr.mod = mod;
|
kr.mod = mod;
|
||||||
kr.caret = ctx->caret;
|
kr.caret = ctx->caret;
|
||||||
kr.want = op == Keypress;
|
|
||||||
kr.reply = replyc;
|
kr.reply = replyc;
|
||||||
chansend(keyc, &kr);
|
chansend(keyc, &kr);
|
||||||
chanrecv(replyc, res);
|
chanrecv(replyc, res);
|
||||||
@@ -312,11 +317,10 @@ releasecontext(Ictx *ctx)
|
|||||||
{
|
{
|
||||||
Keyres res;
|
Keyres res;
|
||||||
|
|
||||||
if(replyc != nil)
|
if(ctx->focused)
|
||||||
sendrequest(ctx, Keyrelease, 0, 0, &res);
|
sendrequest(ctx, Keyrelease, 0, 0, &res);
|
||||||
ctx->focused = 0;
|
ctx->focused = 0;
|
||||||
memset(&ctx->caret, 0, sizeof ctx->caret);
|
memset(&ctx->caret, 0, sizeof ctx->caret);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -326,6 +330,38 @@ dropcontext(Ictx *ctx)
|
|||||||
memset(ctx, 0, sizeof *ctx);
|
memset(ctx, 0, sizeof *ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dropconncontexts(DBusConnection *conn)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < nelem(contexts); i++)
|
||||||
|
if(contexts[i].conn == conn)
|
||||||
|
dropcontext(&contexts[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
processkey(Ictx *ctx, u32int sym, u32int state, Keyres *res)
|
||||||
|
{
|
||||||
|
if(state & Relmask || !ctx->focused)
|
||||||
|
return 0;
|
||||||
|
sendrequest(ctx, Keypress, kget(sym), mget(state), res);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
setcursor(Ictx *ctx, int x, int y, int h)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
|
||||||
|
ctx->caret.valid = 1;
|
||||||
|
ctx->caret.x = x;
|
||||||
|
ctx->caret.y = y;
|
||||||
|
ctx->caret.h = h;
|
||||||
|
if(ctx->focused)
|
||||||
|
sendrequest(ctx, Keycaret, 0, 0, &res);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
appendibustext(DBusMessageIter *it, const char *s)
|
appendibustext(DBusMessageIter *it, const char *s)
|
||||||
{
|
{
|
||||||
@@ -468,7 +504,6 @@ handlekey(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|||||||
dbus_uint32_t sym, code, state;
|
dbus_uint32_t sym, code, state;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
char commit[Maxutf], preedit[Maxutf];
|
char commit[Maxutf], preedit[Maxutf];
|
||||||
u32int ks, mod;
|
|
||||||
|
|
||||||
dbus_error_init(&err);
|
dbus_error_init(&err);
|
||||||
if(!dbus_message_get_args(m, &err,
|
if(!dbus_message_get_args(m, &err,
|
||||||
@@ -480,11 +515,8 @@ handlekey(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|||||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||||
}
|
}
|
||||||
USED(code);
|
USED(code);
|
||||||
if(state & Relmask || !ctx->focused)
|
if(!processkey(ctx, sym, state, &res))
|
||||||
return handlebool(c, m, 0);
|
return handlebool(c, m, 0);
|
||||||
ks = kget(sym);
|
|
||||||
mod = mget(state);
|
|
||||||
sendrequest(ctx, Keypress, ks, mod, &res);
|
|
||||||
stoutf(&res.commit, commit, sizeof commit);
|
stoutf(&res.commit, commit, sizeof commit);
|
||||||
stoutf(&res.preedit, preedit, sizeof preedit);
|
stoutf(&res.preedit, preedit, sizeof preedit);
|
||||||
if(commit[0] != '\0')
|
if(commit[0] != '\0')
|
||||||
@@ -541,7 +573,6 @@ handlecursor(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|||||||
{
|
{
|
||||||
DBusError err;
|
DBusError err;
|
||||||
dbus_int32_t x, y, w, h;
|
dbus_int32_t x, y, w, h;
|
||||||
Keyres res;
|
|
||||||
|
|
||||||
dbus_error_init(&err);
|
dbus_error_init(&err);
|
||||||
if(!dbus_message_get_args(m, &err,
|
if(!dbus_message_get_args(m, &err,
|
||||||
@@ -555,12 +586,7 @@ handlecursor(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|||||||
if(w < 0 || h < 0)
|
if(w < 0 || h < 0)
|
||||||
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
||||||
"cursor dimensions must not be negative");
|
"cursor dimensions must not be negative");
|
||||||
ctx->caret.valid = 1;
|
setcursor(ctx, x, y, h);
|
||||||
ctx->caret.x = x;
|
|
||||||
ctx->caret.y = y;
|
|
||||||
ctx->caret.h = h;
|
|
||||||
if(ctx->focused)
|
|
||||||
sendrequest(ctx, Keycaret, 0, 0, &res);
|
|
||||||
return handlenoop(c, m);
|
return handlenoop(c, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,18 +696,26 @@ newconn(DBusServer *s, DBusConnection *c, void *_)
|
|||||||
USED(_);
|
USED(_);
|
||||||
if(nconns >= Maxconns)
|
if(nconns >= Maxconns)
|
||||||
return;
|
return;
|
||||||
dbus_connection_ref(c);
|
if(!dbus_connection_set_watch_functions(c, addwatch, removewatch,
|
||||||
dbus_connection_set_watch_functions(c, addwatch, removewatch, togglewatch, nil, nil);
|
togglewatch, nil, nil)){
|
||||||
|
fprintf(stderr, "strans: ibus: cannot watch client connection\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
memset(&vt, 0, sizeof(vt));
|
memset(&vt, 0, sizeof(vt));
|
||||||
vt.message_function = onmsg;
|
vt.message_function = onmsg;
|
||||||
dbus_connection_register_fallback(c, "/", &vt, nil);
|
if(!dbus_connection_register_fallback(c, "/", &vt, nil)){
|
||||||
|
fprintf(stderr, "strans: ibus: cannot register client handler\n");
|
||||||
|
dbus_connection_set_watch_functions(c, nil, nil, nil, nil, nil);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dbus_connection_ref(c);
|
||||||
conns[nconns++] = c;
|
conns[nconns++] = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
pruneconns(void)
|
pruneconns(void)
|
||||||
{
|
{
|
||||||
int i, j, k;
|
int i, j;
|
||||||
|
|
||||||
j = 0;
|
j = 0;
|
||||||
for(i = 0; i < nconns; i++){
|
for(i = 0; i < nconns; i++){
|
||||||
@@ -689,9 +723,7 @@ pruneconns(void)
|
|||||||
conns[j++] = conns[i];
|
conns[j++] = conns[i];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for(k = 0; k < nelem(contexts); k++)
|
dropconncontexts(conns[i]);
|
||||||
if(contexts[k].conn == conns[i])
|
|
||||||
dropcontext(&contexts[k]);
|
|
||||||
dbus_connection_unref(conns[i]);
|
dbus_connection_unref(conns[i]);
|
||||||
}
|
}
|
||||||
nconns = j;
|
nconns = j;
|
||||||
@@ -703,7 +735,10 @@ ibusinit(void)
|
|||||||
DBusError err;
|
DBusError err;
|
||||||
char addr[128];
|
char addr[128];
|
||||||
char *full;
|
char *full;
|
||||||
|
int cleanupregistered;
|
||||||
|
|
||||||
|
full = nil;
|
||||||
|
cleanupregistered = 0;
|
||||||
if(buildaddrpath(addrfile, sizeof(addrfile)) < 0){
|
if(buildaddrpath(addrfile, sizeof(addrfile)) < 0){
|
||||||
fprintf(stderr, "strans: ibus: cannot build address path\n");
|
fprintf(stderr, "strans: ibus: cannot build address path\n");
|
||||||
return -1;
|
return -1;
|
||||||
@@ -722,20 +757,37 @@ ibusinit(void)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
dbus_server_set_new_connection_function(srv, newconn, nil, nil);
|
dbus_server_set_new_connection_function(srv, newconn, nil, nil);
|
||||||
dbus_server_set_watch_functions(srv, addwatch, removewatch, togglewatch, nil, nil);
|
if(!dbus_server_set_watch_functions(srv, addwatch, removewatch,
|
||||||
|
togglewatch, nil, nil)){
|
||||||
|
fprintf(stderr, "strans: ibus: cannot watch server\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
full = dbus_server_get_address(srv);
|
full = dbus_server_get_address(srv);
|
||||||
|
if(full == nil){
|
||||||
|
fprintf(stderr, "strans: ibus: cannot get server address\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if(!atexit(unlinkaddr)){
|
||||||
|
fprintf(stderr, "strans: ibus: cannot register address cleanup\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
cleanupregistered = 1;
|
||||||
if(writeaddr(addrfile, full) < 0){
|
if(writeaddr(addrfile, full) < 0){
|
||||||
fprintf(stderr, "strans: ibus: cannot write %s\n", addrfile);
|
fprintf(stderr, "strans: ibus: cannot write %s\n", addrfile);
|
||||||
dbus_free(full);
|
goto fail;
|
||||||
dbus_server_disconnect(srv);
|
|
||||||
dbus_server_unref(srv);
|
|
||||||
srv = nil;
|
|
||||||
addrfile[0] = '\0';
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
dbus_free(full);
|
dbus_free(full);
|
||||||
atexit(unlinkaddr);
|
|
||||||
return 0;
|
return 0;
|
||||||
|
fail:
|
||||||
|
if(cleanupregistered)
|
||||||
|
atexitdont(unlinkaddr);
|
||||||
|
if(full != nil)
|
||||||
|
dbus_free(full);
|
||||||
|
dbus_server_disconnect(srv);
|
||||||
|
dbus_server_unref(srv);
|
||||||
|
srv = nil;
|
||||||
|
addrfile[0] = '\0';
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -755,17 +807,17 @@ ibusthread(void *_)
|
|||||||
for(;;){
|
for(;;){
|
||||||
n = 0;
|
n = 0;
|
||||||
for(i = 0; i < nwatches && n < Maxwatches; i++){
|
for(i = 0; i < nwatches && n < Maxwatches; i++){
|
||||||
if(watches[i].w == nil)
|
if(watches[i] == nil)
|
||||||
continue;
|
continue;
|
||||||
if(!dbus_watch_get_enabled(watches[i].w))
|
if(!dbus_watch_get_enabled(watches[i]))
|
||||||
continue;
|
continue;
|
||||||
pfds[n].fd = dbus_watch_get_unix_fd(watches[i].w);
|
pfds[n].fd = dbus_watch_get_unix_fd(watches[i]);
|
||||||
pfds[n].events = 0;
|
pfds[n].events = 0;
|
||||||
f = dbus_watch_get_flags(watches[i].w);
|
f = dbus_watch_get_flags(watches[i]);
|
||||||
if(f & DBUS_WATCH_READABLE) pfds[n].events |= POLLIN;
|
if(f & DBUS_WATCH_READABLE) pfds[n].events |= POLLIN;
|
||||||
if(f & DBUS_WATCH_WRITABLE) pfds[n].events |= POLLOUT;
|
if(f & DBUS_WATCH_WRITABLE) pfds[n].events |= POLLOUT;
|
||||||
wi[n] = i;
|
wi[n] = i;
|
||||||
polled[n] = watches[i].w;
|
polled[n] = watches[i];
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
rv = poll(pfds, n, 200);
|
rv = poll(pfds, n, 200);
|
||||||
@@ -774,7 +826,7 @@ ibusthread(void *_)
|
|||||||
for(i = 0; i < n; i++){
|
for(i = 0; i < n; i++){
|
||||||
if(pfds[i].revents == 0)
|
if(pfds[i].revents == 0)
|
||||||
continue;
|
continue;
|
||||||
if(watches[wi[i]].w != polled[i])
|
if(watches[wi[i]] != polled[i])
|
||||||
continue;
|
continue;
|
||||||
f = 0;
|
f = 0;
|
||||||
if(pfds[i].revents & POLLIN) f |= DBUS_WATCH_READABLE;
|
if(pfds[i].revents & POLLIN) f |= DBUS_WATCH_READABLE;
|
||||||
|
|||||||
@@ -1,955 +0,0 @@
|
|||||||
/* Generated by wayland-scanner 1.21.0 */
|
|
||||||
|
|
||||||
#ifndef INPUT_METHOD_UNSTABLE_V2_CLIENT_PROTOCOL_H
|
|
||||||
#define INPUT_METHOD_UNSTABLE_V2_CLIENT_PROTOCOL_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include "wayland-client.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @page page_input_method_unstable_v2 The input_method_unstable_v2 protocol
|
|
||||||
* Protocol for creating input methods
|
|
||||||
*
|
|
||||||
* @section page_desc_input_method_unstable_v2 Description
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @section page_ifaces_input_method_unstable_v2 Interfaces
|
|
||||||
* - @subpage page_iface_zwp_input_method_v2 - input method
|
|
||||||
* - @subpage page_iface_zwp_input_popup_surface_v2 - popup surface
|
|
||||||
* - @subpage page_iface_zwp_input_method_keyboard_grab_v2 - keyboard grab
|
|
||||||
* - @subpage page_iface_zwp_input_method_manager_v2 - input method manager
|
|
||||||
* @section page_copyright_input_method_unstable_v2 Copyright
|
|
||||||
* <pre>
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
struct wl_seat;
|
|
||||||
struct wl_surface;
|
|
||||||
struct zwp_input_method_keyboard_grab_v2;
|
|
||||||
struct zwp_input_method_manager_v2;
|
|
||||||
struct zwp_input_method_v2;
|
|
||||||
struct zwp_input_popup_surface_v2;
|
|
||||||
|
|
||||||
#ifndef ZWP_INPUT_METHOD_V2_INTERFACE
|
|
||||||
#define ZWP_INPUT_METHOD_V2_INTERFACE
|
|
||||||
/**
|
|
||||||
* @page page_iface_zwp_input_method_v2 zwp_input_method_v2
|
|
||||||
* @section page_iface_zwp_input_method_v2_desc Description
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* @section page_iface_zwp_input_method_v2_api API
|
|
||||||
* See @ref iface_zwp_input_method_v2.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @defgroup iface_zwp_input_method_v2 The zwp_input_method_v2 interface
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
extern const struct wl_interface zwp_input_method_v2_interface;
|
|
||||||
#endif
|
|
||||||
#ifndef ZWP_INPUT_POPUP_SURFACE_V2_INTERFACE
|
|
||||||
#define ZWP_INPUT_POPUP_SURFACE_V2_INTERFACE
|
|
||||||
/**
|
|
||||||
* @page page_iface_zwp_input_popup_surface_v2 zwp_input_popup_surface_v2
|
|
||||||
* @section page_iface_zwp_input_popup_surface_v2_desc Description
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* @section page_iface_zwp_input_popup_surface_v2_api API
|
|
||||||
* See @ref iface_zwp_input_popup_surface_v2.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @defgroup iface_zwp_input_popup_surface_v2 The zwp_input_popup_surface_v2 interface
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
extern const struct wl_interface zwp_input_popup_surface_v2_interface;
|
|
||||||
#endif
|
|
||||||
#ifndef ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_INTERFACE
|
|
||||||
#define ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_INTERFACE
|
|
||||||
/**
|
|
||||||
* @page page_iface_zwp_input_method_keyboard_grab_v2 zwp_input_method_keyboard_grab_v2
|
|
||||||
* @section page_iface_zwp_input_method_keyboard_grab_v2_desc Description
|
|
||||||
*
|
|
||||||
* The zwp_input_method_keyboard_grab_v2 interface represents an exclusive
|
|
||||||
* grab of the wl_keyboard interface associated with the seat.
|
|
||||||
* @section page_iface_zwp_input_method_keyboard_grab_v2_api API
|
|
||||||
* See @ref iface_zwp_input_method_keyboard_grab_v2.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @defgroup iface_zwp_input_method_keyboard_grab_v2 The zwp_input_method_keyboard_grab_v2 interface
|
|
||||||
*
|
|
||||||
* The zwp_input_method_keyboard_grab_v2 interface represents an exclusive
|
|
||||||
* grab of the wl_keyboard interface associated with the seat.
|
|
||||||
*/
|
|
||||||
extern const struct wl_interface zwp_input_method_keyboard_grab_v2_interface;
|
|
||||||
#endif
|
|
||||||
#ifndef ZWP_INPUT_METHOD_MANAGER_V2_INTERFACE
|
|
||||||
#define ZWP_INPUT_METHOD_MANAGER_V2_INTERFACE
|
|
||||||
/**
|
|
||||||
* @page page_iface_zwp_input_method_manager_v2 zwp_input_method_manager_v2
|
|
||||||
* @section page_iface_zwp_input_method_manager_v2_desc Description
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* @section page_iface_zwp_input_method_manager_v2_api API
|
|
||||||
* See @ref iface_zwp_input_method_manager_v2.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @defgroup iface_zwp_input_method_manager_v2 The zwp_input_method_manager_v2 interface
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
extern const struct wl_interface zwp_input_method_manager_v2_interface;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef ZWP_INPUT_METHOD_V2_ERROR_ENUM
|
|
||||||
#define ZWP_INPUT_METHOD_V2_ERROR_ENUM
|
|
||||||
enum zwp_input_method_v2_error {
|
|
||||||
/**
|
|
||||||
* wl_surface has another role
|
|
||||||
*/
|
|
||||||
ZWP_INPUT_METHOD_V2_ERROR_ROLE = 0,
|
|
||||||
};
|
|
||||||
#endif /* ZWP_INPUT_METHOD_V2_ERROR_ENUM */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
* @struct zwp_input_method_v2_listener
|
|
||||||
*/
|
|
||||||
struct zwp_input_method_v2_listener {
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*activate)(void *data,
|
|
||||||
struct zwp_input_method_v2 *zwp_input_method_v2);
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*deactivate)(void *data,
|
|
||||||
struct zwp_input_method_v2 *zwp_input_method_v2);
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*surrounding_text)(void *data,
|
|
||||||
struct zwp_input_method_v2 *zwp_input_method_v2,
|
|
||||||
const char *text,
|
|
||||||
uint32_t cursor,
|
|
||||||
uint32_t anchor);
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*text_change_cause)(void *data,
|
|
||||||
struct zwp_input_method_v2 *zwp_input_method_v2,
|
|
||||||
uint32_t cause);
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*content_type)(void *data,
|
|
||||||
struct zwp_input_method_v2 *zwp_input_method_v2,
|
|
||||||
uint32_t hint,
|
|
||||||
uint32_t purpose);
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*done)(void *data,
|
|
||||||
struct zwp_input_method_v2 *zwp_input_method_v2);
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*unavailable)(void *data,
|
|
||||||
struct zwp_input_method_v2 *zwp_input_method_v2);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
static inline int
|
|
||||||
zwp_input_method_v2_add_listener(struct zwp_input_method_v2 *zwp_input_method_v2,
|
|
||||||
const struct zwp_input_method_v2_listener *listener, void *data)
|
|
||||||
{
|
|
||||||
return wl_proxy_add_listener((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
(void (**)(void)) listener, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ZWP_INPUT_METHOD_V2_COMMIT_STRING 0
|
|
||||||
#define ZWP_INPUT_METHOD_V2_SET_PREEDIT_STRING 1
|
|
||||||
#define ZWP_INPUT_METHOD_V2_DELETE_SURROUNDING_TEXT 2
|
|
||||||
#define ZWP_INPUT_METHOD_V2_COMMIT 3
|
|
||||||
#define ZWP_INPUT_METHOD_V2_GET_INPUT_POPUP_SURFACE 4
|
|
||||||
#define ZWP_INPUT_METHOD_V2_GRAB_KEYBOARD 5
|
|
||||||
#define ZWP_INPUT_METHOD_V2_DESTROY 6
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_ACTIVATE_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_DEACTIVATE_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_SURROUNDING_TEXT_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_TEXT_CHANGE_CAUSE_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_CONTENT_TYPE_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_DONE_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_UNAVAILABLE_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_COMMIT_STRING_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_SET_PREEDIT_STRING_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_DELETE_SURROUNDING_TEXT_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_COMMIT_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_GET_INPUT_POPUP_SURFACE_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_GRAB_KEYBOARD_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_V2_DESTROY_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_method_v2 */
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_v2_set_user_data(struct zwp_input_method_v2 *zwp_input_method_v2, void *user_data)
|
|
||||||
{
|
|
||||||
wl_proxy_set_user_data((struct wl_proxy *) zwp_input_method_v2, user_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_method_v2 */
|
|
||||||
static inline void *
|
|
||||||
zwp_input_method_v2_get_user_data(struct zwp_input_method_v2 *zwp_input_method_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_user_data((struct wl_proxy *) zwp_input_method_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t
|
|
||||||
zwp_input_method_v2_get_version(struct zwp_input_method_v2 *zwp_input_method_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_v2_commit_string(struct zwp_input_method_v2 *zwp_input_method_v2, const char *text)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
ZWP_INPUT_METHOD_V2_COMMIT_STRING, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2), 0, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_v2_set_preedit_string(struct zwp_input_method_v2 *zwp_input_method_v2, const char *text, int32_t cursor_begin, int32_t cursor_end)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
ZWP_INPUT_METHOD_V2_SET_PREEDIT_STRING, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2), 0, text, cursor_begin, cursor_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_v2_delete_surrounding_text(struct zwp_input_method_v2 *zwp_input_method_v2, uint32_t before_length, uint32_t after_length)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
ZWP_INPUT_METHOD_V2_DELETE_SURROUNDING_TEXT, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2), 0, before_length, after_length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_v2_commit(struct zwp_input_method_v2 *zwp_input_method_v2, uint32_t serial)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
ZWP_INPUT_METHOD_V2_COMMIT, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2), 0, serial);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline struct zwp_input_popup_surface_v2 *
|
|
||||||
zwp_input_method_v2_get_input_popup_surface(struct zwp_input_method_v2 *zwp_input_method_v2, struct wl_surface *surface)
|
|
||||||
{
|
|
||||||
struct wl_proxy *id;
|
|
||||||
|
|
||||||
id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
ZWP_INPUT_METHOD_V2_GET_INPUT_POPUP_SURFACE, &zwp_input_popup_surface_v2_interface, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2), 0, NULL, surface);
|
|
||||||
|
|
||||||
return (struct zwp_input_popup_surface_v2 *) id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline struct zwp_input_method_keyboard_grab_v2 *
|
|
||||||
zwp_input_method_v2_grab_keyboard(struct zwp_input_method_v2 *zwp_input_method_v2)
|
|
||||||
{
|
|
||||||
struct wl_proxy *keyboard;
|
|
||||||
|
|
||||||
keyboard = wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
ZWP_INPUT_METHOD_V2_GRAB_KEYBOARD, &zwp_input_method_keyboard_grab_v2_interface, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2), 0, NULL);
|
|
||||||
|
|
||||||
return (struct zwp_input_method_keyboard_grab_v2 *) keyboard;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_v2
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_v2_destroy(struct zwp_input_method_v2 *zwp_input_method_v2)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_v2,
|
|
||||||
ZWP_INPUT_METHOD_V2_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_v2), WL_MARSHAL_FLAG_DESTROY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_popup_surface_v2
|
|
||||||
* @struct zwp_input_popup_surface_v2_listener
|
|
||||||
*/
|
|
||||||
struct zwp_input_popup_surface_v2_listener {
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
void (*text_input_rectangle)(void *data,
|
|
||||||
struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2,
|
|
||||||
int32_t x,
|
|
||||||
int32_t y,
|
|
||||||
int32_t width,
|
|
||||||
int32_t height);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_popup_surface_v2
|
|
||||||
*/
|
|
||||||
static inline int
|
|
||||||
zwp_input_popup_surface_v2_add_listener(struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2,
|
|
||||||
const struct zwp_input_popup_surface_v2_listener *listener, void *data)
|
|
||||||
{
|
|
||||||
return wl_proxy_add_listener((struct wl_proxy *) zwp_input_popup_surface_v2,
|
|
||||||
(void (**)(void)) listener, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ZWP_INPUT_POPUP_SURFACE_V2_DESTROY 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_popup_surface_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_POPUP_SURFACE_V2_TEXT_INPUT_RECTANGLE_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_popup_surface_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_POPUP_SURFACE_V2_DESTROY_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_popup_surface_v2 */
|
|
||||||
static inline void
|
|
||||||
zwp_input_popup_surface_v2_set_user_data(struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2, void *user_data)
|
|
||||||
{
|
|
||||||
wl_proxy_set_user_data((struct wl_proxy *) zwp_input_popup_surface_v2, user_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_popup_surface_v2 */
|
|
||||||
static inline void *
|
|
||||||
zwp_input_popup_surface_v2_get_user_data(struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_user_data((struct wl_proxy *) zwp_input_popup_surface_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t
|
|
||||||
zwp_input_popup_surface_v2_get_version(struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_version((struct wl_proxy *) zwp_input_popup_surface_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_popup_surface_v2
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_popup_surface_v2_destroy(struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_popup_surface_v2,
|
|
||||||
ZWP_INPUT_POPUP_SURFACE_V2_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_popup_surface_v2), WL_MARSHAL_FLAG_DESTROY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
* @struct zwp_input_method_keyboard_grab_v2_listener
|
|
||||||
*/
|
|
||||||
struct zwp_input_method_keyboard_grab_v2_listener {
|
|
||||||
/**
|
|
||||||
* keyboard mapping
|
|
||||||
*
|
|
||||||
* This event provides a file descriptor to the client which can
|
|
||||||
* be memory-mapped to provide a keyboard mapping description.
|
|
||||||
* @param format keymap format
|
|
||||||
* @param fd keymap file descriptor
|
|
||||||
* @param size keymap size, in bytes
|
|
||||||
*/
|
|
||||||
void (*keymap)(void *data,
|
|
||||||
struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2,
|
|
||||||
uint32_t format,
|
|
||||||
int32_t fd,
|
|
||||||
uint32_t size);
|
|
||||||
/**
|
|
||||||
* key event
|
|
||||||
*
|
|
||||||
* A key was pressed or released. The time argument is a
|
|
||||||
* timestamp with millisecond granularity, with an undefined base.
|
|
||||||
* @param serial serial number of the key event
|
|
||||||
* @param time timestamp with millisecond granularity
|
|
||||||
* @param key key that produced the event
|
|
||||||
* @param state physical state of the key
|
|
||||||
*/
|
|
||||||
void (*key)(void *data,
|
|
||||||
struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2,
|
|
||||||
uint32_t serial,
|
|
||||||
uint32_t time,
|
|
||||||
uint32_t key,
|
|
||||||
uint32_t state);
|
|
||||||
/**
|
|
||||||
* modifier and group state
|
|
||||||
*
|
|
||||||
* Notifies clients that the modifier and/or group state has
|
|
||||||
* changed, and it should update its local state.
|
|
||||||
* @param serial serial number of the modifiers event
|
|
||||||
* @param mods_depressed depressed modifiers
|
|
||||||
* @param mods_latched latched modifiers
|
|
||||||
* @param mods_locked locked modifiers
|
|
||||||
* @param group keyboard layout
|
|
||||||
*/
|
|
||||||
void (*modifiers)(void *data,
|
|
||||||
struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2,
|
|
||||||
uint32_t serial,
|
|
||||||
uint32_t mods_depressed,
|
|
||||||
uint32_t mods_latched,
|
|
||||||
uint32_t mods_locked,
|
|
||||||
uint32_t group);
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
* @param rate the rate of repeating keys in characters per second
|
|
||||||
* @param delay delay in milliseconds since key down until repeating starts
|
|
||||||
*/
|
|
||||||
void (*repeat_info)(void *data,
|
|
||||||
struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2,
|
|
||||||
int32_t rate,
|
|
||||||
int32_t delay);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
*/
|
|
||||||
static inline int
|
|
||||||
zwp_input_method_keyboard_grab_v2_add_listener(struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2,
|
|
||||||
const struct zwp_input_method_keyboard_grab_v2_listener *listener, void *data)
|
|
||||||
{
|
|
||||||
return wl_proxy_add_listener((struct wl_proxy *) zwp_input_method_keyboard_grab_v2,
|
|
||||||
(void (**)(void)) listener, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_RELEASE 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_KEYMAP_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_KEY_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_MODIFIERS_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_REPEAT_INFO_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_RELEASE_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_method_keyboard_grab_v2 */
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_keyboard_grab_v2_set_user_data(struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2, void *user_data)
|
|
||||||
{
|
|
||||||
wl_proxy_set_user_data((struct wl_proxy *) zwp_input_method_keyboard_grab_v2, user_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_method_keyboard_grab_v2 */
|
|
||||||
static inline void *
|
|
||||||
zwp_input_method_keyboard_grab_v2_get_user_data(struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_user_data((struct wl_proxy *) zwp_input_method_keyboard_grab_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t
|
|
||||||
zwp_input_method_keyboard_grab_v2_get_version(struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_version((struct wl_proxy *) zwp_input_method_keyboard_grab_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_method_keyboard_grab_v2 */
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_keyboard_grab_v2_destroy(struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2)
|
|
||||||
{
|
|
||||||
wl_proxy_destroy((struct wl_proxy *) zwp_input_method_keyboard_grab_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_keyboard_grab_v2
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_keyboard_grab_v2_release(struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_keyboard_grab_v2,
|
|
||||||
ZWP_INPUT_METHOD_KEYBOARD_GRAB_V2_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_keyboard_grab_v2), WL_MARSHAL_FLAG_DESTROY);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ZWP_INPUT_METHOD_MANAGER_V2_GET_INPUT_METHOD 0
|
|
||||||
#define ZWP_INPUT_METHOD_MANAGER_V2_DESTROY 1
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_manager_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_MANAGER_V2_GET_INPUT_METHOD_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_manager_v2
|
|
||||||
*/
|
|
||||||
#define ZWP_INPUT_METHOD_MANAGER_V2_DESTROY_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_method_manager_v2 */
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_manager_v2_set_user_data(struct zwp_input_method_manager_v2 *zwp_input_method_manager_v2, void *user_data)
|
|
||||||
{
|
|
||||||
wl_proxy_set_user_data((struct wl_proxy *) zwp_input_method_manager_v2, user_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_input_method_manager_v2 */
|
|
||||||
static inline void *
|
|
||||||
zwp_input_method_manager_v2_get_user_data(struct zwp_input_method_manager_v2 *zwp_input_method_manager_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_user_data((struct wl_proxy *) zwp_input_method_manager_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t
|
|
||||||
zwp_input_method_manager_v2_get_version(struct zwp_input_method_manager_v2 *zwp_input_method_manager_v2)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_version((struct wl_proxy *) zwp_input_method_manager_v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_manager_v2
|
|
||||||
*
|
|
||||||
* Request a new input zwp_input_method_v2 object associated with a given
|
|
||||||
* seat.
|
|
||||||
*/
|
|
||||||
static inline struct zwp_input_method_v2 *
|
|
||||||
zwp_input_method_manager_v2_get_input_method(struct zwp_input_method_manager_v2 *zwp_input_method_manager_v2, struct wl_seat *seat)
|
|
||||||
{
|
|
||||||
struct wl_proxy *input_method;
|
|
||||||
|
|
||||||
input_method = wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_manager_v2,
|
|
||||||
ZWP_INPUT_METHOD_MANAGER_V2_GET_INPUT_METHOD, &zwp_input_method_v2_interface, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_manager_v2), 0, seat, NULL);
|
|
||||||
|
|
||||||
return (struct zwp_input_method_v2 *) input_method;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_input_method_manager_v2
|
|
||||||
*
|
|
||||||
* Destroys the zwp_input_method_manager_v2 object.
|
|
||||||
*
|
|
||||||
* The zwp_input_method_v2 objects originating from it remain valid.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_input_method_manager_v2_destroy(struct zwp_input_method_manager_v2 *zwp_input_method_manager_v2)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_input_method_manager_v2,
|
|
||||||
ZWP_INPUT_METHOD_MANAGER_V2_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_input_method_manager_v2), WL_MARSHAL_FLAG_DESTROY);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
/* Generated by wayland-scanner 1.21.0 */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "wayland-util.h"
|
|
||||||
|
|
||||||
#ifndef __has_attribute
|
|
||||||
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
|
|
||||||
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
|
|
||||||
#else
|
|
||||||
#define WL_PRIVATE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern const struct wl_interface wl_seat_interface;
|
|
||||||
extern const struct wl_interface wl_surface_interface;
|
|
||||||
extern const struct wl_interface zwp_input_method_keyboard_grab_v2_interface;
|
|
||||||
extern const struct wl_interface zwp_input_method_v2_interface;
|
|
||||||
extern const struct wl_interface zwp_input_popup_surface_v2_interface;
|
|
||||||
|
|
||||||
static const struct wl_interface *input_method_unstable_v2_types[] = {
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
&zwp_input_popup_surface_v2_interface,
|
|
||||||
&wl_surface_interface,
|
|
||||||
&zwp_input_method_keyboard_grab_v2_interface,
|
|
||||||
&wl_seat_interface,
|
|
||||||
&zwp_input_method_v2_interface,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_input_method_v2_requests[] = {
|
|
||||||
{ "commit_string", "s", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "set_preedit_string", "sii", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "delete_surrounding_text", "uu", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "commit", "u", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "get_input_popup_surface", "no", input_method_unstable_v2_types + 5 },
|
|
||||||
{ "grab_keyboard", "n", input_method_unstable_v2_types + 7 },
|
|
||||||
{ "destroy", "", input_method_unstable_v2_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_input_method_v2_events[] = {
|
|
||||||
{ "activate", "", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "deactivate", "", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "surrounding_text", "suu", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "text_change_cause", "u", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "content_type", "uu", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "done", "", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "unavailable", "", input_method_unstable_v2_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
WL_PRIVATE const struct wl_interface zwp_input_method_v2_interface = {
|
|
||||||
"zwp_input_method_v2", 1,
|
|
||||||
7, zwp_input_method_v2_requests,
|
|
||||||
7, zwp_input_method_v2_events,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_input_popup_surface_v2_requests[] = {
|
|
||||||
{ "destroy", "", input_method_unstable_v2_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_input_popup_surface_v2_events[] = {
|
|
||||||
{ "text_input_rectangle", "iiii", input_method_unstable_v2_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
WL_PRIVATE const struct wl_interface zwp_input_popup_surface_v2_interface = {
|
|
||||||
"zwp_input_popup_surface_v2", 1,
|
|
||||||
1, zwp_input_popup_surface_v2_requests,
|
|
||||||
1, zwp_input_popup_surface_v2_events,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_input_method_keyboard_grab_v2_requests[] = {
|
|
||||||
{ "release", "", input_method_unstable_v2_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_input_method_keyboard_grab_v2_events[] = {
|
|
||||||
{ "keymap", "uhu", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "key", "uuuu", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "modifiers", "uuuuu", input_method_unstable_v2_types + 0 },
|
|
||||||
{ "repeat_info", "ii", input_method_unstable_v2_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
WL_PRIVATE const struct wl_interface zwp_input_method_keyboard_grab_v2_interface = {
|
|
||||||
"zwp_input_method_keyboard_grab_v2", 1,
|
|
||||||
1, zwp_input_method_keyboard_grab_v2_requests,
|
|
||||||
4, zwp_input_method_keyboard_grab_v2_events,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_input_method_manager_v2_requests[] = {
|
|
||||||
{ "get_input_method", "on", input_method_unstable_v2_types + 8 },
|
|
||||||
{ "destroy", "", input_method_unstable_v2_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
WL_PRIVATE const struct wl_interface zwp_input_method_manager_v2_interface = {
|
|
||||||
"zwp_input_method_manager_v2", 1,
|
|
||||||
2, zwp_input_method_manager_v2_requests,
|
|
||||||
0, NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
4
ipc.h
4
ipc.h
@@ -4,8 +4,8 @@
|
|||||||
/*
|
/*
|
||||||
* Request: [flags, modifiers, key byte 0, ..., key byte 3].
|
* Request: [flags, modifiers, key byte 0, ..., key byte 3].
|
||||||
* Flags request preedit and distinguish lifecycle reset from physical Escape.
|
* Flags request preedit and distinguish lifecycle reset from physical Escape.
|
||||||
* The server assigns the connection's owner generation; it is not sent on
|
* The server identifies the connection as the engine owner; that identity is
|
||||||
* the wire.
|
* not sent on the wire.
|
||||||
* Only Mmask modifier bits are retained.
|
* Only Mmask modifier bits are retained.
|
||||||
* Response: [eaten, commit-length-low, commit-length-high, commit...],
|
* Response: [eaten, commit-length-low, commit-length-high, commit...],
|
||||||
* followed, when requested, by
|
* followed, when requested, by
|
||||||
|
|||||||
13
main.c
13
main.c
@@ -5,12 +5,13 @@ Channel *drawc;
|
|||||||
Channel *keyc;
|
Channel *keyc;
|
||||||
Channel *dictreqc;
|
Channel *dictreqc;
|
||||||
Channel *dictresc;
|
Channel *dictresc;
|
||||||
char *fontdir;
|
char **fontfiles;
|
||||||
|
int nfontfiles;
|
||||||
|
|
||||||
void
|
void
|
||||||
usage(void)
|
usage(void)
|
||||||
{
|
{
|
||||||
fprint(2, "usage: strans mapdir fontdir\n");
|
fprint(2, "usage: strans mapdir fontfile [fontfile ...]\n");
|
||||||
threadexitsall("usage");
|
threadexitsall("usage");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,24 +51,24 @@ erealloc(void *p, ulong n)
|
|||||||
void
|
void
|
||||||
threadmain(int argc, char **argv)
|
threadmain(int argc, char **argv)
|
||||||
{
|
{
|
||||||
if(argc != 3)
|
if(argc < 3 || argc > Maxfonts + 2)
|
||||||
usage();
|
usage();
|
||||||
|
|
||||||
fontdir = argv[2];
|
fontfiles = argv + 2;
|
||||||
|
nfontfiles = argc - 2;
|
||||||
drawc = chancreate(sizeof(Drawcmd), 4);
|
drawc = chancreate(sizeof(Drawcmd), 4);
|
||||||
keyc = chancreate(sizeof(Keyreq), 0);
|
keyc = chancreate(sizeof(Keyreq), 0);
|
||||||
dictreqc = chancreate(sizeof(Dictreq), 4);
|
dictreqc = chancreate(sizeof(Dictreq), 4);
|
||||||
dictresc = chancreate(sizeof(Dictres), 0);
|
dictresc = chancreate(sizeof(Dictres), 0);
|
||||||
mapinit(argv[1]);
|
mapinit(argv[1]);
|
||||||
dictinit(argv[1]);
|
dictinit(argv[1]);
|
||||||
|
srvinit();
|
||||||
if(proccreate(drawthread, nil, 16384) < 0)
|
if(proccreate(drawthread, nil, 16384) < 0)
|
||||||
die("can't create draw worker");
|
die("can't create draw worker");
|
||||||
if(proccreate(srvthread, nil, 16384) < 0)
|
if(proccreate(srvthread, nil, 16384) < 0)
|
||||||
die("can't create server worker");
|
die("can't create server worker");
|
||||||
if(proccreate(ibusthread, nil, 32768) < 0)
|
if(proccreate(ibusthread, nil, 32768) < 0)
|
||||||
die("can't create IBus worker");
|
die("can't create IBus worker");
|
||||||
if(proccreate(waylandthread, nil, 32768) < 0)
|
|
||||||
die("can't create Wayland worker");
|
|
||||||
if(threadcreate(dictthread, nil, 16384) < 0)
|
if(threadcreate(dictthread, nil, 16384) < 0)
|
||||||
die("can't create dictionary worker");
|
die("can't create dictionary worker");
|
||||||
imthread(nil);
|
imthread(nil);
|
||||||
|
|||||||
136
run.sh
136
run.sh
@@ -1,8 +1,134 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")" || exit 1
|
||||||
pkill strans
|
|
||||||
sleep 1
|
if test "$#" -eq 0; then
|
||||||
./strans map font &
|
set --
|
||||||
sleep 1
|
for font in \
|
||||||
|
/usr/share/fonts/TTF/DejaVuSans.ttf \
|
||||||
|
/usr/share/fonts/TTF/Jigmo.ttf \
|
||||||
|
/usr/share/fonts/TTF/Jigmo2.ttf \
|
||||||
|
/usr/share/fonts/noto/NotoSansCJK-Regular.ttc
|
||||||
|
do
|
||||||
|
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
|
||||||
|
|
||||||
|
if ! test -x ./strans; then
|
||||||
|
echo "run.sh: ./strans is not executable; run make first" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if test -n "${DISPLAY-}" && ! test -x xim/strans-xim; then
|
||||||
|
echo "run.sh: xim/strans-xim is not executable; run make first" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case ${XDG_RUNTIME_DIR-} in
|
||||||
|
/) ipc=/strans.sock ;;
|
||||||
|
/*/) ipc=${XDG_RUNTIME_DIR}strans.sock ;;
|
||||||
|
/*) ipc=${XDG_RUNTIME_DIR}/strans.sock ;;
|
||||||
|
*) ipc=/tmp/strans.$(id -u) ;;
|
||||||
|
esac
|
||||||
|
if test -n "${XDG_CONFIG_HOME-}"; then
|
||||||
|
busdir=$XDG_CONFIG_HOME/ibus/bus
|
||||||
|
elif test "${HOME+x}" = x; then
|
||||||
|
busdir=$HOME/.config/ibus/bus
|
||||||
|
else
|
||||||
|
echo "run.sh: HOME or XDG_CONFIG_HOME is required for IBus discovery" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
strans_pid=
|
||||||
|
xim_pid=
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup 0
|
||||||
|
trap 'exit 129' 1
|
||||||
|
trap 'exit 130' 2
|
||||||
|
trap 'exit 143' 15
|
||||||
|
|
||||||
|
./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/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"
|
||||||
|
|||||||
36
srv.c
36
srv.c
@@ -7,14 +7,7 @@
|
|||||||
static char adir[256];
|
static char adir[256];
|
||||||
static Channel *clientc;
|
static Channel *clientc;
|
||||||
|
|
||||||
typedef struct Client Client;
|
static int
|
||||||
struct Client
|
|
||||||
{
|
|
||||||
int fd;
|
|
||||||
uvlong owner;
|
|
||||||
};
|
|
||||||
|
|
||||||
int
|
|
||||||
srvreadkey(int fd, Keyreq *kr)
|
srvreadkey(int fd, Keyreq *kr)
|
||||||
{
|
{
|
||||||
uchar req[Ipcreqsz];
|
uchar req[Ipcreqsz];
|
||||||
@@ -27,51 +20,46 @@ srvreadkey(int fd, Keyreq *kr)
|
|||||||
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
|
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
|
||||||
kr->ks = ks;
|
kr->ks = ks;
|
||||||
kr->mod = mod;
|
kr->mod = mod;
|
||||||
kr->want = want;
|
|
||||||
memset(&kr->caret, 0, sizeof kr->caret);
|
memset(&kr->caret, 0, sizeof kr->caret);
|
||||||
return 0;
|
return want;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
clientthread(void *arg)
|
clientthread(void *arg)
|
||||||
{
|
{
|
||||||
Client *client;
|
|
||||||
Channel *reply;
|
Channel *reply;
|
||||||
int fd;
|
int fd;
|
||||||
Keyreq kr;
|
Keyreq kr;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
uchar out[Ipcmaxresp];
|
uchar out[Ipcmaxresp];
|
||||||
char commit[Maxutf], preedit[Maxutf];
|
char commit[Maxutf], preedit[Maxutf];
|
||||||
int n, ncommit, npreedit;
|
int n, ncommit, npreedit, want;
|
||||||
uchar token;
|
uchar token;
|
||||||
|
|
||||||
client = arg;
|
fd = (int)(uintptr)arg;
|
||||||
fd = client->fd;
|
|
||||||
threadsetname("client %d", fd);
|
threadsetname("client %d", fd);
|
||||||
reply = chancreate(sizeof(Keyres), 0);
|
reply = chancreate(sizeof(Keyres), 0);
|
||||||
kr.reply = reply;
|
kr.reply = reply;
|
||||||
kr.owner = client->owner;
|
kr.owner = &fd;
|
||||||
while(srvreadkey(fd, &kr) == 0){
|
while((want = srvreadkey(fd, &kr)) >= 0){
|
||||||
chansend(keyc, &kr);
|
chansend(keyc, &kr);
|
||||||
chanrecv(reply, &res);
|
chanrecv(reply, &res);
|
||||||
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,
|
n = ipcpackresp(out, sizeof out, res.eaten,
|
||||||
commit, ncommit, preedit, npreedit, kr.want);
|
commit, ncommit, preedit, npreedit, want);
|
||||||
if(n < 0 || ipcsend(fd, out, n) < 0)
|
if(n < 0 || ipcsend(fd, out, n) < 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
kr.op = Keyrelease;
|
kr.op = Keyrelease;
|
||||||
kr.want = 0;
|
|
||||||
chansend(keyc, &kr);
|
chansend(keyc, &kr);
|
||||||
chanrecv(reply, &res);
|
chanrecv(reply, &res);
|
||||||
chanfree(reply);
|
chanfree(reply);
|
||||||
close(fd);
|
close(fd);
|
||||||
free(client);
|
|
||||||
chanrecv(clientc, &token);
|
chanrecv(clientc, &token);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
void
|
||||||
srvinit(void)
|
srvinit(void)
|
||||||
{
|
{
|
||||||
char *addr, path[sizeof(((struct sockaddr_un*)0)->sun_path)];
|
char *addr, path[sizeof(((struct sockaddr_un*)0)->sun_path)];
|
||||||
@@ -91,13 +79,11 @@ srvinit(void)
|
|||||||
void
|
void
|
||||||
srvthread(void*)
|
srvthread(void*)
|
||||||
{
|
{
|
||||||
Client *client;
|
|
||||||
char ldir[40];
|
char ldir[40];
|
||||||
int fd;
|
int fd;
|
||||||
uchar token;
|
uchar token;
|
||||||
|
|
||||||
threadsetname("srv");
|
threadsetname("srv");
|
||||||
srvinit();
|
|
||||||
token = 0;
|
token = 0;
|
||||||
clientc = chancreate(sizeof token, Maxclients);
|
clientc = chancreate(sizeof token, Maxclients);
|
||||||
for(;;){
|
for(;;){
|
||||||
@@ -108,13 +94,9 @@ srvthread(void*)
|
|||||||
close(fd);
|
close(fd);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
client = emalloc(sizeof *client);
|
if(proccreate(clientthread, (void*)(uintptr)fd, 8192) < 0){
|
||||||
client->fd = fd;
|
|
||||||
client->owner = ownernew();
|
|
||||||
if(proccreate(clientthread, client, 8192) < 0){
|
|
||||||
chanrecv(clientc, &token);
|
chanrecv(clientc, &token);
|
||||||
close(fd);
|
close(fd);
|
||||||
free(client);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5079
stb_truetype.h
5079
stb_truetype.h
File diff suppressed because it is too large
Load Diff
27
strans.c
27
strans.c
@@ -4,9 +4,7 @@
|
|||||||
static Im im;
|
static Im im;
|
||||||
static int popup = 0;
|
static int popup = 0;
|
||||||
static int visible = 0;
|
static int visible = 0;
|
||||||
static Lock ownerlock;
|
static void *activeowner;
|
||||||
static uvlong ownerctr;
|
|
||||||
static uvlong activeowner;
|
|
||||||
static Caret caret;
|
static Caret caret;
|
||||||
static void dictqmap(Im*);
|
static void dictqmap(Im*);
|
||||||
static void dictqjp(Im*);
|
static void dictqjp(Im*);
|
||||||
@@ -22,19 +20,6 @@ struct Search
|
|||||||
};
|
};
|
||||||
static Search search;
|
static Search search;
|
||||||
|
|
||||||
uvlong
|
|
||||||
ownernew(void)
|
|
||||||
{
|
|
||||||
uvlong n;
|
|
||||||
|
|
||||||
lock(&ownerlock);
|
|
||||||
n = ++ownerctr;
|
|
||||||
if(n == 0)
|
|
||||||
n = ++ownerctr;
|
|
||||||
unlock(&ownerlock);
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
Lang langs[] = {
|
Lang langs[] = {
|
||||||
{LangEN, nil, nil, nil, nil, nil, nil, nil},
|
{LangEN, nil, nil, nil, nil, nil, nil, nil},
|
||||||
{LangJP, "hira", "kanji", transmap, backjp, dictqjp, nil, nil},
|
{LangJP, "hira", "kanji", transmap, backjp, dictqjp, nil, nil},
|
||||||
@@ -155,7 +140,7 @@ reset(void)
|
|||||||
show();
|
show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
dictsend(Im *im, Str *key)
|
dictsend(Im *im, Str *key)
|
||||||
{
|
{
|
||||||
Dictreq old, req;
|
Dictreq old, req;
|
||||||
@@ -792,7 +777,7 @@ init(void)
|
|||||||
im.l = getlang(LangEN);
|
im.l = getlang(LangEN);
|
||||||
memset(&search, 0, sizeof search);
|
memset(&search, 0, sizeof search);
|
||||||
memset(&caret, 0, sizeof caret);
|
memset(&caret, 0, sizeof caret);
|
||||||
activeowner = 0;
|
activeowner = nil;
|
||||||
visible = 0;
|
visible = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -814,7 +799,7 @@ imhandlekey(Keyreq *kr)
|
|||||||
case Keyrelease:
|
case Keyrelease:
|
||||||
if(kr->owner == activeowner){
|
if(kr->owner == activeowner){
|
||||||
reset();
|
reset();
|
||||||
activeowner = 0;
|
activeowner = nil;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Keyreset:
|
case Keyreset:
|
||||||
@@ -827,7 +812,7 @@ imhandlekey(Keyreq *kr)
|
|||||||
show();
|
show();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
case Keypress:
|
||||||
res.eaten = 0;
|
res.eaten = 0;
|
||||||
if(meaningful(kr)){
|
if(meaningful(kr)){
|
||||||
if(kr->owner != activeowner){
|
if(kr->owner != activeowner){
|
||||||
@@ -840,7 +825,7 @@ imhandlekey(Keyreq *kr)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(kr->want && kr->owner == activeowner){
|
if(kr->owner == activeowner){
|
||||||
if(search.lang)
|
if(search.lang)
|
||||||
res.preedit = search.text;
|
res.preedit = search.text;
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,28 +1,63 @@
|
|||||||
CC = 9c
|
CC = 9c
|
||||||
LD = 9l
|
LD = 9l
|
||||||
|
HOSTCC = cc
|
||||||
CFLAGS = -std=c99 -Wall -Wextra -O2 -g -I.. -I../cutest
|
CFLAGS = -std=c99 -Wall -Wextra -O2 -g -I.. -I../cutest
|
||||||
LIBS = -lthread -lbio
|
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
|
||||||
|
FT_LIBS = $(shell pkg-config --libs freetype2)
|
||||||
|
DBUS_CFLAGS = $(shell pkg-config --cflags dbus-1)
|
||||||
|
DBUS_LIBS = $(shell pkg-config --libs dbus-1)
|
||||||
|
IBUS_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
|
||||||
|
IBUS_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
|
||||||
|
LIBS = -lthread -lbio $(FT_LIBS) $(IBUS_LIBS)
|
||||||
|
|
||||||
PROG = unit_test
|
PROG = unit_test
|
||||||
|
LIVE = ibus_live_test ipc_live_test daemon_collision_test daemon_failure_test \
|
||||||
|
daemon_restart_test
|
||||||
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
||||||
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c wayland_test.c \
|
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
||||||
popup_test.c
|
popup_test.c font_test.c ibus_test.c server_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 wayland_state.c \
|
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
|
||||||
popup_layout.c
|
font.c
|
||||||
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
||||||
OBJS = $(TESTOBJ) $(PARENTOBJ)
|
OBJS = $(TESTOBJ) $(PARENTOBJ)
|
||||||
|
|
||||||
all: $(PROG)
|
all: $(PROG) $(LIVE)
|
||||||
|
|
||||||
check test: $(PROG)
|
check test: $(PROG) $(LIVE)
|
||||||
./$(PROG) $(TESTARGS)
|
./$(PROG) $(TESTARGS)
|
||||||
|
./ibus_live_test ../strans ../map
|
||||||
|
./ipc_live_test ../strans ../map
|
||||||
|
./daemon_collision_test ../strans ../map
|
||||||
|
./daemon_failure_test ../strans
|
||||||
|
./daemon_restart_test ../strans ../map
|
||||||
|
|
||||||
$(PROG): $(OBJS)
|
$(PROG): $(OBJS)
|
||||||
$(LD) -o $@ $(OBJS) $(LIBS)
|
$(LD) -o $@ $(OBJS) $(LIBS)
|
||||||
|
|
||||||
|
ibus_live_test: ibus_live_test.c
|
||||||
|
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g $(DBUS_CFLAGS) -o $@ $< $(DBUS_LIBS)
|
||||||
|
|
||||||
|
ipc_live_test: ipc_live_test.c ../ipc.c ../ipc.h
|
||||||
|
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. -o $@ ipc_live_test.c ../ipc.c
|
||||||
|
|
||||||
|
daemon_collision_test: daemon_collision_test.c ../ipc.c ../ipc.h
|
||||||
|
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(DBUS_CFLAGS) -o $@ \
|
||||||
|
daemon_collision_test.c ../ipc.c $(DBUS_LIBS)
|
||||||
|
|
||||||
|
daemon_failure_test: daemon_failure_test.c
|
||||||
|
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -o $@ $<
|
||||||
|
|
||||||
|
daemon_restart_test: daemon_restart_test.c ../ipc.c ../ipc.h
|
||||||
|
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(DBUS_CFLAGS) -o $@ \
|
||||||
|
daemon_restart_test.c ../ipc.c $(DBUS_LIBS)
|
||||||
|
|
||||||
$(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h ../cutest/cutest.h
|
$(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: ../ibus.c
|
||||||
|
server_test.o: ../srv.c
|
||||||
|
unit_font.o: CFLAGS += $(FT_CFLAGS)
|
||||||
|
|
||||||
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
@@ -31,6 +66,6 @@ unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
|||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJS) $(PROG)
|
rm -f $(OBJS) $(PROG) $(LIVE)
|
||||||
|
|
||||||
.PHONY: all check test clean
|
.PHONY: all check test clean
|
||||||
|
|||||||
1184
tests/daemon_collision_test.c
Normal file
1184
tests/daemon_collision_test.c
Normal file
File diff suppressed because it is too large
Load Diff
635
tests/daemon_failure_test.c
Normal file
635
tests/daemon_failure_test.c
Normal file
@@ -0,0 +1,635 @@
|
|||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/inotify.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Failtimeout = 8000,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct Test Test;
|
||||||
|
|
||||||
|
struct Test
|
||||||
|
{
|
||||||
|
pid_t child;
|
||||||
|
int errfd;
|
||||||
|
int notifyfd;
|
||||||
|
int runtimewd;
|
||||||
|
int buswd;
|
||||||
|
int endpointclean;
|
||||||
|
char root[256];
|
||||||
|
char runtime[320];
|
||||||
|
char config[320];
|
||||||
|
char ibus[384];
|
||||||
|
char bus[448];
|
||||||
|
char home[320];
|
||||||
|
char socket[384];
|
||||||
|
char badmap[320];
|
||||||
|
char missingfont[384];
|
||||||
|
char expected[768];
|
||||||
|
char err[4096];
|
||||||
|
size_t nerr;
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint32_t endpointmask = IN_CREATE|IN_MOVED_TO|IN_MOVED_FROM|IN_ATTRIB|
|
||||||
|
IN_DELETE|IN_MODIFY|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF;
|
||||||
|
|
||||||
|
static int
|
||||||
|
fail(char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
fprintf(stderr, "daemon_failure_test: ");
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vfprintf(stderr, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
fputc('\n', stderr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t
|
||||||
|
nowms(void)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
||||||
|
return -1;
|
||||||
|
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
leftms(int64_t deadline)
|
||||||
|
{
|
||||||
|
int64_t n;
|
||||||
|
|
||||||
|
n = nowms();
|
||||||
|
if(n < 0)
|
||||||
|
return -1;
|
||||||
|
n = deadline - n;
|
||||||
|
if(n <= 0)
|
||||||
|
return 0;
|
||||||
|
if(n > 0x7fffffff)
|
||||||
|
return 0x7fffffff;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
makedir(char *path)
|
||||||
|
{
|
||||||
|
if(mkdir(path, 0700) == 0)
|
||||||
|
return 1;
|
||||||
|
return fail("mkdir %s: %s", path, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
readerrors(Test *t)
|
||||||
|
{
|
||||||
|
ssize_t n;
|
||||||
|
|
||||||
|
if(t->errfd < 0)
|
||||||
|
return;
|
||||||
|
while(t->nerr + 1 < sizeof t->err){
|
||||||
|
n = read(t->errfd, t->err + t->nerr,
|
||||||
|
sizeof t->err - t->nerr - 1);
|
||||||
|
if(n > 0){
|
||||||
|
t->nerr += n;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(n < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
t->err[t->nerr] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
showerrors(Test *t)
|
||||||
|
{
|
||||||
|
readerrors(t);
|
||||||
|
if(t->nerr != 0)
|
||||||
|
fprintf(stderr, "daemon_failure_test: daemon stderr:\n%s", t->err);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
setup(Test *t)
|
||||||
|
{
|
||||||
|
memset(t, 0, sizeof *t);
|
||||||
|
t->child = -1;
|
||||||
|
t->errfd = -1;
|
||||||
|
t->notifyfd = -1;
|
||||||
|
t->runtimewd = -1;
|
||||||
|
t->buswd = -1;
|
||||||
|
t->endpointclean = 1;
|
||||||
|
if(nowms() < 0)
|
||||||
|
return fail("read monotonic clock: %s", strerror(errno));
|
||||||
|
snprintf(t->root, sizeof t->root, "/tmp/strans-failure.XXXXXX");
|
||||||
|
if(mkdtemp(t->root) == NULL){
|
||||||
|
t->root[0] = '\0';
|
||||||
|
return fail("mkdtemp: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
if(snprintf(t->runtime, sizeof t->runtime, "%s/runtime", t->root)
|
||||||
|
>= (int)sizeof t->runtime ||
|
||||||
|
snprintf(t->config, sizeof t->config, "%s/config", t->root)
|
||||||
|
>= (int)sizeof t->config ||
|
||||||
|
snprintf(t->ibus, sizeof t->ibus, "%s/ibus", t->config)
|
||||||
|
>= (int)sizeof t->ibus ||
|
||||||
|
snprintf(t->bus, sizeof t->bus, "%s/bus", t->ibus)
|
||||||
|
>= (int)sizeof t->bus ||
|
||||||
|
snprintf(t->home, sizeof t->home, "%s/home", t->root)
|
||||||
|
>= (int)sizeof t->home ||
|
||||||
|
snprintf(t->socket, sizeof t->socket, "%s/strans.sock", t->runtime)
|
||||||
|
>= (int)sizeof t->socket ||
|
||||||
|
snprintf(t->badmap, sizeof t->badmap, "%s/missing-map", t->root)
|
||||||
|
>= (int)sizeof t->badmap ||
|
||||||
|
snprintf(t->missingfont, sizeof t->missingfont,
|
||||||
|
"%s/missing-font.ttf", t->root) >= (int)sizeof t->missingfont ||
|
||||||
|
snprintf(t->expected, sizeof t->expected,
|
||||||
|
"strans: can't open: %s/hira.map\n", t->badmap)
|
||||||
|
>= (int)sizeof t->expected)
|
||||||
|
return fail("temporary path is too long");
|
||||||
|
if(!makedir(t->runtime) || !makedir(t->config) || !makedir(t->ibus) ||
|
||||||
|
!makedir(t->bus) || !makedir(t->home))
|
||||||
|
return 0;
|
||||||
|
t->notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
|
||||||
|
if(t->notifyfd < 0)
|
||||||
|
return fail("inotify_init1: %s", strerror(errno));
|
||||||
|
t->runtimewd = inotify_add_watch(t->notifyfd, t->runtime, endpointmask);
|
||||||
|
if(t->runtimewd < 0)
|
||||||
|
return fail("watch runtime directory: %s", strerror(errno));
|
||||||
|
t->buswd = inotify_add_watch(t->notifyfd, t->bus, endpointmask);
|
||||||
|
if(t->buswd < 0)
|
||||||
|
return fail("watch IBus directory: %s", strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
startchild(Test *t, char *program)
|
||||||
|
{
|
||||||
|
int errpipe[2], fd;
|
||||||
|
long maxfd;
|
||||||
|
pid_t pid;
|
||||||
|
|
||||||
|
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0)
|
||||||
|
return fail("pipe2: %s", strerror(errno));
|
||||||
|
pid = fork();
|
||||||
|
if(pid < 0){
|
||||||
|
close(errpipe[0]);
|
||||||
|
close(errpipe[1]);
|
||||||
|
return fail("fork: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
if(pid == 0){
|
||||||
|
close(errpipe[0]);
|
||||||
|
if(dup2(errpipe[1], STDERR_FILENO) < 0)
|
||||||
|
_exit(126);
|
||||||
|
close(errpipe[1]);
|
||||||
|
if(close_range(3, UINT_MAX, 0) < 0){
|
||||||
|
maxfd = sysconf(_SC_OPEN_MAX);
|
||||||
|
if(maxfd < 0)
|
||||||
|
maxfd = 1024;
|
||||||
|
for(fd = 3; fd < maxfd; fd++)
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
if(setenv("XDG_RUNTIME_DIR", t->runtime, 1) < 0 ||
|
||||||
|
setenv("XDG_CONFIG_HOME", t->config, 1) < 0 ||
|
||||||
|
setenv("HOME", t->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
||||||
|
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
||||||
|
unsetenv("IBUS_ADDRESS") < 0){
|
||||||
|
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
|
||||||
|
strerror(errno));
|
||||||
|
_exit(126);
|
||||||
|
}
|
||||||
|
execl(program, program, t->badmap, t->missingfont, (char*)0);
|
||||||
|
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
||||||
|
_exit(127);
|
||||||
|
}
|
||||||
|
close(errpipe[1]);
|
||||||
|
t->child = pid;
|
||||||
|
t->errfd = errpipe[0];
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
drainnotify(Test *t)
|
||||||
|
{
|
||||||
|
char buf[4096];
|
||||||
|
char *where;
|
||||||
|
struct inotify_event *ev;
|
||||||
|
ssize_t n;
|
||||||
|
size_t off;
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
ok = 1;
|
||||||
|
for(;;){
|
||||||
|
n = read(t->notifyfd, buf, sizeof buf);
|
||||||
|
if(n < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if(n < 0 && errno == EAGAIN)
|
||||||
|
return ok;
|
||||||
|
if(n < 0)
|
||||||
|
return fail("read endpoint watches: %s", strerror(errno));
|
||||||
|
if(n == 0)
|
||||||
|
return ok;
|
||||||
|
for(off = 0; off + sizeof *ev <= (size_t)n;
|
||||||
|
off += sizeof *ev + ev->len){
|
||||||
|
ev = (struct inotify_event*)(buf + off);
|
||||||
|
if(off + sizeof *ev + ev->len > (size_t)n)
|
||||||
|
return fail("truncated inotify event");
|
||||||
|
if(ev->mask & IN_Q_OVERFLOW){
|
||||||
|
t->endpointclean = 0;
|
||||||
|
fail("endpoint watch queue overflowed");
|
||||||
|
ok = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(ev->mask & (IN_IGNORED|IN_UNMOUNT)){
|
||||||
|
t->endpointclean = 0;
|
||||||
|
fail("endpoint watch became invalid: %#x", ev->mask);
|
||||||
|
ok = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(ev->wd != t->runtimewd && ev->wd != t->buswd)
|
||||||
|
continue;
|
||||||
|
if(!(ev->mask & endpointmask))
|
||||||
|
continue;
|
||||||
|
where = ev->wd == t->runtimewd ? "runtime" : "IBus";
|
||||||
|
t->endpointclean = 0;
|
||||||
|
fail("unexpected %s endpoint event %#x for %s", where,
|
||||||
|
ev->mask, ev->len != 0 ? ev->name : "directory");
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
if(off != (size_t)n)
|
||||||
|
return fail("truncated inotify event buffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
killowned(Test *t, int *status)
|
||||||
|
{
|
||||||
|
pid_t pid;
|
||||||
|
int n, ok;
|
||||||
|
|
||||||
|
if(t->child <= 0)
|
||||||
|
return 1;
|
||||||
|
pid = t->child;
|
||||||
|
ok = 1;
|
||||||
|
if(kill(pid, SIGKILL) < 0 && errno != ESRCH){
|
||||||
|
fail("kill -9 %ld: %s", (long)pid, strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
do
|
||||||
|
n = waitpid(pid, status, 0);
|
||||||
|
while(n < 0 && errno == EINTR);
|
||||||
|
if(n != pid){
|
||||||
|
fail("reap %ld after SIGKILL: %s", (long)pid,
|
||||||
|
n < 0 ? strerror(errno) : "wrong child");
|
||||||
|
ok = 0;
|
||||||
|
}else
|
||||||
|
t->child = -1;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
waitfailed(Test *t)
|
||||||
|
{
|
||||||
|
struct pollfd pfd[2];
|
||||||
|
pid_t pid;
|
||||||
|
int n, ok, status, timeout;
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
pid = t->child;
|
||||||
|
ok = 1;
|
||||||
|
deadline = nowms();
|
||||||
|
if(deadline < 0){
|
||||||
|
fail("read monotonic clock before waiting for daemon: %s",
|
||||||
|
strerror(errno));
|
||||||
|
killowned(t, &status);
|
||||||
|
readerrors(t);
|
||||||
|
drainnotify(t);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
deadline += Failtimeout;
|
||||||
|
for(;;){
|
||||||
|
do
|
||||||
|
n = waitpid(pid, &status, WNOHANG);
|
||||||
|
while(n < 0 && errno == EINTR);
|
||||||
|
if(n == pid){
|
||||||
|
t->child = -1;
|
||||||
|
readerrors(t);
|
||||||
|
if(!drainnotify(t))
|
||||||
|
ok = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(n < 0){
|
||||||
|
fail("waitpid failed daemon %ld: %s", (long)pid,
|
||||||
|
strerror(errno));
|
||||||
|
if(errno == ECHILD){
|
||||||
|
t->child = -1;
|
||||||
|
readerrors(t);
|
||||||
|
drainnotify(t);
|
||||||
|
}else{
|
||||||
|
killowned(t, &status);
|
||||||
|
readerrors(t);
|
||||||
|
drainnotify(t);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
timeout = leftms(deadline);
|
||||||
|
if(timeout < 0){
|
||||||
|
fail("read monotonic clock while waiting for daemon: %s",
|
||||||
|
strerror(errno));
|
||||||
|
killowned(t, &status);
|
||||||
|
readerrors(t);
|
||||||
|
drainnotify(t);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(timeout == 0){
|
||||||
|
fail("daemon %ld did not exit after map initialization failure",
|
||||||
|
(long)pid);
|
||||||
|
killowned(t, &status);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
pfd[0].fd = t->notifyfd;
|
||||||
|
pfd[0].events = POLLIN;
|
||||||
|
pfd[0].revents = 0;
|
||||||
|
pfd[1].fd = t->errfd;
|
||||||
|
pfd[1].events = POLLIN|POLLHUP;
|
||||||
|
pfd[1].revents = 0;
|
||||||
|
n = poll(pfd, 2, timeout);
|
||||||
|
if(n < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if(n < 0){
|
||||||
|
fail("poll failed daemon: %s", strerror(errno));
|
||||||
|
killowned(t, &status);
|
||||||
|
readerrors(t);
|
||||||
|
drainnotify(t);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL)){
|
||||||
|
fail("endpoint watch became unusable: %#x", pfd[0].revents);
|
||||||
|
killowned(t, &status);
|
||||||
|
readerrors(t);
|
||||||
|
drainnotify(t);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(pfd[1].revents & (POLLERR|POLLNVAL)){
|
||||||
|
fail("daemon stderr pipe became unusable: %#x",
|
||||||
|
pfd[1].revents);
|
||||||
|
killowned(t, &status);
|
||||||
|
readerrors(t);
|
||||||
|
drainnotify(t);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if((pfd[0].revents & POLLIN) && !drainnotify(t))
|
||||||
|
ok = 0;
|
||||||
|
if(pfd[1].revents & (POLLIN|POLLHUP))
|
||||||
|
readerrors(t);
|
||||||
|
}
|
||||||
|
if(!WIFEXITED(status) || WEXITSTATUS(status) != 1)
|
||||||
|
return fail("daemon map failure had wait status %#x", status);
|
||||||
|
if(strcmp(t->err, t->expected) != 0)
|
||||||
|
return fail("daemon did not report the expected map initialization failure");
|
||||||
|
return ok && t->endpointclean;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
dirempty(char *path, char *which)
|
||||||
|
{
|
||||||
|
DIR *dir;
|
||||||
|
struct dirent *de;
|
||||||
|
int count, ok;
|
||||||
|
|
||||||
|
dir = opendir(path);
|
||||||
|
if(dir == NULL)
|
||||||
|
return fail("open %s directory %s: %s", which, path, strerror(errno));
|
||||||
|
count = 0;
|
||||||
|
ok = 1;
|
||||||
|
errno = 0;
|
||||||
|
while((de = readdir(dir)) != NULL){
|
||||||
|
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
||||||
|
continue;
|
||||||
|
count++;
|
||||||
|
fail("unexpected %s directory entry %s", which, de->d_name);
|
||||||
|
ok = 0;
|
||||||
|
errno = 0;
|
||||||
|
}
|
||||||
|
if(errno != 0){
|
||||||
|
fail("read %s directory %s: %s", which, path, strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
if(closedir(dir) < 0){
|
||||||
|
fail("close %s directory %s: %s", which, path, strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
if(count != 0)
|
||||||
|
ok = 0;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
checkendpoints(Test *t)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
ok = 1;
|
||||||
|
errno = 0;
|
||||||
|
if(lstat(t->socket, &st) == 0){
|
||||||
|
fail("IPC endpoint exists after failed startup: %s", t->socket);
|
||||||
|
ok = 0;
|
||||||
|
}else if(errno != ENOENT){
|
||||||
|
fail("lstat failed IPC endpoint: %s", strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
if(!dirempty(t->runtime, "runtime"))
|
||||||
|
ok = 0;
|
||||||
|
if(!dirempty(t->bus, "IBus"))
|
||||||
|
ok = 0;
|
||||||
|
return ok && t->endpointclean;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clearfd(int);
|
||||||
|
|
||||||
|
static int
|
||||||
|
removeentry(int dirfd, char *name)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
int fd, ok;
|
||||||
|
|
||||||
|
if(fstatat(dirfd, name, &st, AT_SYMLINK_NOFOLLOW) < 0)
|
||||||
|
return fail("stat cleanup entry %s: %s", name, strerror(errno));
|
||||||
|
if(!S_ISDIR(st.st_mode)){
|
||||||
|
if(unlinkat(dirfd, name, 0) == 0 || errno == ENOENT)
|
||||||
|
return 1;
|
||||||
|
return fail("remove cleanup entry %s: %s", name, strerror(errno));
|
||||||
|
}
|
||||||
|
fd = openat(dirfd, name, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
|
||||||
|
if(fd < 0)
|
||||||
|
return fail("open cleanup directory %s: %s", name, strerror(errno));
|
||||||
|
ok = clearfd(fd);
|
||||||
|
if(close(fd) < 0){
|
||||||
|
fail("close cleanup directory %s: %s", name, strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
if(unlinkat(dirfd, name, AT_REMOVEDIR) < 0 && errno != ENOENT){
|
||||||
|
fail("remove cleanup directory %s: %s", name, strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
clearfd(int fd)
|
||||||
|
{
|
||||||
|
DIR *dir;
|
||||||
|
struct dirent *de;
|
||||||
|
int copy, ok;
|
||||||
|
|
||||||
|
copy = dup(fd);
|
||||||
|
if(copy < 0)
|
||||||
|
return fail("duplicate cleanup directory: %s", strerror(errno));
|
||||||
|
dir = fdopendir(copy);
|
||||||
|
if(dir == NULL){
|
||||||
|
close(copy);
|
||||||
|
return fail("open cleanup directory stream: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
ok = 1;
|
||||||
|
errno = 0;
|
||||||
|
while((de = readdir(dir)) != NULL){
|
||||||
|
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
||||||
|
continue;
|
||||||
|
if(!removeentry(fd, de->d_name))
|
||||||
|
ok = 0;
|
||||||
|
errno = 0;
|
||||||
|
}
|
||||||
|
if(errno != 0){
|
||||||
|
fail("read cleanup directory: %s", strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
if(closedir(dir) < 0){
|
||||||
|
fail("close cleanup directory stream: %s", strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
removeroot(Test *t)
|
||||||
|
{
|
||||||
|
int fd, ok;
|
||||||
|
|
||||||
|
if(t->root[0] == '\0')
|
||||||
|
return 1;
|
||||||
|
fd = open(t->root, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
|
||||||
|
if(fd < 0)
|
||||||
|
return errno == ENOENT || fail("open cleanup root %s: %s", t->root,
|
||||||
|
strerror(errno));
|
||||||
|
ok = clearfd(fd);
|
||||||
|
if(close(fd) < 0){
|
||||||
|
fail("close cleanup root %s: %s", t->root, strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
if(rmdir(t->root) < 0 && errno != ENOENT){
|
||||||
|
fail("rmdir %s: %s", t->root, strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
cleanup(Test *t)
|
||||||
|
{
|
||||||
|
int n, ok, status;
|
||||||
|
|
||||||
|
ok = 1;
|
||||||
|
if(t->child > 0){
|
||||||
|
do
|
||||||
|
n = waitpid(t->child, &status, WNOHANG);
|
||||||
|
while(n < 0 && errno == EINTR);
|
||||||
|
if(n == t->child)
|
||||||
|
t->child = -1;
|
||||||
|
else if(n == 0){
|
||||||
|
fail("failed-start daemon still running during cleanup");
|
||||||
|
ok = 0;
|
||||||
|
if(!killowned(t, &status))
|
||||||
|
ok = 0;
|
||||||
|
}else{
|
||||||
|
fail("check failed-start daemon during cleanup: %s",
|
||||||
|
strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
if(errno == ECHILD)
|
||||||
|
t->child = -1;
|
||||||
|
else if(!killowned(t, &status))
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
readerrors(t);
|
||||||
|
if(t->errfd >= 0){
|
||||||
|
if(close(t->errfd) < 0){
|
||||||
|
fail("close daemon stderr: %s", strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
t->errfd = -1;
|
||||||
|
}
|
||||||
|
if(t->notifyfd >= 0){
|
||||||
|
if(t->runtimewd >= 0 &&
|
||||||
|
inotify_rm_watch(t->notifyfd, t->runtimewd) < 0 && errno != EINVAL){
|
||||||
|
fail("remove runtime watch: %s", strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
if(t->buswd >= 0 &&
|
||||||
|
inotify_rm_watch(t->notifyfd, t->buswd) < 0 && errno != EINVAL){
|
||||||
|
fail("remove IBus watch: %s", strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
if(close(t->notifyfd) < 0){
|
||||||
|
fail("close endpoint watches: %s", strerror(errno));
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
t->notifyfd = -1;
|
||||||
|
}
|
||||||
|
if(!removeroot(t))
|
||||||
|
ok = 0;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
Test test;
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
if(argc != 2){
|
||||||
|
fprintf(stderr, "usage: daemon_failure_test strans\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
ok = setup(&test);
|
||||||
|
if(ok)
|
||||||
|
ok = startchild(&test, argv[1]);
|
||||||
|
if(ok){
|
||||||
|
ok = waitfailed(&test);
|
||||||
|
if(test.child <= 0 && !checkendpoints(&test))
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
if(!cleanup(&test))
|
||||||
|
ok = 0;
|
||||||
|
if(!ok){
|
||||||
|
showerrors(&test);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("failed daemon startup published no endpoints: ok\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
1855
tests/daemon_restart_test.c
Normal file
1855
tests/daemon_restart_test.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,37 @@ Str shownpre(Im*);
|
|||||||
|
|
||||||
static int typekeys(struct ct*, char*, Str*);
|
static int typekeys(struct ct*, char*, Str*);
|
||||||
|
|
||||||
|
void
|
||||||
|
testengineinit(int lang)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
im.l = getlang(lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testenginehandle(Keyreq *req)
|
||||||
|
{
|
||||||
|
imhandlekey(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
void*
|
||||||
|
testengineowner(void)
|
||||||
|
{
|
||||||
|
return activeowner;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testenginepreedit(Str *preedit)
|
||||||
|
{
|
||||||
|
impre(preedit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testenginecaret(Caret *at)
|
||||||
|
{
|
||||||
|
*at = caret;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
vietnamese_state_lifetime(struct ct *t)
|
vietnamese_state_lifetime(struct ct *t)
|
||||||
{
|
{
|
||||||
@@ -161,7 +192,7 @@ engine_dictionary_queue_latest_wins(struct ct *t)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Keyres
|
static Keyres
|
||||||
ownerrequestat(uvlong owner, int op, Rune key, u32int mod, Caret *caret)
|
ownerrequestat(void *owner, int op, Rune key, u32int mod, Caret *caret)
|
||||||
{
|
{
|
||||||
Channel *reply;
|
Channel *reply;
|
||||||
Keyreq req;
|
Keyreq req;
|
||||||
@@ -175,7 +206,6 @@ ownerrequestat(uvlong owner, int op, Rune key, u32int mod, Caret *caret)
|
|||||||
req.mod = mod;
|
req.mod = mod;
|
||||||
if(caret != nil)
|
if(caret != nil)
|
||||||
req.caret = *caret;
|
req.caret = *caret;
|
||||||
req.want = 1;
|
|
||||||
req.reply = reply;
|
req.reply = reply;
|
||||||
imhandlekey(&req);
|
imhandlekey(&req);
|
||||||
chanrecv(reply, &res);
|
chanrecv(reply, &res);
|
||||||
@@ -184,7 +214,7 @@ ownerrequestat(uvlong owner, int op, Rune key, u32int mod, Caret *caret)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Keyres
|
static Keyres
|
||||||
ownerrequest(uvlong owner, int op, Rune key, u32int mod)
|
ownerrequest(void *owner, int op, Rune key, u32int mod)
|
||||||
{
|
{
|
||||||
return ownerrequestat(owner, op, key, mod, nil);
|
return ownerrequestat(owner, op, key, mod, nil);
|
||||||
}
|
}
|
||||||
@@ -194,57 +224,75 @@ engine_active_owner_lifecycle(struct ct *t)
|
|||||||
{
|
{
|
||||||
Keyres res;
|
Keyres res;
|
||||||
Str shown;
|
Str shown;
|
||||||
uvlong a, b, c;
|
char a, b, c;
|
||||||
|
|
||||||
a = ownernew();
|
|
||||||
b = ownernew();
|
|
||||||
c = ownernew();
|
|
||||||
CT_CHECK(t, a != 0 && b != 0 && c != 0);
|
|
||||||
CT_CHECK(t, a != b && b != c && a != c);
|
|
||||||
init();
|
init();
|
||||||
im.l = getlang(LangJP);
|
im.l = getlang(LangJP);
|
||||||
res = ownerrequest(a, Keypress, 'k', 0);
|
res = ownerrequest(&a, Keypress, 'k', 0);
|
||||||
CT_CHECK(t, res.eaten);
|
CT_CHECK(t, res.eaten);
|
||||||
res = ownerrequest(a, Keypress, 'a', 0);
|
res = ownerrequest(&a, Keypress, 'a', 0);
|
||||||
checkstr(t, "first owner preedit", "か", &res.preedit);
|
checkstr(t, "first owner preedit", "か", &res.preedit);
|
||||||
CT_EQ_UINT(t, a, activeowner);
|
CT_EQ_PTR(t, &a, activeowner);
|
||||||
|
|
||||||
res = ownerrequest(b, Keypress, 'n', 0);
|
res = ownerrequest(&b, Keypress, 'n', 0);
|
||||||
checkstr(t, "takeover resets old preedit", "ん", &res.preedit);
|
checkstr(t, "takeover resets old preedit", "ん", &res.preedit);
|
||||||
CT_EQ_UINT(t, b, activeowner);
|
CT_EQ_PTR(t, &b, activeowner);
|
||||||
res = ownerrequest(a, Keyreset, 0, 0);
|
res = ownerrequest(&a, Keyreset, 0, 0);
|
||||||
CT_EQ_INT(t, 0, res.preedit.n);
|
CT_EQ_INT(t, 0, res.preedit.n);
|
||||||
shown = shownpre(&im);
|
shown = shownpre(&im);
|
||||||
checkstr(t, "stale reset preserves owner", "ん", &shown);
|
checkstr(t, "stale reset preserves owner", "ん", &shown);
|
||||||
CT_EQ_UINT(t, b, activeowner);
|
CT_EQ_PTR(t, &b, activeowner);
|
||||||
|
|
||||||
res = ownerrequest(b, Keypress, 'y', 0);
|
res = ownerrequest(&b, Keypress, 'y', 0);
|
||||||
res = ownerrequest(b, Keypress, 'a', 0);
|
res = ownerrequest(&b, Keypress, 'a', 0);
|
||||||
checkstr(t, "current owner continues", "にゃ", &res.preedit);
|
checkstr(t, "current owner continues", "にゃ", &res.preedit);
|
||||||
ownerrequest(a, Keyrelease, 0, 0);
|
ownerrequest(&a, Keyrelease, 0, 0);
|
||||||
shown = shownpre(&im);
|
shown = shownpre(&im);
|
||||||
checkstr(t, "stale release preserves owner", "にゃ", &shown);
|
checkstr(t, "stale release preserves owner", "にゃ", &shown);
|
||||||
CT_EQ_UINT(t, b, activeowner);
|
CT_EQ_PTR(t, &b, activeowner);
|
||||||
|
|
||||||
ownerrequest(b, Keyrelease, 0, 0);
|
ownerrequest(&b, Keyrelease, 0, 0);
|
||||||
CT_EQ_UINT(t, 0, activeowner);
|
CT_EQ_PTR(t, nil, activeowner);
|
||||||
CT_EQ_INT(t, 0, im.pre.n);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
CT_EQ_INT(t, 0, im.raw.n);
|
CT_EQ_INT(t, 0, im.raw.n);
|
||||||
ownerrequest(c, Keypress, Kmodfirst, 0);
|
ownerrequest(&c, Keypress, Kmodfirst, 0);
|
||||||
CT_EQ_UINT(t, 0, activeowner);
|
CT_EQ_PTR(t, nil, activeowner);
|
||||||
res = ownerrequest(c, Keypress, 'k', 0);
|
res = ownerrequest(&c, Keypress, 'k', 0);
|
||||||
CT_EQ_UINT(t, c, activeowner);
|
CT_EQ_PTR(t, &c, activeowner);
|
||||||
checkstr(t, "next owner first key", "k", &res.preedit);
|
checkstr(t, "next owner first key", "k", &res.preedit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
engine_active_owner_reset(struct ct *t)
|
||||||
|
{
|
||||||
|
Caret at;
|
||||||
|
Keyres res;
|
||||||
|
char owner;
|
||||||
|
|
||||||
|
memset(&at, 0, sizeof at);
|
||||||
|
at.valid = 1;
|
||||||
|
at.x = 10;
|
||||||
|
at.y = 20;
|
||||||
|
at.h = 14;
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJP);
|
||||||
|
ownerrequestat(&owner, Keypress, 'k', 0, &at);
|
||||||
|
res = ownerrequest(&owner, Keypress, 'a', 0);
|
||||||
|
checkstr(t, "preedit before reset", "か", &res.preedit);
|
||||||
|
res = ownerrequest(&owner, Keyreset, 0, 0);
|
||||||
|
CT_EQ_PTR(t, &owner, activeowner);
|
||||||
|
CT_EQ_INT(t, 0, res.preedit.n);
|
||||||
|
CT_CHECK(t, !caret.valid);
|
||||||
|
res = ownerrequest(&owner, Keypress, 'n', 0);
|
||||||
|
checkstr(t, "same owner after reset", "ん", &res.preedit);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
engine_active_owner_caret(struct ct *t)
|
engine_active_owner_caret(struct ct *t)
|
||||||
{
|
{
|
||||||
Caret a, moved;
|
Caret a, moved;
|
||||||
uvlong one, two;
|
char one, two;
|
||||||
|
|
||||||
one = ownernew();
|
|
||||||
two = ownernew();
|
|
||||||
memset(&a, 0, sizeof a);
|
memset(&a, 0, sizeof a);
|
||||||
a.valid = 1;
|
a.valid = 1;
|
||||||
a.x = 10;
|
a.x = 10;
|
||||||
@@ -254,15 +302,15 @@ engine_active_owner_caret(struct ct *t)
|
|||||||
moved.x = 80;
|
moved.x = 80;
|
||||||
init();
|
init();
|
||||||
im.l = getlang(LangJP);
|
im.l = getlang(LangJP);
|
||||||
ownerrequestat(one, Keypress, 'k', 0, &a);
|
ownerrequestat(&one, Keypress, 'k', 0, &a);
|
||||||
CT_CHECK(t, caret.valid);
|
CT_CHECK(t, caret.valid);
|
||||||
CT_EQ_INT(t, 10, caret.x);
|
CT_EQ_INT(t, 10, caret.x);
|
||||||
ownerrequestat(two, Keycaret, 0, 0, &moved);
|
ownerrequestat(&two, Keycaret, 0, 0, &moved);
|
||||||
CT_EQ_INT(t, 10, caret.x);
|
CT_EQ_INT(t, 10, caret.x);
|
||||||
ownerrequestat(one, Keycaret, 0, 0, &moved);
|
ownerrequestat(&one, Keycaret, 0, 0, &moved);
|
||||||
CT_EQ_INT(t, 80, caret.x);
|
CT_EQ_INT(t, 80, caret.x);
|
||||||
ownerrequest(two, Keypress, 'n', 0);
|
ownerrequest(&two, Keypress, 'n', 0);
|
||||||
CT_EQ_UINT(t, two, activeowner);
|
CT_EQ_PTR(t, &two, activeowner);
|
||||||
CT_CHECK(t, !caret.valid);
|
CT_CHECK(t, !caret.valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1430,7 +1478,8 @@ engine_randomized_stress(struct ct *t)
|
|||||||
Keyres res;
|
Keyres res;
|
||||||
Caret pos;
|
Caret pos;
|
||||||
u32int rnd, mod;
|
u32int rnd, mod;
|
||||||
uvlong owners[3], owner;
|
char contexts[3];
|
||||||
|
void *owner, *owners[3];
|
||||||
int i, j, op;
|
int i, j, op;
|
||||||
|
|
||||||
vi = getlang(LangVI);
|
vi = getlang(LangVI);
|
||||||
@@ -1439,9 +1488,9 @@ engine_randomized_stress(struct ct *t)
|
|||||||
vimap = vi->map;
|
vimap = vi->map;
|
||||||
vi->map = testvi.map;
|
vi->map = testvi.map;
|
||||||
emojibegin(&f, 0);
|
emojibegin(&f, 0);
|
||||||
owners[0] = ownernew();
|
owners[0] = &contexts[0];
|
||||||
owners[1] = ownernew();
|
owners[1] = &contexts[1];
|
||||||
owners[2] = ownernew();
|
owners[2] = &contexts[2];
|
||||||
rnd = 0x5eed1234U;
|
rnd = 0x5eed1234U;
|
||||||
for(i = 0; i < 16000; i++){
|
for(i = 0; i < 16000; i++){
|
||||||
rnd = stressrand(&rnd);
|
rnd = stressrand(&rnd);
|
||||||
@@ -1506,7 +1555,7 @@ engine_randomized_stress(struct ct *t)
|
|||||||
i = 16000;
|
i = 16000;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(activeowner != 0 && activeowner != owners[0] &&
|
if(activeowner != nil && activeowner != owners[0] &&
|
||||||
activeowner != owners[1] && activeowner != owners[2]){
|
activeowner != owners[1] && activeowner != owners[2]){
|
||||||
CT_ERRORF(t, "unknown active owner after stress operation %d", i);
|
CT_ERRORF(t, "unknown active owner after stress operation %d", i);
|
||||||
break;
|
break;
|
||||||
|
|||||||
157
tests/font_test.c
Normal file
157
tests/font_test.c
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Guard = 8,
|
||||||
|
Testw = 3 * Fontsz,
|
||||||
|
Testh = 3 * Fontsz,
|
||||||
|
Testn = Testw * Testh,
|
||||||
|
Missingn = Fontsz * Fontsz,
|
||||||
|
};
|
||||||
|
|
||||||
|
static u32int guardcolor = 0x5a5a5a5a;
|
||||||
|
static char *testfonts[] = {
|
||||||
|
"/usr/share/fonts/TTF/DejaVuSans.ttf",
|
||||||
|
"/usr/share/fonts/TTF/Jigmo.ttf",
|
||||||
|
"/usr/share/fonts/TTF/Jigmo2.ttf",
|
||||||
|
};
|
||||||
|
static char *missingfonts[] = {
|
||||||
|
"/strans-test-no-such-font",
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
fillpixels(u32int *p, int n, u32int color)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < n; i++)
|
||||||
|
p[i] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
hasink(u32int *p, int n)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < n; i++)
|
||||||
|
if(p[i] != Colbg)
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
renders(Rune r)
|
||||||
|
{
|
||||||
|
u32int buf[Fontsz * Fontsz];
|
||||||
|
|
||||||
|
fillpixels(buf, nelem(buf), Colbg);
|
||||||
|
putfont(buf, Fontsz, Fontsz, 0, 0, r);
|
||||||
|
return hasink(buf, nelem(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
inkbounds(u32int *p, int *minx, int *miny, int *maxx, int *maxy)
|
||||||
|
{
|
||||||
|
int x, y;
|
||||||
|
|
||||||
|
*minx = Testw;
|
||||||
|
*miny = Testh;
|
||||||
|
*maxx = -1;
|
||||||
|
*maxy = -1;
|
||||||
|
for(y = 0; y < Testh; y++){
|
||||||
|
for(x = 0; x < Testw; x++){
|
||||||
|
if(p[y * Testw + x] == Colbg)
|
||||||
|
continue;
|
||||||
|
*minx = min(*minx, x);
|
||||||
|
*miny = min(*miny, y);
|
||||||
|
*maxx = max(*maxx, x);
|
||||||
|
*maxy = max(*maxy, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *maxx >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
checkclip(struct ct *t, u32int *ref, int dx, int dy)
|
||||||
|
{
|
||||||
|
u32int *buf, *mem, *want;
|
||||||
|
int i, sx, sy, x, y;
|
||||||
|
|
||||||
|
mem = emalloc((Testn + 2 * Guard) * sizeof mem[0]);
|
||||||
|
want = emalloc(Testn * sizeof want[0]);
|
||||||
|
fillpixels(mem, Testn + 2 * Guard, guardcolor);
|
||||||
|
buf = mem + Guard;
|
||||||
|
fillpixels(buf, Testn, Colbg);
|
||||||
|
fillpixels(want, Testn, Colbg);
|
||||||
|
for(y = 0; y < Testh; y++){
|
||||||
|
for(x = 0; x < Testw; x++){
|
||||||
|
sx = x - dx;
|
||||||
|
sy = y - dy;
|
||||||
|
if(sx >= 0 && sx < Testw && sy >= 0 && sy < Testh)
|
||||||
|
want[y * Testw + x] = ref[sy * Testw + sx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
putfont(buf, Testw, Testh, Fontsz + dx, Fontsz + dy, 'A');
|
||||||
|
CT_CHECK(t, hasink(buf, Testn));
|
||||||
|
CT_EQ_MEM(t, want, buf, Testn * sizeof buf[0]);
|
||||||
|
for(i = 0; i < Guard; i++){
|
||||||
|
CT_EQ_UINT(t, guardcolor, mem[i]);
|
||||||
|
CT_EQ_UINT(t, guardcolor, mem[Guard + Testn + i]);
|
||||||
|
}
|
||||||
|
free(want);
|
||||||
|
free(mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
font_render(struct ct *t)
|
||||||
|
{
|
||||||
|
u32int *buf, *missing, *ref;
|
||||||
|
int i, minx, miny, maxx, maxy;
|
||||||
|
|
||||||
|
missing = emalloc((Missingn + 2 * Guard) * sizeof missing[0]);
|
||||||
|
fillpixels(missing, Missingn + 2 * Guard, guardcolor);
|
||||||
|
buf = missing + Guard;
|
||||||
|
fillpixels(buf, Missingn, Colbg);
|
||||||
|
CT_CHECK(t, !fontinit(missingfonts, nelem(missingfonts)));
|
||||||
|
putfont(buf, Fontsz, Fontsz, 0, 0, 'A');
|
||||||
|
for(i = 0; i < Missingn; i++)
|
||||||
|
CT_EQ_UINT(t, Colbg, buf[i]);
|
||||||
|
for(i = 0; i < Guard; i++){
|
||||||
|
CT_EQ_UINT(t, guardcolor, missing[i]);
|
||||||
|
CT_EQ_UINT(t, guardcolor, missing[Guard + Missingn + i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!CT_CHECK(t, fontinit(testfonts, nelem(testfonts)))){
|
||||||
|
free(missing);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CT_CHECK(t, renders('A'));
|
||||||
|
CT_CHECK(t, renders(0xac00));
|
||||||
|
CT_CHECK(t, renders(0x4e00));
|
||||||
|
CT_CHECK(t, renders(0x1f600));
|
||||||
|
CT_CHECK(t, renders(0x2000b));
|
||||||
|
fillpixels(missing, Missingn + 2 * Guard, guardcolor);
|
||||||
|
buf = missing + Guard;
|
||||||
|
fillpixels(buf, Missingn, Colbg);
|
||||||
|
putfont(buf, Fontsz, Fontsz, 0, 0, 0x10ffff);
|
||||||
|
for(i = 0; i < Missingn; i++)
|
||||||
|
CT_EQ_UINT(t, Colbg, buf[i]);
|
||||||
|
for(i = 0; i < Guard; i++){
|
||||||
|
CT_EQ_UINT(t, guardcolor, missing[i]);
|
||||||
|
CT_EQ_UINT(t, guardcolor, missing[Guard + Missingn + i]);
|
||||||
|
}
|
||||||
|
free(missing);
|
||||||
|
|
||||||
|
ref = emalloc(Testn * sizeof ref[0]);
|
||||||
|
fillpixels(ref, Testn, Colbg);
|
||||||
|
putfont(ref, Testw, Testh, Fontsz, Fontsz, 'A');
|
||||||
|
if(!CT_CHECK(t, inkbounds(ref, &minx, &miny, &maxx, &maxy))){
|
||||||
|
free(ref);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
checkclip(t, ref, -minx - 1, 0);
|
||||||
|
checkclip(t, ref, Testw - maxx, 0);
|
||||||
|
checkclip(t, ref, 0, -miny - 1);
|
||||||
|
checkclip(t, ref, 0, Testh - maxy);
|
||||||
|
free(ref);
|
||||||
|
}
|
||||||
1227
tests/ibus_live_test.c
Normal file
1227
tests/ibus_live_test.c
Normal file
File diff suppressed because it is too large
Load Diff
476
tests/ibus_test.c
Normal file
476
tests/ibus_test.c
Normal file
@@ -0,0 +1,476 @@
|
|||||||
|
#include "../ibus.c"
|
||||||
|
#include "../cutest/cutest.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
void testengineinit(int);
|
||||||
|
void testenginehandle(Keyreq*);
|
||||||
|
void* testengineowner(void);
|
||||||
|
void testenginepreedit(Str*);
|
||||||
|
void testenginecaret(Caret*);
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Testctrlmask = 1<<2,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct Ibusfix Ibusfix;
|
||||||
|
struct Ibusfix
|
||||||
|
{
|
||||||
|
Channel *oldreply;
|
||||||
|
Channel *trace;
|
||||||
|
Channel *stop;
|
||||||
|
Channel *done;
|
||||||
|
DBusConnection *c1;
|
||||||
|
DBusConnection *c2;
|
||||||
|
int pumpactive;
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
ibus_machine_id_fallback(struct ct *t)
|
||||||
|
{
|
||||||
|
char root[] = "/tmp/strans-machine-id.XXXXXX";
|
||||||
|
char primary[128], fallback[128], got[64];
|
||||||
|
char *path[2];
|
||||||
|
FILE *fp;
|
||||||
|
int madeprimary, madefallback;
|
||||||
|
|
||||||
|
madeprimary = 0;
|
||||||
|
madefallback = 0;
|
||||||
|
if(!CT_CHECK(t, mkdtemp(root) != nil))
|
||||||
|
return;
|
||||||
|
if(!CT_CHECK(t, snprintf(primary, sizeof primary, "%s/primary", root)
|
||||||
|
< (int)sizeof primary) ||
|
||||||
|
!CT_CHECK(t, snprintf(fallback, sizeof fallback, "%s/fallback", root)
|
||||||
|
< (int)sizeof fallback))
|
||||||
|
goto cleanup;
|
||||||
|
if(!CT_CHECK(t, mkdir(primary, 0700) == 0))
|
||||||
|
goto cleanup;
|
||||||
|
madeprimary = 1;
|
||||||
|
fp = fopen(fallback, "w");
|
||||||
|
if(!CT_CHECK(t, fp != nil))
|
||||||
|
goto cleanup;
|
||||||
|
madefallback = 1;
|
||||||
|
if(!CT_CHECK(t, fputs("fallback-machine-id\n", fp) >= 0)){
|
||||||
|
fclose(fp);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
if(!CT_CHECK(t, fclose(fp) == 0))
|
||||||
|
goto cleanup;
|
||||||
|
path[0] = primary;
|
||||||
|
path[1] = fallback;
|
||||||
|
machineidfiles(got, sizeof got, path, nelem(path));
|
||||||
|
CT_EQ_STR(t, "fallback-machine-id", got);
|
||||||
|
cleanup:
|
||||||
|
if(madefallback)
|
||||||
|
CT_CHECK(t, unlink(fallback) == 0);
|
||||||
|
if(madeprimary)
|
||||||
|
CT_CHECK(t, rmdir(primary) == 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
|
||||||
|
enginepump(void *arg)
|
||||||
|
{
|
||||||
|
Ibusfix *f;
|
||||||
|
Keyreq req;
|
||||||
|
uchar token;
|
||||||
|
Alt alts[] = {
|
||||||
|
{keyc, &req, CHANRCV, nil},
|
||||||
|
{nil, &token, CHANRCV, nil},
|
||||||
|
{nil, nil, CHANEND, nil},
|
||||||
|
};
|
||||||
|
|
||||||
|
f = arg;
|
||||||
|
alts[1].c = f->stop;
|
||||||
|
for(;;)
|
||||||
|
switch(alt(alts)){
|
||||||
|
case 0:
|
||||||
|
chansend(f->trace, &req);
|
||||||
|
testenginehandle(&req);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
chansend(f->done, &token);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
ibusbegin(struct ct *t, Ibusfix *f)
|
||||||
|
{
|
||||||
|
Drawcmd dc;
|
||||||
|
|
||||||
|
memset(f, 0, sizeof *f);
|
||||||
|
memset(contexts, 0, sizeof contexts);
|
||||||
|
memset(conns, 0, sizeof conns);
|
||||||
|
nconns = 0;
|
||||||
|
while(channbrecv(drawc, &dc) > 0)
|
||||||
|
;
|
||||||
|
testengineinit(LangEN);
|
||||||
|
f->oldreply = replyc;
|
||||||
|
replyc = chancreate(sizeof(Keyres), 0);
|
||||||
|
f->trace = chancreate(sizeof(Keyreq), Maxcontexts);
|
||||||
|
f->stop = chancreate(sizeof(uchar), 0);
|
||||||
|
f->done = chancreate(sizeof(uchar), 0);
|
||||||
|
f->pumpactive = threadcreate(enginepump, f, 8192) >= 0;
|
||||||
|
f->c1 = malloc(1);
|
||||||
|
f->c2 = malloc(1);
|
||||||
|
return CT_CHECK(t, f->pumpactive && f->c1 != nil && f->c2 != nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ibusend(Ibusfix *f)
|
||||||
|
{
|
||||||
|
Drawcmd dc;
|
||||||
|
Keyreq req;
|
||||||
|
uchar token;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if(f->pumpactive){
|
||||||
|
for(i = 0; i < nelem(contexts); i++)
|
||||||
|
if(contexts[i].conn != nil)
|
||||||
|
dropcontext(&contexts[i]);
|
||||||
|
while(channbrecv(f->trace, &req) > 0)
|
||||||
|
;
|
||||||
|
token = 0;
|
||||||
|
chansend(f->stop, &token);
|
||||||
|
chanrecv(f->done, &token);
|
||||||
|
}
|
||||||
|
memset(contexts, 0, sizeof contexts);
|
||||||
|
memset(conns, 0, sizeof conns);
|
||||||
|
nconns = 0;
|
||||||
|
testengineinit(LangEN);
|
||||||
|
while(channbrecv(drawc, &dc) > 0)
|
||||||
|
;
|
||||||
|
free(f->c1);
|
||||||
|
free(f->c2);
|
||||||
|
chanfree(replyc);
|
||||||
|
replyc = f->oldreply;
|
||||||
|
chanfree(f->trace);
|
||||||
|
chanfree(f->stop);
|
||||||
|
chanfree(f->done);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Keyreq
|
||||||
|
nexttrace(struct ct *t, Ibusfix *f, int op, Ictx *ctx)
|
||||||
|
{
|
||||||
|
Keyreq req;
|
||||||
|
|
||||||
|
memset(&req, 0, sizeof req);
|
||||||
|
if(!CT_CHECK(t, channbrecv(f->trace, &req) > 0))
|
||||||
|
return req;
|
||||||
|
CT_EQ_INT(t, op, req.op);
|
||||||
|
CT_EQ_PTR(t, ctx, req.owner);
|
||||||
|
CT_EQ_PTR(t, replyc, req.reply);
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
notrace(struct ct *t, Ibusfix *f)
|
||||||
|
{
|
||||||
|
Keyreq req;
|
||||||
|
|
||||||
|
CT_CHECK(t, channbrecv(f->trace, &req) <= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
checkpreedit(struct ct *t, char *want, Keyres *res)
|
||||||
|
{
|
||||||
|
char got[Maxutf];
|
||||||
|
|
||||||
|
stoutf(&res->preedit, got, sizeof got);
|
||||||
|
CT_EQ_STR(t, want, got);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
checkenginepreedit(struct ct *t, char *want)
|
||||||
|
{
|
||||||
|
char got[Maxutf];
|
||||||
|
Str preedit;
|
||||||
|
|
||||||
|
testenginepreedit(&preedit);
|
||||||
|
stoutf(&preedit, got, sizeof got);
|
||||||
|
CT_EQ_STR(t, want, got);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Keyres
|
||||||
|
contextkey(struct ct *t, Ibusfix *f, Ictx *ctx, u32int sym, u32int state)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
|
||||||
|
memset(&res, 0, sizeof res);
|
||||||
|
CT_CHECK(t, processkey(ctx, sym, state, &res));
|
||||||
|
nexttrace(t, f, Keypress, ctx);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ibus_context_lifecycle(struct ct *t)
|
||||||
|
{
|
||||||
|
Ibusfix f;
|
||||||
|
Ictx *a, *b, *reused;
|
||||||
|
Keyreq req;
|
||||||
|
Keyres res;
|
||||||
|
Caret at;
|
||||||
|
|
||||||
|
if(!ibusbegin(t, &f))
|
||||||
|
goto cleanup;
|
||||||
|
a = newcontext(f.c1, "/context/one");
|
||||||
|
b = newcontext(f.c2, "/context/one");
|
||||||
|
if(!CT_CHECK(t, a != nil && b != nil && a != b))
|
||||||
|
goto cleanup;
|
||||||
|
CT_EQ_PTR(t, a, findcontext(f.c1, "/context/one"));
|
||||||
|
CT_EQ_PTR(t, b, findcontext(f.c2, "/context/one"));
|
||||||
|
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/missing"));
|
||||||
|
|
||||||
|
memset(&res, 0, sizeof res);
|
||||||
|
CT_CHECK(t, !processkey(a, 'x', 0, &res));
|
||||||
|
CT_CHECK(t, !res.eaten);
|
||||||
|
notrace(t, &f);
|
||||||
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
|
|
||||||
|
setcursor(a, 10, 20, 14);
|
||||||
|
notrace(t, &f);
|
||||||
|
CT_CHECK(t, a->caret.valid);
|
||||||
|
a->focused = 1;
|
||||||
|
memset(&res, 0, sizeof res);
|
||||||
|
CT_CHECK(t, !processkey(a, 'x', Relmask, &res));
|
||||||
|
CT_CHECK(t, !res.eaten);
|
||||||
|
notrace(t, &f);
|
||||||
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
|
|
||||||
|
memset(&res, 0, sizeof res);
|
||||||
|
CT_CHECK(t, processkey(a, 'x', 0, &res));
|
||||||
|
req = nexttrace(t, &f, Keypress, a);
|
||||||
|
CT_CHECK(t, !res.eaten);
|
||||||
|
CT_CHECK(t, req.caret.valid);
|
||||||
|
CT_EQ_INT(t, 10, req.caret.x);
|
||||||
|
CT_EQ_INT(t, 20, req.caret.y);
|
||||||
|
CT_EQ_INT(t, 14, req.caret.h);
|
||||||
|
CT_EQ_PTR(t, a, testengineowner());
|
||||||
|
testenginecaret(&at);
|
||||||
|
CT_CHECK(t, at.valid);
|
||||||
|
CT_EQ_INT(t, 10, at.x);
|
||||||
|
CT_EQ_INT(t, 20, at.y);
|
||||||
|
res = contextkey(t, &f, a, 'n', Testctrlmask);
|
||||||
|
CT_CHECK(t, res.eaten);
|
||||||
|
contextkey(t, &f, a, 'k', 0);
|
||||||
|
res = contextkey(t, &f, a, 'a', 0);
|
||||||
|
checkpreedit(t, "か", &res);
|
||||||
|
|
||||||
|
b->focused = 1;
|
||||||
|
res = contextkey(t, &f, b, 'n', 0);
|
||||||
|
checkpreedit(t, "ん", &res);
|
||||||
|
CT_EQ_PTR(t, b, testengineowner());
|
||||||
|
|
||||||
|
setcursor(b, 50, 60, 12);
|
||||||
|
req = nexttrace(t, &f, Keycaret, b);
|
||||||
|
CT_CHECK(t, req.caret.valid);
|
||||||
|
CT_EQ_INT(t, 50, req.caret.x);
|
||||||
|
testenginecaret(&at);
|
||||||
|
CT_CHECK(t, at.valid);
|
||||||
|
CT_EQ_INT(t, 50, at.x);
|
||||||
|
|
||||||
|
memset(&res, 0, sizeof res);
|
||||||
|
sendrequest(a, Keyreset, 0, 0, &res);
|
||||||
|
nexttrace(t, &f, Keyreset, a);
|
||||||
|
checkpreedit(t, "", &res);
|
||||||
|
checkenginepreedit(t, "ん");
|
||||||
|
CT_EQ_PTR(t, b, testengineowner());
|
||||||
|
|
||||||
|
setcursor(a, 90, 100, 20);
|
||||||
|
nexttrace(t, &f, Keycaret, a);
|
||||||
|
testenginecaret(&at);
|
||||||
|
CT_EQ_INT(t, 50, at.x);
|
||||||
|
CT_EQ_INT(t, 60, at.y);
|
||||||
|
|
||||||
|
releasecontext(a);
|
||||||
|
nexttrace(t, &f, Keyrelease, a);
|
||||||
|
CT_CHECK(t, !a->focused);
|
||||||
|
CT_CHECK(t, !a->caret.valid);
|
||||||
|
checkenginepreedit(t, "ん");
|
||||||
|
CT_EQ_PTR(t, b, testengineowner());
|
||||||
|
releasecontext(a);
|
||||||
|
notrace(t, &f);
|
||||||
|
dropcontext(a);
|
||||||
|
notrace(t, &f);
|
||||||
|
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/one"));
|
||||||
|
|
||||||
|
reused = newcontext(f.c1, "/context/stale-destroy");
|
||||||
|
CT_EQ_PTR(t, a, reused);
|
||||||
|
reused->focused = 1;
|
||||||
|
dropcontext(reused);
|
||||||
|
nexttrace(t, &f, Keyrelease, reused);
|
||||||
|
notrace(t, &f);
|
||||||
|
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/stale-destroy"));
|
||||||
|
checkenginepreedit(t, "ん");
|
||||||
|
CT_EQ_PTR(t, b, testengineowner());
|
||||||
|
|
||||||
|
reused = newcontext(f.c1, "/context/reused");
|
||||||
|
CT_EQ_PTR(t, a, reused);
|
||||||
|
reused->focused = 1;
|
||||||
|
dropconncontexts(f.c1);
|
||||||
|
nexttrace(t, &f, Keyrelease, reused);
|
||||||
|
notrace(t, &f);
|
||||||
|
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/reused"));
|
||||||
|
checkenginepreedit(t, "ん");
|
||||||
|
CT_EQ_PTR(t, b, testengineowner());
|
||||||
|
dropconncontexts(f.c1);
|
||||||
|
notrace(t, &f);
|
||||||
|
|
||||||
|
memset(&res, 0, sizeof res);
|
||||||
|
CT_CHECK(t, !processkey(b, 'x', Relmask, &res));
|
||||||
|
CT_CHECK(t, !res.eaten);
|
||||||
|
notrace(t, &f);
|
||||||
|
CT_EQ_PTR(t, b, testengineowner());
|
||||||
|
cleanup:
|
||||||
|
ibusend(&f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ibus_active_release_lifecycle(struct ct *t)
|
||||||
|
{
|
||||||
|
Ibusfix f;
|
||||||
|
Ictx *ctx, *reused;
|
||||||
|
Keyres res;
|
||||||
|
|
||||||
|
if(!ibusbegin(t, &f))
|
||||||
|
goto cleanup;
|
||||||
|
ctx = newcontext(f.c1, "/context/reset");
|
||||||
|
if(!CT_CHECK(t, ctx != nil))
|
||||||
|
goto cleanup;
|
||||||
|
ctx->focused = 1;
|
||||||
|
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
||||||
|
res = contextkey(t, &f, ctx, 'n', 0);
|
||||||
|
checkpreedit(t, "ん", &res);
|
||||||
|
|
||||||
|
memset(&res, 0, sizeof res);
|
||||||
|
sendrequest(ctx, Keyreset, 0, 0, &res);
|
||||||
|
nexttrace(t, &f, Keyreset, ctx);
|
||||||
|
checkpreedit(t, "", &res);
|
||||||
|
checkenginepreedit(t, "");
|
||||||
|
CT_CHECK(t, ctx->focused);
|
||||||
|
CT_EQ_PTR(t, ctx, testengineowner());
|
||||||
|
res = contextkey(t, &f, ctx, 'k', 0);
|
||||||
|
checkpreedit(t, "k", &res);
|
||||||
|
CT_EQ_PTR(t, ctx, testengineowner());
|
||||||
|
|
||||||
|
releasecontext(ctx);
|
||||||
|
nexttrace(t, &f, Keyrelease, ctx);
|
||||||
|
CT_CHECK(t, !ctx->focused);
|
||||||
|
checkenginepreedit(t, "");
|
||||||
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
|
releasecontext(ctx);
|
||||||
|
notrace(t, &f);
|
||||||
|
dropcontext(ctx);
|
||||||
|
notrace(t, &f);
|
||||||
|
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/reset"));
|
||||||
|
|
||||||
|
reused = newcontext(f.c1, "/context/destroy");
|
||||||
|
CT_EQ_PTR(t, ctx, reused);
|
||||||
|
reused->focused = 1;
|
||||||
|
contextkey(t, &f, reused, 'n', 0);
|
||||||
|
CT_EQ_PTR(t, reused, testengineowner());
|
||||||
|
dropcontext(reused);
|
||||||
|
nexttrace(t, &f, Keyrelease, reused);
|
||||||
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
|
notrace(t, &f);
|
||||||
|
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/destroy"));
|
||||||
|
|
||||||
|
reused = newcontext(f.c1, "/context/prune");
|
||||||
|
CT_EQ_PTR(t, ctx, reused);
|
||||||
|
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);
|
||||||
|
CT_EQ_PTR(t, reused, testengineowner());
|
||||||
|
dropconncontexts(f.c1);
|
||||||
|
nexttrace(t, &f, Keyrelease, reused);
|
||||||
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
|
notrace(t, &f);
|
||||||
|
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/prune"));
|
||||||
|
dropconncontexts(f.c1);
|
||||||
|
notrace(t, &f);
|
||||||
|
cleanup:
|
||||||
|
ibusend(&f);
|
||||||
|
}
|
||||||
821
tests/ipc_live_test.c
Normal file
821
tests/ipc_live_test.c
Normal file
@@ -0,0 +1,821 @@
|
|||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/inotify.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../ipc.h"
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Maxclients = 64,
|
||||||
|
Calltimeout = 4000,
|
||||||
|
Starttimeout = 8000,
|
||||||
|
Stoptimeout = 3000,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct Daemon Daemon;
|
||||||
|
typedef struct Response Response;
|
||||||
|
|
||||||
|
struct Daemon
|
||||||
|
{
|
||||||
|
pid_t pid;
|
||||||
|
int errfd;
|
||||||
|
char root[256];
|
||||||
|
char runtime[320];
|
||||||
|
char config[320];
|
||||||
|
char ibus[384];
|
||||||
|
char bus[448];
|
||||||
|
char home[320];
|
||||||
|
char socket[384];
|
||||||
|
char err[4096];
|
||||||
|
size_t nerr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Response
|
||||||
|
{
|
||||||
|
int eaten;
|
||||||
|
char commit[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
|
||||||
|
getlen(unsigned char p[Ipclensz])
|
||||||
|
{
|
||||||
|
return p[0] | (p[1] << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
readresponseuntil(int fd, int want, Response *res, int64_t deadline,
|
||||||
|
int quiet)
|
||||||
|
{
|
||||||
|
unsigned char hdr[Ipcresphdrsz], len[Ipclensz];
|
||||||
|
size_t n;
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
memset(res, 0, sizeof *res);
|
||||||
|
rv = readuntil(fd, hdr, sizeof hdr, deadline);
|
||||||
|
if(rv <= 0){
|
||||||
|
if(!quiet)
|
||||||
|
fail("read response header: %s",
|
||||||
|
rv == 0 ? "peer closed" : strerror(errno));
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
res->eaten = hdr[0] != 0;
|
||||||
|
n = getlen(hdr + 1);
|
||||||
|
if(n > Ipcfieldmax){
|
||||||
|
fail("invalid commit length %zu", n);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if(n != 0){
|
||||||
|
rv = readuntil(fd, res->commit, n, deadline);
|
||||||
|
if(rv <= 0){
|
||||||
|
if(!quiet)
|
||||||
|
fail("read commit: %s",
|
||||||
|
rv == 0 ? "peer closed" : strerror(errno));
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res->commit[n] = '\0';
|
||||||
|
if(!want)
|
||||||
|
return 1;
|
||||||
|
rv = readuntil(fd, len, sizeof len, deadline);
|
||||||
|
if(rv <= 0){
|
||||||
|
if(!quiet)
|
||||||
|
fail("read preedit length: %s",
|
||||||
|
rv == 0 ? "peer closed" : strerror(errno));
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
n = getlen(len);
|
||||||
|
if(n > Ipcfieldmax){
|
||||||
|
fail("invalid preedit length %zu", n);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if(n != 0){
|
||||||
|
rv = readuntil(fd, res->preedit, n, deadline);
|
||||||
|
if(rv <= 0){
|
||||||
|
if(!quiet)
|
||||||
|
fail("read preedit: %s",
|
||||||
|
rv == 0 ? "peer closed" : strerror(errno));
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res->preedit[n] = '\0';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sendkey(int fd, int want, uint32_t mod, uint32_t key)
|
||||||
|
{
|
||||||
|
unsigned char req[Ipcreqsz];
|
||||||
|
|
||||||
|
ipcpackreq(req, want, mod, key);
|
||||||
|
return ipcsend(fd, req, sizeof req) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sendreset(int fd, int want)
|
||||||
|
{
|
||||||
|
unsigned char req[Ipcreqsz];
|
||||||
|
|
||||||
|
ipcpackreset(req, want);
|
||||||
|
return ipcsend(fd, req, sizeof req) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
||||||
|
char *where)
|
||||||
|
{
|
||||||
|
Response res;
|
||||||
|
|
||||||
|
if(readresponseuntil(fd, want, &res, nowms() + Calltimeout, 0) != 1)
|
||||||
|
return 0;
|
||||||
|
if(res.eaten != eaten || strcmp(res.commit, commit) != 0 ||
|
||||||
|
(want && strcmp(res.preedit, preedit) != 0))
|
||||||
|
return fail("%s: eaten=%d commit=%s preedit=%s",
|
||||||
|
where, res.eaten, res.commit, res.preedit);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
requestreset(int fd, int want, int eaten, char *preedit, char *where)
|
||||||
|
{
|
||||||
|
if(!sendreset(fd, want))
|
||||||
|
return fail("send %s: %s", where, strerror(errno));
|
||||||
|
return expectresponse(fd, want, eaten, "", preedit, where);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
requestkey(int fd, int want, uint32_t mod, uint32_t key, int eaten,
|
||||||
|
char *commit, char *preedit, char *where)
|
||||||
|
{
|
||||||
|
if(!sendkey(fd, want, mod, key))
|
||||||
|
return fail("send %s: %s", where, strerror(errno));
|
||||||
|
return expectresponse(fd, want, eaten, commit, preedit, where);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
tryclient(char *path, int64_t deadline, int *client)
|
||||||
|
{
|
||||||
|
Response res;
|
||||||
|
int fd, rv;
|
||||||
|
|
||||||
|
fd = connectuntil(path, deadline);
|
||||||
|
if(fd < 0)
|
||||||
|
return errno == ETIMEDOUT ? -1 : 0;
|
||||||
|
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
||||||
|
close(fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
rv = readresponseuntil(fd, 1, &res, deadline, 1);
|
||||||
|
if(rv != 1){
|
||||||
|
close(fd);
|
||||||
|
return rv < 0 && errno == ETIMEDOUT ? -1 : 0;
|
||||||
|
}
|
||||||
|
if(res.eaten || res.commit[0] != '\0' || res.preedit[0] != '\0'){
|
||||||
|
close(fd);
|
||||||
|
fail("replacement client returned invalid modifier response");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*client = fd;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
waitslot(char *path, int *client, char *where)
|
||||||
|
{
|
||||||
|
int rv;
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
deadline = nowms() + Calltimeout;
|
||||||
|
for(;;){
|
||||||
|
rv = tryclient(path, deadline, client);
|
||||||
|
if(rv > 0)
|
||||||
|
return 1;
|
||||||
|
if(rv < 0 || leftms(deadline) == 0)
|
||||||
|
return fail("%s: timed out waiting for a worker slot", where);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
overflowrejected(char *path)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
unsigned char byte;
|
||||||
|
ssize_t n;
|
||||||
|
int fd, timeout;
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
deadline = nowms() + Calltimeout;
|
||||||
|
fd = connectuntil(path, deadline);
|
||||||
|
if(fd < 0)
|
||||||
|
return fail("overflow connect: %s", strerror(errno));
|
||||||
|
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
||||||
|
close(fd);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
for(;;){
|
||||||
|
timeout = leftms(deadline);
|
||||||
|
if(timeout == 0){
|
||||||
|
close(fd);
|
||||||
|
return fail("overflow client was not rejected");
|
||||||
|
}
|
||||||
|
pfd.fd = fd;
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
pfd.revents = 0;
|
||||||
|
n = poll(&pfd, 1, timeout);
|
||||||
|
if(n < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if(n <= 0){
|
||||||
|
close(fd);
|
||||||
|
return fail("poll overflow client: %s",
|
||||||
|
n == 0 ? "timed out" : strerror(errno));
|
||||||
|
}
|
||||||
|
n = recv(fd, &byte, 1, 0);
|
||||||
|
if(n < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if(n == 0 || (n < 0 && (errno == ECONNRESET || errno == EPIPE))){
|
||||||
|
close(fd);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if(n < 0){
|
||||||
|
close(fd);
|
||||||
|
return fail("read overflow rejection: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
return fail("overflow client received a response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
runlistener(Daemon *d)
|
||||||
|
{
|
||||||
|
int client[Maxclients];
|
||||||
|
int i, ok;
|
||||||
|
|
||||||
|
for(i = 0; i < Maxclients; i++)
|
||||||
|
client[i] = -1;
|
||||||
|
ok = 0;
|
||||||
|
client[0] = connectuntil(d->socket, nowms() + Calltimeout);
|
||||||
|
if(client[0] < 0){
|
||||||
|
fail("connect first client: %s", strerror(errno));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if(!requestkey(client[0], 1, Mctrl, 'n', 1, "", "", "select Japanese") ||
|
||||||
|
!requestkey(client[0], 1, 0, 'k', 1, "", "k", "GTK preedit") ||
|
||||||
|
!requestreset(client[0], 1, 1, "", "active reset"))
|
||||||
|
goto out;
|
||||||
|
if(!sendkey(client[0], 0, 0, 'k') ||
|
||||||
|
!sendkey(client[0], 1, 0, Kmodfirst)){
|
||||||
|
fail("send pipelined XIM/GTK requests: %s", strerror(errno));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if(!expectresponse(client[0], 0, 1, "", "", "XIM framing") ||
|
||||||
|
!expectresponse(client[0], 1, 0, "", "k", "GTK after XIM framing"))
|
||||||
|
goto out;
|
||||||
|
for(i = 1; i < Maxclients; i++){
|
||||||
|
client[i] = connectuntil(d->socket, nowms() + Calltimeout);
|
||||||
|
if(client[i] < 0){
|
||||||
|
fail("connect capacity client %d: %s", i, strerror(errno));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if(!requestkey(client[i], 1, 0, Kmodfirst, 0, "", "",
|
||||||
|
"capacity modifier"))
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if(!overflowrejected(d->socket))
|
||||||
|
goto out;
|
||||||
|
close(client[Maxclients-1]);
|
||||||
|
client[Maxclients-1] = -1;
|
||||||
|
if(!waitslot(d->socket, &client[Maxclients-1], "inactive slot recovery"))
|
||||||
|
goto out;
|
||||||
|
close(client[0]);
|
||||||
|
client[0] = -1;
|
||||||
|
if(!waitslot(d->socket, &client[0], "active slot recovery") ||
|
||||||
|
!requestkey(client[0], 1, 0, 'a', 1, "", "あ",
|
||||||
|
"post-disconnect composition"))
|
||||||
|
goto out;
|
||||||
|
ok = 1;
|
||||||
|
out:
|
||||||
|
for(i = 0; i < Maxclients; i++)
|
||||||
|
if(client[i] >= 0)
|
||||||
|
close(client[i]);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
Daemon daemon;
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
if(argc != 3){
|
||||||
|
fprintf(stderr, "usage: ipc_live_test strans mapdir\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
ok = startdaemon(&daemon, argv[1], argv[2]);
|
||||||
|
if(ok)
|
||||||
|
ok = runlistener(&daemon);
|
||||||
|
if(!ok)
|
||||||
|
showerrors(&daemon);
|
||||||
|
if(!stopdaemon(&daemon))
|
||||||
|
ok = 0;
|
||||||
|
if(!ok)
|
||||||
|
return 1;
|
||||||
|
printf("ipc live listener: ok\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -9,13 +9,16 @@ from pathlib import Path
|
|||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
MKEMOJI = ROOT / "map" / "mkemoji"
|
MKEMOJI = ROOT / "map" / "mkemoji"
|
||||||
|
TIMEOUT = 10
|
||||||
|
|
||||||
|
|
||||||
def generate(source=None):
|
def generate(source=None):
|
||||||
args = [sys.executable, "-B", str(MKEMOJI)]
|
args = [sys.executable, "-B", str(MKEMOJI)]
|
||||||
if source is not None:
|
if source is not None:
|
||||||
args.append(str(source))
|
args.append(str(source))
|
||||||
return subprocess.run(args, capture_output=True, text=True, check=False)
|
return subprocess.run(
|
||||||
|
args, capture_output=True, text=True, check=False, timeout=TIMEOUT
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def table(output):
|
def table(output):
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from pathlib import Path
|
|||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
IMPORT = ROOT / "map" / "libhangul2hanja"
|
IMPORT = ROOT / "map" / "libhangul2hanja"
|
||||||
GENERATE = ROOT / "map" / "mkhanja"
|
GENERATE = ROOT / "map" / "mkhanja"
|
||||||
|
TIMEOUT = 10
|
||||||
|
|
||||||
|
|
||||||
def run(program, source=None, stdin=None, hostile=False):
|
def run(program, source=None, stdin=None, hostile=False):
|
||||||
@@ -29,6 +30,7 @@ def run(program, source=None, stdin=None, hostile=False):
|
|||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
env=env,
|
env=env,
|
||||||
check=False,
|
check=False,
|
||||||
|
timeout=TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
370
tests/server_test.c
Normal file
370
tests/server_test.c
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
#include <errno.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../srv.c"
|
||||||
|
#include "../cutest/cutest.h"
|
||||||
|
|
||||||
|
void testengineinit(int);
|
||||||
|
void testenginehandle(Keyreq*);
|
||||||
|
void* testengineowner(void);
|
||||||
|
void testenginepreedit(Str*);
|
||||||
|
|
||||||
|
#undef recv
|
||||||
|
|
||||||
|
typedef struct Enginegate Enginegate;
|
||||||
|
typedef struct Testclient Testclient;
|
||||||
|
|
||||||
|
struct Enginegate
|
||||||
|
{
|
||||||
|
Channel *seen;
|
||||||
|
Channel *go;
|
||||||
|
Channel *stop;
|
||||||
|
Channel *done;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Testclient
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
int peer;
|
||||||
|
dev_t dev;
|
||||||
|
ino_t ino;
|
||||||
|
Channel *done;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
enginegate(void *arg)
|
||||||
|
{
|
||||||
|
Enginegate *g;
|
||||||
|
Keyreq req;
|
||||||
|
uchar token;
|
||||||
|
Alt alts[] = {
|
||||||
|
{keyc, &req, CHANRCV, nil},
|
||||||
|
{nil, &token, CHANRCV, nil},
|
||||||
|
{nil, nil, CHANEND, nil},
|
||||||
|
};
|
||||||
|
|
||||||
|
g = arg;
|
||||||
|
alts[1].c = g->stop;
|
||||||
|
for(;;)
|
||||||
|
switch(alt(alts)){
|
||||||
|
case 0:
|
||||||
|
chansend(g->seen, &req);
|
||||||
|
chanrecv(g->go, &token);
|
||||||
|
testenginehandle(&req);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
chansend(g->done, &token);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clientproc(void *arg)
|
||||||
|
{
|
||||||
|
Testclient *client;
|
||||||
|
uchar token;
|
||||||
|
|
||||||
|
client = arg;
|
||||||
|
clientthread((void*)(uintptr)client->fd);
|
||||||
|
token = 0;
|
||||||
|
chansend(client->done, &token);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
startclient(struct ct *t, Testclient *client)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
int fd[2];
|
||||||
|
uchar token;
|
||||||
|
|
||||||
|
memset(client, 0, sizeof *client);
|
||||||
|
client->fd = -1;
|
||||||
|
client->peer = -1;
|
||||||
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0)
|
||||||
|
return CT_ERRORF(t, "socketpair failed: %s", strerror(errno));
|
||||||
|
client->fd = fd[0];
|
||||||
|
client->peer = fd[1];
|
||||||
|
if(fstat(client->fd, &st) < 0){
|
||||||
|
close(client->fd);
|
||||||
|
close(client->peer);
|
||||||
|
client->fd = -1;
|
||||||
|
client->peer = -1;
|
||||||
|
return CT_ERRORF(t, "socket stat failed: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
client->dev = st.st_dev;
|
||||||
|
client->ino = st.st_ino;
|
||||||
|
client->done = chancreate(sizeof token, 0);
|
||||||
|
token = 0;
|
||||||
|
if(channbsend(clientc, &token) <= 0){
|
||||||
|
close(client->fd);
|
||||||
|
close(client->peer);
|
||||||
|
chanfree(client->done);
|
||||||
|
client->fd = -1;
|
||||||
|
client->peer = -1;
|
||||||
|
client->done = nil;
|
||||||
|
return CT_ERRORF(t, "client slot unavailable");
|
||||||
|
}
|
||||||
|
if(proccreate(clientproc, client, 8192) < 0){
|
||||||
|
chanrecv(clientc, &token);
|
||||||
|
close(client->fd);
|
||||||
|
close(client->peer);
|
||||||
|
chanfree(client->done);
|
||||||
|
client->fd = -1;
|
||||||
|
client->peer = -1;
|
||||||
|
client->done = nil;
|
||||||
|
return CT_ERRORF(t, "client worker creation failed");
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sendkey(struct ct *t, Testclient *client, int want, u32int mod, u32int key)
|
||||||
|
{
|
||||||
|
uchar req[Ipcreqsz];
|
||||||
|
|
||||||
|
ipcpackreq(req, want, mod, key);
|
||||||
|
if(ipcsend(client->peer, req, sizeof req) == 0)
|
||||||
|
return 1;
|
||||||
|
return CT_ERRORF(t, "request send failed: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sendreset(struct ct *t, Testclient *client, int want)
|
||||||
|
{
|
||||||
|
uchar req[Ipcreqsz];
|
||||||
|
|
||||||
|
ipcpackreset(req, want);
|
||||||
|
if(ipcsend(client->peer, req, sizeof req) == 0)
|
||||||
|
return 1;
|
||||||
|
return CT_ERRORF(t, "reset send failed: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Keyreq
|
||||||
|
nextrequest(struct ct *t, Enginegate *g, int op)
|
||||||
|
{
|
||||||
|
Keyreq req;
|
||||||
|
|
||||||
|
memset(&req, 0, sizeof req);
|
||||||
|
chanrecv(g->seen, &req);
|
||||||
|
CT_EQ_INT(t, op, req.op);
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
allowrequest(Enginegate *g)
|
||||||
|
{
|
||||||
|
uchar token;
|
||||||
|
|
||||||
|
token = 0;
|
||||||
|
chansend(g->go, &token);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
readreply(struct ct *t, Testclient *client, int want, char *preedit, int npreedit)
|
||||||
|
{
|
||||||
|
char commit[Maxutf];
|
||||||
|
Ipcresp resp;
|
||||||
|
|
||||||
|
if(ipcreadresp(client->peer, want, commit, sizeof commit,
|
||||||
|
preedit, npreedit, &resp) < 0)
|
||||||
|
return CT_ERRORF(t, "response read failed: %s", strerror(errno));
|
||||||
|
CT_EQ_STR(t, "", commit);
|
||||||
|
return resp.eaten;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
waitclient(struct ct *t, Testclient *client)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
uchar token;
|
||||||
|
|
||||||
|
chanrecv(client->done, &token);
|
||||||
|
errno = 0;
|
||||||
|
if(fstat(client->fd, &st) == 0)
|
||||||
|
CT_CHECK(t, st.st_dev != client->dev || st.st_ino != client->ino);
|
||||||
|
else
|
||||||
|
CT_EQ_INT(t, EBADF, errno);
|
||||||
|
chanfree(client->done);
|
||||||
|
client->done = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
disconnectclient(struct ct *t, Enginegate *g, Testclient *client, void *owner)
|
||||||
|
{
|
||||||
|
Keyreq req;
|
||||||
|
uchar token;
|
||||||
|
|
||||||
|
close(client->peer);
|
||||||
|
client->peer = -1;
|
||||||
|
req = nextrequest(t, g, Keyrelease);
|
||||||
|
if(owner != nil)
|
||||||
|
CT_EQ_PTR(t, owner, req.owner);
|
||||||
|
CT_CHECK(t, channbrecv(client->done, &token) <= 0);
|
||||||
|
allowrequest(g);
|
||||||
|
waitclient(t, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
server_connection_ownership(struct ct *t)
|
||||||
|
{
|
||||||
|
Channel *oldclientc;
|
||||||
|
Enginegate gate;
|
||||||
|
Testclient a, b, c;
|
||||||
|
Keyreq req;
|
||||||
|
Drawcmd dc;
|
||||||
|
Str shown;
|
||||||
|
char preedit[Maxutf];
|
||||||
|
void *aowner, *bowner, *cowner;
|
||||||
|
uchar byte, token;
|
||||||
|
ssize_t n;
|
||||||
|
int gateactive;
|
||||||
|
|
||||||
|
memset(&gate, 0, sizeof gate);
|
||||||
|
memset(&a, 0, sizeof a);
|
||||||
|
memset(&b, 0, sizeof b);
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
a.fd = a.peer = b.fd = b.peer = c.fd = c.peer = -1;
|
||||||
|
aowner = bowner = cowner = nil;
|
||||||
|
oldclientc = clientc;
|
||||||
|
while(channbrecv(drawc, &dc) > 0)
|
||||||
|
;
|
||||||
|
clientc = chancreate(sizeof token, 3);
|
||||||
|
gate.seen = chancreate(sizeof(Keyreq), 0);
|
||||||
|
gate.go = chancreate(sizeof token, 0);
|
||||||
|
gate.stop = chancreate(sizeof token, 0);
|
||||||
|
gate.done = chancreate(sizeof token, 0);
|
||||||
|
testengineinit(LangJP);
|
||||||
|
gateactive = proccreate(enginegate, &gate, 8192) >= 0;
|
||||||
|
if(!CT_CHECK(t, gateactive))
|
||||||
|
goto cleanup;
|
||||||
|
if(!startclient(t, &a) || !startclient(t, &b))
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
if(!sendkey(t, &a, 1, 0, 'k'))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &gate, Keypress);
|
||||||
|
aowner = req.owner;
|
||||||
|
allowrequest(&gate);
|
||||||
|
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
|
||||||
|
CT_EQ_STR(t, "k", preedit);
|
||||||
|
if(!sendkey(t, &a, 1, 0, 'a'))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &gate, Keypress);
|
||||||
|
CT_EQ_PTR(t, aowner, req.owner);
|
||||||
|
allowrequest(&gate);
|
||||||
|
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
|
||||||
|
CT_EQ_STR(t, "か", preedit);
|
||||||
|
CT_EQ_PTR(t, aowner, testengineowner());
|
||||||
|
|
||||||
|
if(!sendkey(t, &b, 1, 0, 'n'))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &gate, Keypress);
|
||||||
|
bowner = req.owner;
|
||||||
|
CT_CHECK(t, bowner != aowner);
|
||||||
|
allowrequest(&gate);
|
||||||
|
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
||||||
|
CT_EQ_STR(t, "ん", preedit);
|
||||||
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
|
|
||||||
|
if(!sendreset(t, &a, 1))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &gate, Keyreset);
|
||||||
|
CT_EQ_PTR(t, aowner, req.owner);
|
||||||
|
allowrequest(&gate);
|
||||||
|
CT_CHECK(t, readreply(t, &a, 1, preedit, sizeof preedit));
|
||||||
|
CT_EQ_STR(t, "", preedit);
|
||||||
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
|
if(!sendkey(t, &b, 1, 0, Kmodfirst))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &gate, Keypress);
|
||||||
|
allowrequest(&gate);
|
||||||
|
CT_CHECK(t, !readreply(t, &b, 1, preedit, sizeof preedit));
|
||||||
|
CT_EQ_STR(t, "ん", preedit);
|
||||||
|
|
||||||
|
disconnectclient(t, &gate, &a, aowner);
|
||||||
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
|
if(!sendkey(t, &b, 1, 0, 'y'))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &gate, Keypress);
|
||||||
|
allowrequest(&gate);
|
||||||
|
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
||||||
|
if(!sendkey(t, &b, 1, 0, 'a'))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &gate, Keypress);
|
||||||
|
allowrequest(&gate);
|
||||||
|
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
||||||
|
CT_EQ_STR(t, "にゃ", preedit);
|
||||||
|
|
||||||
|
if(!sendreset(t, &b, 1))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &gate, Keyreset);
|
||||||
|
CT_EQ_PTR(t, bowner, req.owner);
|
||||||
|
allowrequest(&gate);
|
||||||
|
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
||||||
|
CT_EQ_STR(t, "", preedit);
|
||||||
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
|
|
||||||
|
if(!sendkey(t, &b, 0, 0, 'k'))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &gate, Keypress);
|
||||||
|
CT_EQ_PTR(t, bowner, req.owner);
|
||||||
|
allowrequest(&gate);
|
||||||
|
CT_CHECK(t, readreply(t, &b, 0, nil, 0));
|
||||||
|
errno = 0;
|
||||||
|
n = recv(b.peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
|
||||||
|
CT_EQ_INT(t, -1, n);
|
||||||
|
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
|
||||||
|
CT_EQ_PTR(t, bowner, testengineowner());
|
||||||
|
if(!sendkey(t, &b, 1, 0, 'a'))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &gate, Keypress);
|
||||||
|
allowrequest(&gate);
|
||||||
|
CT_CHECK(t, readreply(t, &b, 1, preedit, sizeof preedit));
|
||||||
|
CT_EQ_STR(t, "か", preedit);
|
||||||
|
|
||||||
|
disconnectclient(t, &gate, &b, bowner);
|
||||||
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
|
testenginepreedit(&shown);
|
||||||
|
CT_EQ_INT(t, 0, shown.n);
|
||||||
|
|
||||||
|
if(!startclient(t, &c))
|
||||||
|
goto cleanup;
|
||||||
|
if(!sendkey(t, &c, 1, 0, 'k'))
|
||||||
|
goto cleanup;
|
||||||
|
req = nextrequest(t, &gate, Keypress);
|
||||||
|
cowner = req.owner;
|
||||||
|
close(c.peer);
|
||||||
|
c.peer = -1;
|
||||||
|
allowrequest(&gate);
|
||||||
|
req = nextrequest(t, &gate, Keyrelease);
|
||||||
|
CT_EQ_PTR(t, cowner, req.owner);
|
||||||
|
CT_EQ_PTR(t, cowner, testengineowner());
|
||||||
|
CT_CHECK(t, channbrecv(c.done, &token) <= 0);
|
||||||
|
allowrequest(&gate);
|
||||||
|
waitclient(t, &c);
|
||||||
|
CT_EQ_PTR(t, nil, testengineowner());
|
||||||
|
CT_CHECK(t, channbrecv(clientc, &token) <= 0);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if(a.peer >= 0)
|
||||||
|
disconnectclient(t, &gate, &a, aowner);
|
||||||
|
if(b.peer >= 0)
|
||||||
|
disconnectclient(t, &gate, &b, bowner);
|
||||||
|
if(c.peer >= 0)
|
||||||
|
disconnectclient(t, &gate, &c, cowner);
|
||||||
|
if(gateactive){
|
||||||
|
token = 0;
|
||||||
|
chansend(gate.stop, &token);
|
||||||
|
chanrecv(gate.done, &token);
|
||||||
|
}
|
||||||
|
chanfree(gate.seen);
|
||||||
|
chanfree(gate.go);
|
||||||
|
chanfree(gate.stop);
|
||||||
|
chanfree(gate.done);
|
||||||
|
chanfree(clientc);
|
||||||
|
clientc = oldclientc;
|
||||||
|
while(channbrecv(drawc, &dc) > 0)
|
||||||
|
;
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ from pathlib import Path
|
|||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
CONVERTER = ROOT / "map" / "skk2ktrans"
|
CONVERTER = ROOT / "map" / "skk2ktrans"
|
||||||
FIXTURES = Path(__file__).parent / "data" / "skk"
|
FIXTURES = Path(__file__).parent / "data" / "skk"
|
||||||
|
TIMEOUT = 10
|
||||||
|
|
||||||
|
|
||||||
class Skk2KtransTest(unittest.TestCase):
|
class Skk2KtransTest(unittest.TestCase):
|
||||||
@@ -19,27 +20,31 @@ class Skk2KtransTest(unittest.TestCase):
|
|||||||
input_path = Path(directory) / "source.skk"
|
input_path = Path(directory) / "source.skk"
|
||||||
input_path.write_bytes(source.encode("euc_jp"))
|
input_path.write_bytes(source.encode("euc_jp"))
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[CONVERTER, input_path], capture_output=True, check=False)
|
[CONVERTER, input_path], capture_output=True, check=False,
|
||||||
|
timeout=TIMEOUT)
|
||||||
self.assertEqual(result.returncode, 0, result.stderr.decode())
|
self.assertEqual(result.returncode, 0, result.stderr.decode())
|
||||||
self.assertEqual(result.stdout, expected)
|
self.assertEqual(result.stdout, expected)
|
||||||
|
|
||||||
def test_invalid_euc_jp_is_rejected(self):
|
def test_invalid_euc_jp_is_rejected(self):
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[CONVERTER], input=b"\xff\xff\n", capture_output=True, check=False)
|
[CONVERTER], input=b"\xff\xff\n", capture_output=True, check=False,
|
||||||
|
timeout=TIMEOUT)
|
||||||
self.assertNotEqual(result.returncode, 0)
|
self.assertNotEqual(result.returncode, 0)
|
||||||
self.assertTrue(result.stderr)
|
self.assertTrue(result.stderr)
|
||||||
|
|
||||||
def test_escaped_candidate_is_rejected(self):
|
def test_escaped_candidate_is_rejected(self):
|
||||||
source = b"key /escaped\\/slash/\n"
|
source = b"key /escaped\\/slash/\n"
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[CONVERTER], input=source, capture_output=True, check=False)
|
[CONVERTER], input=source, capture_output=True, check=False,
|
||||||
|
timeout=TIMEOUT)
|
||||||
self.assertNotEqual(result.returncode, 0)
|
self.assertNotEqual(result.returncode, 0)
|
||||||
self.assertIn(b"escaped candidates are unsupported", result.stderr)
|
self.assertIn(b"escaped candidates are unsupported", result.stderr)
|
||||||
|
|
||||||
def test_expression_candidate_is_rejected(self):
|
def test_expression_candidate_is_rejected(self):
|
||||||
source = b'key /(concat "a/b")/literal/\n'
|
source = b'key /(concat "a/b")/literal/\n'
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[CONVERTER], input=source, capture_output=True, check=False)
|
[CONVERTER], input=source, capture_output=True, check=False,
|
||||||
|
timeout=TIMEOUT)
|
||||||
self.assertNotEqual(result.returncode, 0)
|
self.assertNotEqual(result.returncode, 0)
|
||||||
self.assertIn(b"expression candidates are unsupported", result.stderr)
|
self.assertIn(b"expression candidates are unsupported", result.stderr)
|
||||||
|
|
||||||
@@ -50,7 +55,8 @@ class Skk2KtransTest(unittest.TestCase):
|
|||||||
first.write_bytes(b"a /A/")
|
first.write_bytes(b"a /A/")
|
||||||
second.write_bytes(b"b /B/\n")
|
second.write_bytes(b"b /B/\n")
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[CONVERTER, first, second], capture_output=True, check=False)
|
[CONVERTER, first, second], capture_output=True, check=False,
|
||||||
|
timeout=TIMEOUT)
|
||||||
self.assertEqual(result.returncode, 0, result.stderr.decode())
|
self.assertEqual(result.returncode, 0, result.stderr.decode())
|
||||||
self.assertEqual(result.stdout, b"a\tA\nb\tB\n")
|
self.assertEqual(result.stdout, b"a\tA\nb\tB\n")
|
||||||
|
|
||||||
|
|||||||
10
tests/test.h
10
tests/test.h
@@ -17,10 +17,8 @@ void hmap_long_utf8_keys(struct ct*);
|
|||||||
void hmap_binary_keys_and_invalid_lengths(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_optional_outputs_and_invalid_lengths(struct ct*);
|
||||||
void wayland_press_release_state(struct ct*);
|
|
||||||
void wayland_repeat_state(struct ct*);
|
|
||||||
void wayland_repeat_cancellation(struct ct*);
|
|
||||||
void popup_layout(struct ct*);
|
void popup_layout(struct ct*);
|
||||||
|
void font_render(struct ct*);
|
||||||
void production_maps_load(struct ct*);
|
void production_maps_load(struct ct*);
|
||||||
void transmap_states(struct ct*);
|
void transmap_states(struct ct*);
|
||||||
void korean_sequences(struct ct*);
|
void korean_sequences(struct ct*);
|
||||||
@@ -36,6 +34,7 @@ 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_dictionary_queue_latest_wins(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_caret(struct ct*);
|
void engine_active_owner_caret(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*);
|
||||||
@@ -71,3 +70,8 @@ void ipc_response_empty_and_preedit(struct ct*);
|
|||||||
void ipc_response_max_and_drain(struct ct*);
|
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 ibus_machine_id_fallback(struct ct*);
|
||||||
|
void ibus_startup_requires_ownership(struct ct*);
|
||||||
|
void ibus_context_lifecycle(struct ct*);
|
||||||
|
void ibus_active_release_lifecycle(struct ct*);
|
||||||
|
|||||||
@@ -71,10 +71,8 @@ static const struct ct_test tests[] = {
|
|||||||
{ "hmap/binary-invalid-lengths", hmap_binary_keys_and_invalid_lengths },
|
{ "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/optional-invalid-lengths", trie_optional_outputs_and_invalid_lengths },
|
||||||
{ "wayland/press-release", wayland_press_release_state },
|
|
||||||
{ "wayland/repeat", wayland_repeat_state },
|
|
||||||
{ "wayland/repeat-cancellation", wayland_repeat_cancellation },
|
|
||||||
{ "popup/layout", popup_layout },
|
{ "popup/layout", popup_layout },
|
||||||
|
{ "font/render", font_render },
|
||||||
{ "map/production-lifecycle", production_maps_load },
|
{ "map/production-lifecycle", production_maps_load },
|
||||||
{ "transmap/states", transmap_states },
|
{ "transmap/states", transmap_states },
|
||||||
{ "hangul/sequences", korean_sequences },
|
{ "hangul/sequences", korean_sequences },
|
||||||
@@ -90,6 +88,7 @@ static const struct ct_test tests[] = {
|
|||||||
{ "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/dictionary-queue-latest", engine_dictionary_queue_latest_wins },
|
||||||
{ "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-caret", engine_active_owner_caret },
|
{ "engine/active-owner-caret", engine_active_owner_caret },
|
||||||
{ "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 },
|
||||||
@@ -125,6 +124,11 @@ static const struct ct_test tests[] = {
|
|||||||
{ "ipc/response-max-drain", ipc_response_max_and_drain },
|
{ "ipc/response-max-drain", ipc_response_max_and_drain },
|
||||||
{ "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 },
|
||||||
|
{ "ibus/machine-id-fallback", ibus_machine_id_fallback },
|
||||||
|
{ "ibus/startup-requires-ownership", ibus_startup_requires_ownership },
|
||||||
|
{ "ibus/context-lifecycle", ibus_context_lifecycle },
|
||||||
|
{ "ibus/active-release-lifecycle", ibus_active_release_lifecycle },
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
#include "test.h"
|
|
||||||
|
|
||||||
void
|
|
||||||
wayland_press_release_state(struct ct *t)
|
|
||||||
{
|
|
||||||
Wlstate s;
|
|
||||||
|
|
||||||
wlstateinit(&s);
|
|
||||||
CT_CHECK(t, wlstatepress(&s, 30, 1, 0, 0));
|
|
||||||
CT_CHECK(t, !wlstaterelease(&s, 30));
|
|
||||||
CT_CHECK(t, wlstaterelease(&s, 30));
|
|
||||||
CT_CHECK(t, wlstatepress(&s, 31, 0, 0, 0));
|
|
||||||
CT_CHECK(t, wlstaterelease(&s, 31));
|
|
||||||
CT_CHECK(t, !wlstatepress(&s, Wlkeymax, 1, 0, 0));
|
|
||||||
CT_CHECK(t, wlstaterelease(&s, Wlkeymax));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
wayland_repeat_state(struct ct *t)
|
|
||||||
{
|
|
||||||
Wlstate s;
|
|
||||||
u32int key;
|
|
||||||
|
|
||||||
wlstateinit(&s);
|
|
||||||
wlstaterepeatinfo(&s, 25, 400);
|
|
||||||
CT_CHECK(t, wlstatepress(&s, 14, 1, 1, 100));
|
|
||||||
CT_EQ_INT(t, 400, wlstatetimeout(&s, 100));
|
|
||||||
CT_CHECK(t, !wlstaterepeat(&s, 499, &key));
|
|
||||||
CT_CHECK(t, wlstaterepeat(&s, 500, &key));
|
|
||||||
CT_EQ_UINT(t, 14, key);
|
|
||||||
CT_EQ_INT(t, 40, wlstatetimeout(&s, 500));
|
|
||||||
CT_CHECK(t, !wlstaterelease(&s, 14));
|
|
||||||
CT_EQ_INT(t, -1, wlstatetimeout(&s, 500));
|
|
||||||
|
|
||||||
CT_CHECK(t, wlstatepress(&s, 15, 1, 0, 600));
|
|
||||||
CT_EQ_INT(t, -1, wlstatetimeout(&s, 600));
|
|
||||||
CT_CHECK(t, !wlstaterelease(&s, 15));
|
|
||||||
CT_CHECK(t, wlstatepress(&s, 16, 0, 1, 600));
|
|
||||||
CT_EQ_INT(t, -1, wlstatetimeout(&s, 600));
|
|
||||||
CT_CHECK(t, wlstaterelease(&s, 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
wayland_repeat_cancellation(struct ct *t)
|
|
||||||
{
|
|
||||||
Wlstate s;
|
|
||||||
|
|
||||||
wlstateinit(&s);
|
|
||||||
wlstaterepeatinfo(&s, 20, 300);
|
|
||||||
CT_CHECK(t, wlstatepress(&s, 20, 1, 1, 0));
|
|
||||||
CT_CHECK(t, wlstatepress(&s, 19, 0, 0, 10));
|
|
||||||
CT_EQ_INT(t, -1, wlstatetimeout(&s, 10));
|
|
||||||
CT_CHECK(t, !wlstaterelease(&s, 20));
|
|
||||||
CT_CHECK(t, wlstaterelease(&s, 19));
|
|
||||||
CT_CHECK(t, wlstatepress(&s, 20, 1, 1, 0));
|
|
||||||
wlstaterepeatinfo(&s, 30, 200);
|
|
||||||
CT_EQ_INT(t, -1, wlstatetimeout(&s, 0));
|
|
||||||
CT_CHECK(t, !wlstaterelease(&s, 20));
|
|
||||||
CT_CHECK(t, wlstatepress(&s, 21, 1, 1, 0));
|
|
||||||
wlstatecancelrepeat(&s);
|
|
||||||
CT_EQ_INT(t, -1, wlstatetimeout(&s, 0));
|
|
||||||
CT_CHECK(t, !wlstaterelease(&s, 21));
|
|
||||||
CT_CHECK(t, wlstatepress(&s, 22, 1, 1, 0));
|
|
||||||
wlstateclear(&s);
|
|
||||||
CT_CHECK(t, wlstaterelease(&s, 22));
|
|
||||||
}
|
|
||||||
@@ -1,280 +0,0 @@
|
|||||||
/* Generated by wayland-scanner 1.23.1 */
|
|
||||||
|
|
||||||
#ifndef VIRTUAL_KEYBOARD_UNSTABLE_V1_CLIENT_PROTOCOL_H
|
|
||||||
#define VIRTUAL_KEYBOARD_UNSTABLE_V1_CLIENT_PROTOCOL_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include "wayland-client.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @page page_virtual_keyboard_unstable_v1 The virtual_keyboard_unstable_v1 protocol
|
|
||||||
* @section page_ifaces_virtual_keyboard_unstable_v1 Interfaces
|
|
||||||
* - @subpage page_iface_zwp_virtual_keyboard_v1 - virtual keyboard
|
|
||||||
* - @subpage page_iface_zwp_virtual_keyboard_manager_v1 - virtual keyboard manager
|
|
||||||
* @section page_copyright_virtual_keyboard_unstable_v1 Copyright
|
|
||||||
* <pre>
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
struct wl_seat;
|
|
||||||
struct zwp_virtual_keyboard_manager_v1;
|
|
||||||
struct zwp_virtual_keyboard_v1;
|
|
||||||
|
|
||||||
#ifndef ZWP_VIRTUAL_KEYBOARD_V1_INTERFACE
|
|
||||||
#define ZWP_VIRTUAL_KEYBOARD_V1_INTERFACE
|
|
||||||
/**
|
|
||||||
* @page page_iface_zwp_virtual_keyboard_v1 zwp_virtual_keyboard_v1
|
|
||||||
* @section page_iface_zwp_virtual_keyboard_v1_desc Description
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* @section page_iface_zwp_virtual_keyboard_v1_api API
|
|
||||||
* See @ref iface_zwp_virtual_keyboard_v1.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @defgroup iface_zwp_virtual_keyboard_v1 The zwp_virtual_keyboard_v1 interface
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
extern const struct wl_interface zwp_virtual_keyboard_v1_interface;
|
|
||||||
#endif
|
|
||||||
#ifndef ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_INTERFACE
|
|
||||||
#define ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_INTERFACE
|
|
||||||
/**
|
|
||||||
* @page page_iface_zwp_virtual_keyboard_manager_v1 zwp_virtual_keyboard_manager_v1
|
|
||||||
* @section page_iface_zwp_virtual_keyboard_manager_v1_desc Description
|
|
||||||
*
|
|
||||||
* A virtual keyboard manager allows an application to provide keyboard
|
|
||||||
* input events as if they came from a physical keyboard.
|
|
||||||
* @section page_iface_zwp_virtual_keyboard_manager_v1_api API
|
|
||||||
* See @ref iface_zwp_virtual_keyboard_manager_v1.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @defgroup iface_zwp_virtual_keyboard_manager_v1 The zwp_virtual_keyboard_manager_v1 interface
|
|
||||||
*
|
|
||||||
* A virtual keyboard manager allows an application to provide keyboard
|
|
||||||
* input events as if they came from a physical keyboard.
|
|
||||||
*/
|
|
||||||
extern const struct wl_interface zwp_virtual_keyboard_manager_v1_interface;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef ZWP_VIRTUAL_KEYBOARD_V1_ERROR_ENUM
|
|
||||||
#define ZWP_VIRTUAL_KEYBOARD_V1_ERROR_ENUM
|
|
||||||
enum zwp_virtual_keyboard_v1_error {
|
|
||||||
/**
|
|
||||||
* No keymap was set
|
|
||||||
*/
|
|
||||||
ZWP_VIRTUAL_KEYBOARD_V1_ERROR_NO_KEYMAP = 0,
|
|
||||||
};
|
|
||||||
#endif /* ZWP_VIRTUAL_KEYBOARD_V1_ERROR_ENUM */
|
|
||||||
|
|
||||||
#define ZWP_VIRTUAL_KEYBOARD_V1_KEYMAP 0
|
|
||||||
#define ZWP_VIRTUAL_KEYBOARD_V1_KEY 1
|
|
||||||
#define ZWP_VIRTUAL_KEYBOARD_V1_MODIFIERS 2
|
|
||||||
#define ZWP_VIRTUAL_KEYBOARD_V1_DESTROY 3
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_virtual_keyboard_v1
|
|
||||||
*/
|
|
||||||
#define ZWP_VIRTUAL_KEYBOARD_V1_KEYMAP_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_virtual_keyboard_v1
|
|
||||||
*/
|
|
||||||
#define ZWP_VIRTUAL_KEYBOARD_V1_KEY_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_virtual_keyboard_v1
|
|
||||||
*/
|
|
||||||
#define ZWP_VIRTUAL_KEYBOARD_V1_MODIFIERS_SINCE_VERSION 1
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_virtual_keyboard_v1
|
|
||||||
*/
|
|
||||||
#define ZWP_VIRTUAL_KEYBOARD_V1_DESTROY_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_virtual_keyboard_v1 */
|
|
||||||
static inline void
|
|
||||||
zwp_virtual_keyboard_v1_set_user_data(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1, void *user_data)
|
|
||||||
{
|
|
||||||
wl_proxy_set_user_data((struct wl_proxy *) zwp_virtual_keyboard_v1, user_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_virtual_keyboard_v1 */
|
|
||||||
static inline void *
|
|
||||||
zwp_virtual_keyboard_v1_get_user_data(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_user_data((struct wl_proxy *) zwp_virtual_keyboard_v1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t
|
|
||||||
zwp_virtual_keyboard_v1_get_version(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_version((struct wl_proxy *) zwp_virtual_keyboard_v1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_virtual_keyboard_v1
|
|
||||||
*
|
|
||||||
* Provide a file descriptor to the compositor which can be
|
|
||||||
* memory-mapped to provide a keyboard mapping description.
|
|
||||||
*
|
|
||||||
* Format carries a value from the keymap_format enumeration.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_virtual_keyboard_v1_keymap(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1, uint32_t format, int32_t fd, uint32_t size)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_virtual_keyboard_v1,
|
|
||||||
ZWP_VIRTUAL_KEYBOARD_V1_KEYMAP, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_virtual_keyboard_v1), 0, format, fd, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_virtual_keyboard_v1
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_virtual_keyboard_v1_key(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1, uint32_t time, uint32_t key, uint32_t state)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_virtual_keyboard_v1,
|
|
||||||
ZWP_VIRTUAL_KEYBOARD_V1_KEY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_virtual_keyboard_v1), 0, time, key, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_virtual_keyboard_v1
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_virtual_keyboard_v1_modifiers(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_virtual_keyboard_v1,
|
|
||||||
ZWP_VIRTUAL_KEYBOARD_V1_MODIFIERS, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_virtual_keyboard_v1), 0, mods_depressed, mods_latched, mods_locked, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_virtual_keyboard_v1
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
zwp_virtual_keyboard_v1_destroy(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1)
|
|
||||||
{
|
|
||||||
wl_proxy_marshal_flags((struct wl_proxy *) zwp_virtual_keyboard_v1,
|
|
||||||
ZWP_VIRTUAL_KEYBOARD_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_virtual_keyboard_v1), WL_MARSHAL_FLAG_DESTROY);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_ERROR_ENUM
|
|
||||||
#define ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_ERROR_ENUM
|
|
||||||
enum zwp_virtual_keyboard_manager_v1_error {
|
|
||||||
/**
|
|
||||||
* client not authorized to use the interface
|
|
||||||
*/
|
|
||||||
ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_ERROR_UNAUTHORIZED = 0,
|
|
||||||
};
|
|
||||||
#endif /* ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_ERROR_ENUM */
|
|
||||||
|
|
||||||
#define ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_CREATE_VIRTUAL_KEYBOARD 0
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_virtual_keyboard_manager_v1
|
|
||||||
*/
|
|
||||||
#define ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_CREATE_VIRTUAL_KEYBOARD_SINCE_VERSION 1
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_virtual_keyboard_manager_v1 */
|
|
||||||
static inline void
|
|
||||||
zwp_virtual_keyboard_manager_v1_set_user_data(struct zwp_virtual_keyboard_manager_v1 *zwp_virtual_keyboard_manager_v1, void *user_data)
|
|
||||||
{
|
|
||||||
wl_proxy_set_user_data((struct wl_proxy *) zwp_virtual_keyboard_manager_v1, user_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_virtual_keyboard_manager_v1 */
|
|
||||||
static inline void *
|
|
||||||
zwp_virtual_keyboard_manager_v1_get_user_data(struct zwp_virtual_keyboard_manager_v1 *zwp_virtual_keyboard_manager_v1)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_user_data((struct wl_proxy *) zwp_virtual_keyboard_manager_v1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t
|
|
||||||
zwp_virtual_keyboard_manager_v1_get_version(struct zwp_virtual_keyboard_manager_v1 *zwp_virtual_keyboard_manager_v1)
|
|
||||||
{
|
|
||||||
return wl_proxy_get_version((struct wl_proxy *) zwp_virtual_keyboard_manager_v1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ingroup iface_zwp_virtual_keyboard_manager_v1 */
|
|
||||||
static inline void
|
|
||||||
zwp_virtual_keyboard_manager_v1_destroy(struct zwp_virtual_keyboard_manager_v1 *zwp_virtual_keyboard_manager_v1)
|
|
||||||
{
|
|
||||||
wl_proxy_destroy((struct wl_proxy *) zwp_virtual_keyboard_manager_v1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ingroup iface_zwp_virtual_keyboard_manager_v1
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static inline struct zwp_virtual_keyboard_v1 *
|
|
||||||
zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(struct zwp_virtual_keyboard_manager_v1 *zwp_virtual_keyboard_manager_v1, struct wl_seat *seat)
|
|
||||||
{
|
|
||||||
struct wl_proxy *id;
|
|
||||||
|
|
||||||
id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_virtual_keyboard_manager_v1,
|
|
||||||
ZWP_VIRTUAL_KEYBOARD_MANAGER_V1_CREATE_VIRTUAL_KEYBOARD, &zwp_virtual_keyboard_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwp_virtual_keyboard_manager_v1), 0, seat, NULL);
|
|
||||||
|
|
||||||
return (struct zwp_virtual_keyboard_v1 *) id;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
/* Generated by wayland-scanner 1.23.1 */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "wayland-util.h"
|
|
||||||
|
|
||||||
#ifndef __has_attribute
|
|
||||||
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
|
|
||||||
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
|
|
||||||
#else
|
|
||||||
#define WL_PRIVATE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern const struct wl_interface wl_seat_interface;
|
|
||||||
extern const struct wl_interface zwp_virtual_keyboard_v1_interface;
|
|
||||||
|
|
||||||
static const struct wl_interface *virtual_keyboard_unstable_v1_types[] = {
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
&wl_seat_interface,
|
|
||||||
&zwp_virtual_keyboard_v1_interface,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_virtual_keyboard_v1_requests[] = {
|
|
||||||
{ "keymap", "uhu", virtual_keyboard_unstable_v1_types + 0 },
|
|
||||||
{ "key", "uuu", virtual_keyboard_unstable_v1_types + 0 },
|
|
||||||
{ "modifiers", "uuuu", virtual_keyboard_unstable_v1_types + 0 },
|
|
||||||
{ "destroy", "", virtual_keyboard_unstable_v1_types + 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
WL_PRIVATE const struct wl_interface zwp_virtual_keyboard_v1_interface = {
|
|
||||||
"zwp_virtual_keyboard_v1", 1,
|
|
||||||
4, zwp_virtual_keyboard_v1_requests,
|
|
||||||
0, NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct wl_message zwp_virtual_keyboard_manager_v1_requests[] = {
|
|
||||||
{ "create_virtual_keyboard", "on", virtual_keyboard_unstable_v1_types + 4 },
|
|
||||||
};
|
|
||||||
|
|
||||||
WL_PRIVATE const struct wl_interface zwp_virtual_keyboard_manager_v1_interface = {
|
|
||||||
"zwp_virtual_keyboard_manager_v1", 1,
|
|
||||||
1, zwp_virtual_keyboard_manager_v1_requests,
|
|
||||||
0, NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
573
wayland.c
573
wayland.c
@@ -1,573 +0,0 @@
|
|||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <wayland-client.h>
|
|
||||||
#include <xkbcommon/xkbcommon.h>
|
|
||||||
#include "input-method-unstable-v2-client-protocol.h"
|
|
||||||
#include "virtual-keyboard-unstable-v1-client-protocol.h"
|
|
||||||
|
|
||||||
static struct wl_display *dpy;
|
|
||||||
static struct wl_registry *reg;
|
|
||||||
static struct wl_seat *seat;
|
|
||||||
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_input_method_keyboard_grab_v2 *grab;
|
|
||||||
static struct zwp_virtual_keyboard_v1 *vk;
|
|
||||||
static struct xkb_context *xkb;
|
|
||||||
static struct xkb_keymap *keymap;
|
|
||||||
static struct xkb_state *xkbst;
|
|
||||||
static int active;
|
|
||||||
static int pending;
|
|
||||||
static int newactivation;
|
|
||||||
static int running;
|
|
||||||
static u32int imserial;
|
|
||||||
static u32int seatname;
|
|
||||||
static u32int immgrname;
|
|
||||||
static u32int vkmgrname;
|
|
||||||
static Channel *replyc;
|
|
||||||
static uvlong owner;
|
|
||||||
static Wlstate keys;
|
|
||||||
|
|
||||||
static uvlong
|
|
||||||
nowms(void)
|
|
||||||
{
|
|
||||||
struct timespec ts;
|
|
||||||
|
|
||||||
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
|
||||||
return 0;
|
|
||||||
return (uvlong)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sendkey(u32int ks, u32int mod, Keyres *res)
|
|
||||||
{
|
|
||||||
Keyreq kr;
|
|
||||||
|
|
||||||
memset(&kr, 0, sizeof kr);
|
|
||||||
kr.owner = owner;
|
|
||||||
kr.op = Keypress;
|
|
||||||
kr.ks = ks;
|
|
||||||
kr.mod = mod;
|
|
||||||
kr.want = 1;
|
|
||||||
kr.reply = replyc;
|
|
||||||
chansend(keyc, &kr);
|
|
||||||
chanrecv(replyc, res);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
releaseowner(void)
|
|
||||||
{
|
|
||||||
Keyreq kr;
|
|
||||||
Keyres res;
|
|
||||||
|
|
||||||
if(owner == 0)
|
|
||||||
return;
|
|
||||||
memset(&kr, 0, sizeof kr);
|
|
||||||
kr.owner = owner;
|
|
||||||
kr.op = Keyrelease;
|
|
||||||
kr.reply = replyc;
|
|
||||||
chansend(keyc, &kr);
|
|
||||||
chanrecv(replyc, &res);
|
|
||||||
owner = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static u32int
|
|
||||||
kget(xkb_keysym_t ks)
|
|
||||||
{
|
|
||||||
u32int c;
|
|
||||||
|
|
||||||
c = xkb_keysym_to_utf32(ks);
|
|
||||||
if(c >= ' ' && c != 0x7f)
|
|
||||||
return c;
|
|
||||||
if(ks >= 0xff00 && ks <= 0xffff)
|
|
||||||
return Kspec + (ks - 0xff00);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
static u32int
|
|
||||||
mget(void)
|
|
||||||
{
|
|
||||||
u32int m;
|
|
||||||
|
|
||||||
m = 0;
|
|
||||||
if(xkbst == nil)
|
|
||||||
return 0;
|
|
||||||
if(xkb_state_mod_name_is_active(xkbst,
|
|
||||||
XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE) > 0)
|
|
||||||
m |= Mshift;
|
|
||||||
if(xkb_state_mod_name_is_active(xkbst,
|
|
||||||
XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) > 0)
|
|
||||||
m |= Mctrl;
|
|
||||||
if(xkb_state_mod_name_is_active(xkbst,
|
|
||||||
XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE) > 0)
|
|
||||||
m |= Malt;
|
|
||||||
if(xkb_state_mod_name_is_active(xkbst,
|
|
||||||
XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE) > 0)
|
|
||||||
m |= Msuper;
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
putresult(Keyres *res)
|
|
||||||
{
|
|
||||||
char commit[Maxutf], preedit[Maxutf];
|
|
||||||
int plen;
|
|
||||||
|
|
||||||
if(im == nil)
|
|
||||||
return;
|
|
||||||
stoutf(&res->commit, commit, sizeof commit);
|
|
||||||
stoutf(&res->preedit, preedit, sizeof preedit);
|
|
||||||
if(commit[0] != '\0')
|
|
||||||
zwp_input_method_v2_commit_string(im, commit);
|
|
||||||
plen = strlen(preedit);
|
|
||||||
zwp_input_method_v2_set_preedit_string(im, preedit, plen, plen);
|
|
||||||
zwp_input_method_v2_commit(im, imserial);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
forwardkey(u32int time, u32int keycode, u32int state)
|
|
||||||
{
|
|
||||||
if(vk != nil)
|
|
||||||
zwp_virtual_keyboard_v1_key(vk, time, keycode, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
keypress(u32int time, u32int keycode)
|
|
||||||
{
|
|
||||||
xkb_keysym_t ks;
|
|
||||||
u32int k, mod;
|
|
||||||
Keyres res;
|
|
||||||
int repeatable;
|
|
||||||
|
|
||||||
if(xkbst == nil || keycode >= Wlkeymax){
|
|
||||||
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
||||||
wlstatepress(&keys, keycode, 0, 0, nowms());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ks = xkb_state_key_get_one_sym(xkbst, keycode + 8);
|
|
||||||
k = kget(ks);
|
|
||||||
if(k == 0){
|
|
||||||
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
||||||
wlstatepress(&keys, keycode, 0, 0, nowms());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mod = mget();
|
|
||||||
sendkey(k, mod, &res);
|
|
||||||
putresult(&res);
|
|
||||||
repeatable = keymap != nil &&
|
|
||||||
xkb_keymap_key_repeats(keymap, keycode + 8);
|
|
||||||
wlstatepress(&keys, keycode, res.eaten, repeatable, nowms());
|
|
||||||
if(!res.eaten)
|
|
||||||
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
keyrepeat(u32int time, u32int keycode)
|
|
||||||
{
|
|
||||||
xkb_keysym_t ks;
|
|
||||||
u32int k;
|
|
||||||
Keyres res;
|
|
||||||
|
|
||||||
if(xkbst == nil)
|
|
||||||
return;
|
|
||||||
ks = xkb_state_key_get_one_sym(xkbst, keycode + 8);
|
|
||||||
k = kget(ks);
|
|
||||||
if(k == 0)
|
|
||||||
return;
|
|
||||||
sendkey(k, mget(), &res);
|
|
||||||
putresult(&res);
|
|
||||||
/* The original eaten press still owns its eventual physical release. */
|
|
||||||
if(!res.eaten){
|
|
||||||
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
||||||
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_RELEASED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
kpress(u32int time, u32int keycode, u32int state)
|
|
||||||
{
|
|
||||||
if(state == WL_KEYBOARD_KEY_STATE_PRESSED){
|
|
||||||
keypress(time, keycode);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(wlstaterelease(&keys, keycode))
|
|
||||||
forwardkey(time, keycode, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
kg_keymap(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
|
|
||||||
u32int format, int32_t fd, u32int size)
|
|
||||||
{
|
|
||||||
char *s;
|
|
||||||
|
|
||||||
(void)data;
|
|
||||||
(void)g;
|
|
||||||
wlstatecancelrepeat(&keys);
|
|
||||||
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){
|
|
||||||
if(xkbst != nil){
|
|
||||||
xkb_state_unref(xkbst);
|
|
||||||
xkbst = nil;
|
|
||||||
}
|
|
||||||
if(keymap != nil)
|
|
||||||
xkb_keymap_unref(keymap);
|
|
||||||
keymap = xkb_keymap_new_from_string(xkb, s,
|
|
||||||
XKB_KEYMAP_FORMAT_TEXT_V1,
|
|
||||||
XKB_KEYMAP_COMPILE_NO_FLAGS);
|
|
||||||
if(keymap != nil)
|
|
||||||
xkbst = xkb_state_new(keymap);
|
|
||||||
munmap(s, size);
|
|
||||||
}
|
|
||||||
if(vk != nil)
|
|
||||||
zwp_virtual_keyboard_v1_keymap(vk, format, fd, size);
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
kg_key(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
|
|
||||||
u32int serial, u32int time, u32int key, u32int state)
|
|
||||||
{
|
|
||||||
(void)data;
|
|
||||||
(void)g;
|
|
||||||
(void)serial;
|
|
||||||
kpress(time, key, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
kg_mods(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
|
|
||||||
u32int serial, u32int dep, u32int lat, u32int lck, u32int group)
|
|
||||||
{
|
|
||||||
(void)data;
|
|
||||||
(void)g;
|
|
||||||
(void)serial;
|
|
||||||
if(xkbst != nil)
|
|
||||||
xkb_state_update_mask(xkbst, dep, lat, lck, 0, 0, group);
|
|
||||||
if(vk != nil)
|
|
||||||
zwp_virtual_keyboard_v1_modifiers(vk, dep, lat, lck, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
kg_repeat(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
|
|
||||||
int32_t rate, int32_t delay)
|
|
||||||
{
|
|
||||||
(void)data;
|
|
||||||
(void)g;
|
|
||||||
wlstaterepeatinfo(&keys, rate, delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct zwp_input_method_keyboard_grab_v2_listener
|
|
||||||
grab_listener = {
|
|
||||||
.keymap = kg_keymap,
|
|
||||||
.key = kg_key,
|
|
||||||
.modifiers = kg_mods,
|
|
||||||
.repeat_info = kg_repeat,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
im_activate(void *data, struct zwp_input_method_v2 *m)
|
|
||||||
{
|
|
||||||
(void)data;
|
|
||||||
(void)m;
|
|
||||||
pending = 1;
|
|
||||||
newactivation = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
im_deactivate(void *data, struct zwp_input_method_v2 *m)
|
|
||||||
{
|
|
||||||
(void)data;
|
|
||||||
(void)m;
|
|
||||||
pending = 0;
|
|
||||||
wlstateclear(&keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
im_surrounding(void *data, struct zwp_input_method_v2 *m,
|
|
||||||
const char *text, u32int cursor, u32int anchor)
|
|
||||||
{
|
|
||||||
(void)data;
|
|
||||||
(void)m;
|
|
||||||
(void)text;
|
|
||||||
(void)cursor;
|
|
||||||
(void)anchor;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
im_textchange(void *data, struct zwp_input_method_v2 *m, u32int cause)
|
|
||||||
{
|
|
||||||
(void)data;
|
|
||||||
(void)m;
|
|
||||||
(void)cause;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
im_content(void *data, struct zwp_input_method_v2 *m,
|
|
||||||
u32int hint, u32int purpose)
|
|
||||||
{
|
|
||||||
(void)data;
|
|
||||||
(void)m;
|
|
||||||
(void)hint;
|
|
||||||
(void)purpose;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
endactivation(void)
|
|
||||||
{
|
|
||||||
wlstateclear(&keys);
|
|
||||||
active = 0;
|
|
||||||
releaseowner();
|
|
||||||
if(grab != nil){
|
|
||||||
zwp_input_method_keyboard_grab_v2_release(grab);
|
|
||||||
grab = nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
beginactivation(void)
|
|
||||||
{
|
|
||||||
if(active)
|
|
||||||
endactivation();
|
|
||||||
active = 1;
|
|
||||||
owner = ownernew();
|
|
||||||
grab = zwp_input_method_v2_grab_keyboard(im);
|
|
||||||
if(grab != nil)
|
|
||||||
zwp_input_method_keyboard_grab_v2_add_listener(grab,
|
|
||||||
&grab_listener, nil);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
im_done(void *data, struct zwp_input_method_v2 *m)
|
|
||||||
{
|
|
||||||
(void)data;
|
|
||||||
(void)m;
|
|
||||||
imserial++;
|
|
||||||
if(newactivation && pending)
|
|
||||||
beginactivation();
|
|
||||||
else if(!pending && active)
|
|
||||||
endactivation();
|
|
||||||
newactivation = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
im_unavail(void *data, struct zwp_input_method_v2 *m)
|
|
||||||
{
|
|
||||||
(void)data;
|
|
||||||
(void)m;
|
|
||||||
wlstateclear(&keys);
|
|
||||||
running = 0;
|
|
||||||
fprint(2, "strans: wayland: input-method unavailable\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct zwp_input_method_v2_listener im_listener = {
|
|
||||||
.activate = im_activate,
|
|
||||||
.deactivate = im_deactivate,
|
|
||||||
.surrounding_text = im_surrounding,
|
|
||||||
.text_change_cause = im_textchange,
|
|
||||||
.content_type = im_content,
|
|
||||||
.done = im_done,
|
|
||||||
.unavailable = im_unavail,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
reg_global(void *data, struct wl_registry *r, u32int name,
|
|
||||||
const char *iface, u32int version)
|
|
||||||
{
|
|
||||||
(void)data;
|
|
||||||
if(strcmp(iface, wl_seat_interface.name) == 0 && seat == nil){
|
|
||||||
seatname = name;
|
|
||||||
seat = wl_registry_bind(r, name, &wl_seat_interface,
|
|
||||||
version < 5 ? version : 5);
|
|
||||||
}else if(strcmp(iface,
|
|
||||||
zwp_input_method_manager_v2_interface.name) == 0 && immgr == nil){
|
|
||||||
immgrname = name;
|
|
||||||
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 == nil){
|
|
||||||
vkmgrname = name;
|
|
||||||
vkmgr = wl_registry_bind(r, name,
|
|
||||||
&zwp_virtual_keyboard_manager_v1_interface, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
reg_remove(void *data, struct wl_registry *r, u32int name)
|
|
||||||
{
|
|
||||||
(void)data;
|
|
||||||
(void)r;
|
|
||||||
if(name == seatname || name == immgrname || name == vkmgrname)
|
|
||||||
running = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct wl_registry_listener reg_listener = {
|
|
||||||
.global = reg_global,
|
|
||||||
.global_remove = reg_remove,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int
|
|
||||||
dispatch(void)
|
|
||||||
{
|
|
||||||
struct pollfd pfd;
|
|
||||||
u32int key;
|
|
||||||
uvlong now;
|
|
||||||
int flush, timeout, n;
|
|
||||||
|
|
||||||
pfd.fd = wl_display_get_fd(dpy);
|
|
||||||
while(running){
|
|
||||||
if(wl_display_dispatch_pending(dpy) < 0)
|
|
||||||
return -1;
|
|
||||||
if(!running)
|
|
||||||
break;
|
|
||||||
now = nowms();
|
|
||||||
if(wlstaterepeat(&keys, now, &key))
|
|
||||||
keyrepeat((u32int)now, key);
|
|
||||||
while(wl_display_prepare_read(dpy) != 0)
|
|
||||||
if(wl_display_dispatch_pending(dpy) < 0)
|
|
||||||
return -1;
|
|
||||||
flush = wl_display_flush(dpy);
|
|
||||||
if(flush < 0 && errno != EAGAIN){
|
|
||||||
wl_display_cancel_read(dpy);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
pfd.events = POLLIN;
|
|
||||||
if(flush < 0)
|
|
||||||
pfd.events |= POLLOUT;
|
|
||||||
pfd.revents = 0;
|
|
||||||
timeout = wlstatetimeout(&keys, nowms());
|
|
||||||
n = poll(&pfd, 1, timeout);
|
|
||||||
if(n < 0){
|
|
||||||
wl_display_cancel_read(dpy);
|
|
||||||
if(errno == EINTR)
|
|
||||||
continue;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if(n == 0){
|
|
||||||
wl_display_cancel_read(dpy);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(pfd.revents & POLLIN){
|
|
||||||
if(wl_display_read_events(dpy) < 0)
|
|
||||||
return -1;
|
|
||||||
}else
|
|
||||||
wl_display_cancel_read(dpy);
|
|
||||||
if(pfd.revents & (POLLERR | POLLHUP | POLLNVAL))
|
|
||||||
return -1;
|
|
||||||
if(pfd.revents & POLLOUT)
|
|
||||||
wl_display_flush(dpy);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
resetconnection(void)
|
|
||||||
{
|
|
||||||
dpy = nil;
|
|
||||||
reg = nil;
|
|
||||||
seat = nil;
|
|
||||||
immgr = nil;
|
|
||||||
vkmgr = nil;
|
|
||||||
im = nil;
|
|
||||||
grab = nil;
|
|
||||||
vk = nil;
|
|
||||||
xkb = nil;
|
|
||||||
keymap = nil;
|
|
||||||
xkbst = nil;
|
|
||||||
active = 0;
|
|
||||||
pending = 0;
|
|
||||||
newactivation = 0;
|
|
||||||
running = 1;
|
|
||||||
imserial = 0;
|
|
||||||
seatname = 0;
|
|
||||||
immgrname = 0;
|
|
||||||
vkmgrname = 0;
|
|
||||||
owner = 0;
|
|
||||||
wlstateinit(&keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
cleanup(void)
|
|
||||||
{
|
|
||||||
running = 0;
|
|
||||||
wlstateclear(&keys);
|
|
||||||
releaseowner();
|
|
||||||
active = 0;
|
|
||||||
if(xkbst != nil)
|
|
||||||
xkb_state_unref(xkbst);
|
|
||||||
if(keymap != nil)
|
|
||||||
xkb_keymap_unref(keymap);
|
|
||||||
if(xkb != nil)
|
|
||||||
xkb_context_unref(xkb);
|
|
||||||
if(grab != nil)
|
|
||||||
wl_proxy_destroy((struct wl_proxy*)grab);
|
|
||||||
if(vk != nil)
|
|
||||||
wl_proxy_destroy((struct wl_proxy*)vk);
|
|
||||||
if(im != nil)
|
|
||||||
wl_proxy_destroy((struct wl_proxy*)im);
|
|
||||||
if(vkmgr != nil)
|
|
||||||
wl_proxy_destroy((struct wl_proxy*)vkmgr);
|
|
||||||
if(immgr != nil)
|
|
||||||
wl_proxy_destroy((struct wl_proxy*)immgr);
|
|
||||||
if(seat != nil)
|
|
||||||
wl_proxy_destroy((struct wl_proxy*)seat);
|
|
||||||
if(reg != nil)
|
|
||||||
wl_registry_destroy(reg);
|
|
||||||
if(dpy != nil){
|
|
||||||
wl_display_disconnect(dpy);
|
|
||||||
dpy = nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
connectonce(void)
|
|
||||||
{
|
|
||||||
resetconnection();
|
|
||||||
dpy = wl_display_connect(nil);
|
|
||||||
if(dpy == nil)
|
|
||||||
return;
|
|
||||||
xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
|
||||||
if(xkb == nil)
|
|
||||||
return;
|
|
||||||
reg = wl_display_get_registry(dpy);
|
|
||||||
if(reg == nil)
|
|
||||||
return;
|
|
||||||
wl_registry_add_listener(reg, ®_listener, nil);
|
|
||||||
if(wl_display_roundtrip(dpy) < 0)
|
|
||||||
return;
|
|
||||||
if(!running)
|
|
||||||
return;
|
|
||||||
if(seat == nil || immgr == nil || vkmgr == nil){
|
|
||||||
fprint(2,
|
|
||||||
"strans: wayland: compositor missing input-method-v2\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
im = zwp_input_method_manager_v2_get_input_method(immgr, seat);
|
|
||||||
vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(vkmgr, seat);
|
|
||||||
if(im == nil || vk == nil)
|
|
||||||
return;
|
|
||||||
zwp_input_method_v2_add_listener(im, &im_listener, nil);
|
|
||||||
dispatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
waylandthread(void *_)
|
|
||||||
{
|
|
||||||
(void)_;
|
|
||||||
threadsetname("wayland");
|
|
||||||
replyc = chancreate(sizeof(Keyres), 0);
|
|
||||||
for(;;){
|
|
||||||
connectonce();
|
|
||||||
cleanup();
|
|
||||||
poll(nil, 0, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
107
wayland_state.c
107
wayland_state.c
@@ -1,107 +0,0 @@
|
|||||||
#include "dat.h"
|
|
||||||
#include "fn.h"
|
|
||||||
|
|
||||||
#include <limits.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
enum {
|
|
||||||
Downforward = 1,
|
|
||||||
Downeaten = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
void
|
|
||||||
wlstateinit(Wlstate *s)
|
|
||||||
{
|
|
||||||
memset(s, 0, sizeof *s);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
wlstatecancelrepeat(Wlstate *s)
|
|
||||||
{
|
|
||||||
s->repeating = 0;
|
|
||||||
s->repeatkey = 0;
|
|
||||||
s->repeatat = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
wlstateclear(Wlstate *s)
|
|
||||||
{
|
|
||||||
memset(s->down, 0, sizeof s->down);
|
|
||||||
wlstatecancelrepeat(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
wlstaterepeatinfo(Wlstate *s, int rate, int delay)
|
|
||||||
{
|
|
||||||
/* Settings changes end the old schedule; a later press starts a new one. */
|
|
||||||
wlstatecancelrepeat(s);
|
|
||||||
s->rate = rate > 0 ? rate : 0;
|
|
||||||
s->delay = delay > 0 ? delay : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
wlstatepress(Wlstate *s, u32int key, int eaten, int repeatable, uvlong now)
|
|
||||||
{
|
|
||||||
uvlong delay;
|
|
||||||
|
|
||||||
if(key >= Wlkeymax)
|
|
||||||
return 0;
|
|
||||||
s->down[key] = eaten ? Downeaten : Downforward;
|
|
||||||
wlstatecancelrepeat(s);
|
|
||||||
if(eaten && repeatable && s->rate > 0){
|
|
||||||
delay = s->delay;
|
|
||||||
if(~(uvlong)0 - now < delay)
|
|
||||||
s->repeatat = ~(uvlong)0;
|
|
||||||
else
|
|
||||||
s->repeatat = now + delay;
|
|
||||||
s->repeatkey = key;
|
|
||||||
s->repeating = 1;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
wlstaterelease(Wlstate *s, u32int key)
|
|
||||||
{
|
|
||||||
int forward;
|
|
||||||
|
|
||||||
if(key >= Wlkeymax)
|
|
||||||
return 1;
|
|
||||||
forward = s->down[key] != Downeaten;
|
|
||||||
s->down[key] = 0;
|
|
||||||
if(s->repeating && s->repeatkey == key)
|
|
||||||
wlstatecancelrepeat(s);
|
|
||||||
return forward;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
wlstatetimeout(Wlstate *s, uvlong now)
|
|
||||||
{
|
|
||||||
uvlong d;
|
|
||||||
|
|
||||||
if(!s->repeating)
|
|
||||||
return -1;
|
|
||||||
if(now >= s->repeatat)
|
|
||||||
return 0;
|
|
||||||
d = s->repeatat - now;
|
|
||||||
return d > INT_MAX ? INT_MAX : (int)d;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
wlstaterepeat(Wlstate *s, uvlong now, u32int *key)
|
|
||||||
{
|
|
||||||
uvlong step;
|
|
||||||
|
|
||||||
if(!s->repeating || now < s->repeatat)
|
|
||||||
return 0;
|
|
||||||
if(key != 0)
|
|
||||||
*key = s->repeatkey;
|
|
||||||
step = 1000 / s->rate;
|
|
||||||
if(step == 0)
|
|
||||||
step = 1;
|
|
||||||
if(~(uvlong)0 - now < step)
|
|
||||||
s->repeatat = ~(uvlong)0;
|
|
||||||
else
|
|
||||||
s->repeatat = now + step;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
11
win.c
11
win.c
@@ -6,7 +6,8 @@ enum {
|
|||||||
Asciitofull = 0xFEE0,
|
Asciitofull = 0xFEE0,
|
||||||
};
|
};
|
||||||
|
|
||||||
extern char *fontdir;
|
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;
|
||||||
@@ -140,7 +141,7 @@ wininit(void)
|
|||||||
mask = XCB_CW_BACK_PIXMAP;
|
mask = XCB_CW_BACK_PIXMAP;
|
||||||
xcb_change_window_attributes(conn, win, mask, &pix);
|
xcb_change_window_attributes(conn, win, mask, &pix);
|
||||||
img = emalloc(Imgw * Imgh * sizeof(img[0]));
|
img = emalloc(Imgw * Imgh * sizeof(img[0]));
|
||||||
if(!fontinit(fontdir)){
|
if(!fontinit(fontfiles, nfontfiles)){
|
||||||
fprint(2, "strans: popup disabled: no usable fonts\n");
|
fprint(2, "strans: popup disabled: no usable fonts\n");
|
||||||
wincleanup();
|
wincleanup();
|
||||||
return 0;
|
return 0;
|
||||||
@@ -170,7 +171,7 @@ fill(u32int *buf, int n, u32int color)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
drawkouho(Drawcmd *dc, int first, int n, int w, int h)
|
drawkouho(Drawcmd *dc, int n, int w, int h)
|
||||||
{
|
{
|
||||||
int npre, sely, y, i;
|
int npre, sely, y, i;
|
||||||
Str *s;
|
Str *s;
|
||||||
@@ -184,7 +185,7 @@ drawkouho(Drawcmd *dc, int first, int n, int w, int h)
|
|||||||
fill(img + sely * w, Fontsz * w, Colsel);
|
fill(img + sely * w, Fontsz * w, Colsel);
|
||||||
}
|
}
|
||||||
for(i = 0, y = npre * Fontsz; i < n; i++, y += Fontsz){
|
for(i = 0, y = npre * Fontsz; i < n; i++, y += Fontsz){
|
||||||
s = &dc->kouho[first+i];
|
s = &dc->kouho[i];
|
||||||
putfont(img, w, h, 0, y, '1' + i + Asciitofull);
|
putfont(img, w, h, 0, y, '1' + i + Asciitofull);
|
||||||
drawstr(img, 2*Fontsz, y, s->r, s->n, w, h);
|
drawstr(img, 2*Fontsz, y, s->r, s->n, w, h);
|
||||||
}
|
}
|
||||||
@@ -243,7 +244,7 @@ winshow(Drawcmd *dc)
|
|||||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
|
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
|
||||||
vals);
|
vals);
|
||||||
xcb_map_window(conn, win);
|
xcb_map_window(conn, win);
|
||||||
drawkouho(dc, 0, n, w, h);
|
drawkouho(dc, n, w, h);
|
||||||
putimage(w, h);
|
putimage(w, h);
|
||||||
return xcb_flush(conn) > 0;
|
return xcb_flush(conn) > 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user