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
|
||||
/tests/*.o
|
||||
/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/strans-xim
|
||||
/xim/xim_test
|
||||
|
||||
@@ -2,6 +2,7 @@ FROM archlinux:base@sha256:b0deabeb3d283da2c7f7dbf0eea051b7b2cd0554e0b737cc457fd
|
||||
|
||||
RUN pacman -Syu --noconfirm --needed \
|
||||
diffutils \
|
||||
freetype2 \
|
||||
gcc \
|
||||
gtk3 \
|
||||
libxcb \
|
||||
@@ -10,13 +11,15 @@ RUN pacman -Syu --noconfirm --needed \
|
||||
pkgconf \
|
||||
plan9port \
|
||||
python \
|
||||
wayland \
|
||||
ttf-dejavu \
|
||||
ttf-jigmo \
|
||||
which \
|
||||
xcb-imdkit \
|
||||
dbus \
|
||||
&& pacman -Scc --noconfirm
|
||||
|
||||
RUN printf "egrep='grep -E'\n" > /usr/lib/plan9/config
|
||||
RUN dbus-uuidgen --ensure=/etc/machine-id
|
||||
|
||||
ENV PLAN9=/usr/lib/plan9
|
||||
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
|
||||
`map/kanji.dict` grants the option to use GPL version 2 or any later
|
||||
version.
|
||||
- Font license texts are stored in `font/` and associated with exact files
|
||||
by `font/PROVENANCE`.
|
||||
- `stb_truetype.h` includes its complete dual MIT/Public Domain terms.
|
||||
- Generated Wayland files include their MIT-style notices in each file.
|
||||
- License texts and provenance for the historically bundled fonts are retained
|
||||
in `font/`; no font binary remains in the source tree.
|
||||
|
||||
No repository-wide license has been declared for original strans source.
|
||||
|
||||
19
Makefile
19
Makefile
@@ -1,27 +1,26 @@
|
||||
CC = 9c
|
||||
LD = 9l
|
||||
PKG_CFLAGS = $(shell pkg-config --cflags dbus-1 wayland-client xkbcommon)
|
||||
PKG_LIBS = $(shell pkg-config --libs dbus-1 wayland-client xkbcommon)
|
||||
PKG_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
|
||||
PKG_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
|
||||
FT_CFLAGS = $(shell pkg-config --cflags freetype2)
|
||||
FT_LIBS = $(shell pkg-config --libs freetype2)
|
||||
CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS)
|
||||
PROG = strans
|
||||
DOCKER_IMAGE = strans-build
|
||||
DOCKER_RUN = docker run --rm --user "$$(id -u):$$(id -g)" \
|
||||
-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)
|
||||
|
||||
all: $(PROG) xim gtk
|
||||
|
||||
$(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
|
||||
# Avoid Arch's GLIBC_2.43 sqrtf optimization for older glibc runtimes.
|
||||
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
|
||||
font.o: CFLAGS += $(FT_CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(PROG)
|
||||
@@ -57,7 +56,7 @@ docker: docker-image
|
||||
check: verify-map test
|
||||
$(MAKE) -C xim check
|
||||
|
||||
test:
|
||||
test: $(PROG)
|
||||
$(MAKE) -C tests check TESTARGS="$(TESTARGS)"
|
||||
|
||||
verify-map:
|
||||
|
||||
76
README.md
76
README.md
@@ -1,10 +1,9 @@
|
||||
# strans
|
||||
|
||||
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,
|
||||
IBus, and Wayland input-method-v2 frontends. The existing Vietnamese Telex
|
||||
mode remains available for compatibility, but is not an actively developed
|
||||
language mode.
|
||||
and emoji. It provides one engine shared by IBus, XIM, and GTK 3 frontends.
|
||||
The existing Vietnamese Telex mode remains available for compatibility, but
|
||||
is not an actively developed language mode.
|
||||
|
||||
## Input behavior
|
||||
|
||||
@@ -75,7 +74,7 @@ build steps.
|
||||
The resulting runtime artifacts are:
|
||||
|
||||
```text
|
||||
strans daemon, IBus and Wayland frontends
|
||||
strans daemon and IBus frontend
|
||||
xim/strans-xim XIM frontend
|
||||
gtk/im-strans.so GTK 3 frontend
|
||||
```
|
||||
@@ -99,12 +98,41 @@ tab-separated data consumable by the runtime dictionary reader.
|
||||
|
||||
## 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
|
||||
./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
|
||||
`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
|
||||
@@ -139,10 +167,9 @@ For IBus clients such as kitty:
|
||||
GLFW_IM_MODULE=ibus kitty
|
||||
```
|
||||
|
||||
Wayland needs no environment variable. Start `strans` after a compositor
|
||||
that supports input-method-v2. Neither ibus-daemon nor fcitx is required.
|
||||
Starting without `DISPLAY` is supported: the optional X popup is disabled,
|
||||
while input processing and non-X frontends continue to run.
|
||||
strans provides its own IBus endpoint; neither ibus-daemon nor fcitx is
|
||||
required. Starting without `DISPLAY` is supported: input processing and IBus
|
||||
continue to run while the optional X popup is disabled.
|
||||
|
||||
## Ownership and rendering constraints
|
||||
|
||||
@@ -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;
|
||||
stale events cannot clear a newer owner's preedit.
|
||||
|
||||
The popup is an optional X window, including when the active frontend is
|
||||
Wayland. It uses deterministic font fallback and fixed-width rune cells;
|
||||
U+FE0E and U+FE0F variation selectors use zero cells. It does not perform
|
||||
OpenType shaping, color-emoji rendering, or general grapheme clustering, and
|
||||
there is no native Wayland popup renderer. Multi-rune emoji are committed
|
||||
intact but are drawn as their constituent rune cells under this boundary. A
|
||||
supplied caret rectangle is used when available, with the pointer as fallback.
|
||||
The X renderer accepts only a 24-depth, 32-bpp TrueColor root format and
|
||||
disables itself cleanly on other visuals.
|
||||
The popup is an optional X window. It uses the explicit font-file order and
|
||||
fixed-width rune cells; U+FE0E and U+FE0F variation selectors use zero cells.
|
||||
It does not perform OpenType shaping, color-emoji rendering, or general
|
||||
grapheme clustering. Multi-rune emoji are committed intact but are drawn as
|
||||
their constituent rune cells under this boundary. A supplied caret rectangle
|
||||
is used when available, with the pointer as fallback. The X renderer accepts
|
||||
only a 24-depth, 32-bpp TrueColor root format and disables itself cleanly on
|
||||
other visuals.
|
||||
|
||||
## Data, third-party code, and licensing
|
||||
|
||||
Data and third-party provenance is recorded in
|
||||
[`docs/PROVENANCE.md`](docs/PROVENANCE.md), with font-specific details in
|
||||
[`font/PROVENANCE`](font/PROVENANCE). 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
|
||||
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
|
||||
@@ -174,6 +202,6 @@ the BSD 3-Clause license in
|
||||
[`LICENSES/BSD-3-Clause-libhangul-hanja.txt`](LICENSES/BSD-3-Clause-libhangul-hanja.txt).
|
||||
|
||||
The repository does not currently declare a license for the strans project
|
||||
source as a whole. The licenses of bundled data, fonts, generated protocol
|
||||
code, and third-party components do not by themselves license the original
|
||||
strans source.
|
||||
source as a whole. The licenses of bundled data, historical font assets, and
|
||||
third-party components do not by themselves license the original strans
|
||||
source.
|
||||
|
||||
91
bench.sh
91
bench.sh
@@ -2,34 +2,101 @@
|
||||
|
||||
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=
|
||||
perf_pid=
|
||||
|
||||
cleanup()
|
||||
{
|
||||
status=$?
|
||||
trap - 0 1 2 15
|
||||
if test -n "$perf_pid"; then
|
||||
kill -INT "$perf_pid" 2>/dev/null || true
|
||||
wait "$perf_pid" 2>/dev/null || true
|
||||
kill -INT "$perf_pid" 2>/dev/null || :
|
||||
wait "$perf_pid" 2>/dev/null || :
|
||||
perf_pid=
|
||||
fi
|
||||
if test -n "$strans_pid"; then
|
||||
kill "$strans_pid" 2>/dev/null || true
|
||||
wait "$strans_pid" 2>/dev/null || true
|
||||
kill "$strans_pid" 2>/dev/null || :
|
||||
wait "$strans_pid" 2>/dev/null || :
|
||||
strans_pid=
|
||||
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=$!
|
||||
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
|
||||
echo "cache warmed up"
|
||||
echo "renderer warmed up"
|
||||
|
||||
perf record -g -o bench/perf.data -p "$strans_pid" &
|
||||
perf_pid=$!
|
||||
@@ -39,5 +106,5 @@ kill -0 "$perf_pid"
|
||||
./bench/bench bench/bench.keys 100
|
||||
|
||||
kill -INT "$perf_pid"
|
||||
wait "$perf_pid" 2>/dev/null || true
|
||||
wait "$perf_pid" 2>/dev/null || :
|
||||
perf_pid=
|
||||
|
||||
17
dat.h
17
dat.h
@@ -18,7 +18,7 @@ enum
|
||||
LangVI = 0x16,
|
||||
|
||||
Fontsz = 32,
|
||||
Nglyphs = 0x20000,
|
||||
Maxfonts = 4,
|
||||
Maxrunes = 64,
|
||||
Maxutf = Maxrunes * UTFmax + 1,
|
||||
};
|
||||
@@ -28,7 +28,6 @@ enum
|
||||
Maxclients = 64,
|
||||
Maxkouho = 32,
|
||||
Maxdisp = 9,
|
||||
Wlkeymax = 0x300,
|
||||
Imgw = (Maxrunes + 3) * Fontsz,
|
||||
Imgh = (Maxdisp + 1) * Fontsz,
|
||||
|
||||
@@ -44,17 +43,6 @@ struct Str
|
||||
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;
|
||||
struct Emit
|
||||
{
|
||||
@@ -166,12 +154,11 @@ struct Keyres
|
||||
|
||||
struct Keyreq
|
||||
{
|
||||
uvlong owner;
|
||||
void *owner; /* stable until the context's release is acknowledged */
|
||||
int op;
|
||||
u32int ks;
|
||||
u32int mod;
|
||||
Caret caret;
|
||||
int want; /* nonzero: include preedit in reply */
|
||||
Channel *reply;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ identifiers are:
|
||||
- input SHA-256: `dd44dcc856cf542b1022d0f39c2e9b9f8805fdcc5923be80f04849ed97ce0996`
|
||||
|
||||
`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
|
||||
not part of this data. `map/mkhanja` groups those rows by reading for the
|
||||
runtime dictionary without changing candidate order, retaining the first 32
|
||||
@@ -46,45 +46,31 @@ new SKK revision.
|
||||
|
||||
## Fonts
|
||||
|
||||
The popup bundles Noto Sans, Japanese-region Noto Sans Mono CJK, and Noto
|
||||
Emoji. Exact checksums, versions or source revisions, download locations
|
||||
where known, fallback order, and license-file names are recorded in
|
||||
`font/PROVENANCE`. The corresponding OFL 1.1 and Apache 2.0 texts are kept
|
||||
beside the fonts.
|
||||
No font binaries are bundled. The daemon receives one to four font file paths
|
||||
on its command line and tries them in that order; it does not discover fonts by
|
||||
family name or directory contents. `font/PROVENANCE` retains the exact
|
||||
checksums, source revisions, and license records for the three Noto faces that
|
||||
were formerly stored in the repository.
|
||||
|
||||
## 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
|
||||
August 28, 2021 in its header. Its exact repository checksum is:
|
||||
- `ttf-dejavu` `2.37+18+g9b5d1b2f-8`, providing
|
||||
`/usr/share/fonts/TTF/DejaVuSans.ttf` (SHA-256
|
||||
`6038a160b491e121c1f12c7bccb4a9c8730296e3adc1086a059404ed84b7451c`);
|
||||
- `ttf-jigmo` `20250912-1`, providing
|
||||
`/usr/share/fonts/TTF/Jigmo.ttf` (SHA-256
|
||||
`c8f295b9bd8f9f117a76b3a454aaaa1bb5b4babc18e254978c6deb88464e40cf`)
|
||||
and `/usr/share/fonts/TTF/Jigmo2.ttf` (SHA-256
|
||||
`5da3582efe77e22073b86b3b86b556d7111148a76b957cfb53318a91da2efff0`).
|
||||
|
||||
```text
|
||||
ecd30b05e0dd4fea3a13c26810dd9e1992dc379049482c393d5a19e6b5090aab
|
||||
```
|
||||
|
||||
The file was present in the repository's first commit,
|
||||
`dcd1147638f908eca7e4b7616815e215022cc99d`. No upstream commit or download
|
||||
URL was recorded. Its complete dual MIT/Public Domain terms are retained at
|
||||
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.
|
||||
Pacman validates the package checksums and repository signatures. DejaVu Sans
|
||||
supplies ASCII and the monochrome U+1F600 outline in the tests; Jigmo supplies
|
||||
Hangul and BMP CJK, and Jigmo2 supplies U+2000B. Their installed license files
|
||||
are `/usr/share/licenses/ttf-dejavu/LICENSE` and
|
||||
`/usr/share/licenses/ttf-jigmo/LICENSE.txt`; the packages identify their terms
|
||||
as the DejaVu custom license and CC0 1.0, respectively. These packages are
|
||||
test fixtures, not names or paths hard-coded into the daemon.
|
||||
|
||||
## Git submodules
|
||||
|
||||
|
||||
15
fn.h
15
fn.h
@@ -36,24 +36,13 @@ Emit transko(Im*, Rune);
|
||||
Emit transvi(Im*, Rune);
|
||||
void backko(Im*);
|
||||
void backvi(Im*);
|
||||
void dictsend(Im*, Str*);
|
||||
uvlong ownernew(void);
|
||||
|
||||
int srvreadkey(int, Keyreq*);
|
||||
void srvinit(void);
|
||||
void srvthread(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* erealloc(void*, ulong);
|
||||
|
||||
int fontinit(char*);
|
||||
int fontinit(char**, int);
|
||||
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 <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include "fn.h"
|
||||
|
||||
typedef struct Glyph Glyph;
|
||||
typedef struct Font Font;
|
||||
struct Glyph
|
||||
{
|
||||
uchar *bmp;
|
||||
int w, h, ox, oy, base;
|
||||
};
|
||||
|
||||
struct Font
|
||||
{
|
||||
stbtt_fontinfo info;
|
||||
uchar *data;
|
||||
float scale;
|
||||
FT_Face face;
|
||||
int base;
|
||||
};
|
||||
|
||||
enum { Maxfonts = 4 };
|
||||
|
||||
static FT_Library lib;
|
||||
static Font fonts[Maxfonts];
|
||||
static int nfonts;
|
||||
static Glyph cache[Nglyphs];
|
||||
static u32int blendtab[2][256];
|
||||
|
||||
static u32int
|
||||
@@ -41,117 +30,139 @@ blend(u32int bg, u32int fg, int a)
|
||||
static int
|
||||
loadfont(char *path)
|
||||
{
|
||||
FT_Face face;
|
||||
Font *f;
|
||||
int ascent, descent, gap, fd;
|
||||
long sz, n;
|
||||
vlong height, pixels;
|
||||
|
||||
fd = open(path, OREAD);
|
||||
if(fd < 0){
|
||||
fprint(2, "strans: popup: can't open font: %s\n", path);
|
||||
if(nfonts >= nelem(fonts))
|
||||
return 0;
|
||||
if(FT_New_Face(lib, path, 0, &face) != 0){
|
||||
fprint(2, "strans: popup: can't load font: %s\n", path);
|
||||
return 0;
|
||||
}
|
||||
sz = seek(fd, 0, 2);
|
||||
seek(fd, 0, 0);
|
||||
if(sz <= 0){
|
||||
fprint(2, "strans: popup: empty font: %s\n", path);
|
||||
close(fd);
|
||||
height = (vlong)face->ascender - face->descender;
|
||||
if(height <= 0 || face->units_per_EM == 0){
|
||||
fprint(2, "strans: popup: invalid font metrics: %s\n", path);
|
||||
FT_Done_Face(face);
|
||||
return 0;
|
||||
}
|
||||
pixels = (vlong)Fontsz * face->units_per_EM / height;
|
||||
if(pixels < 1)
|
||||
pixels = 1;
|
||||
if(FT_Set_Pixel_Sizes(face, 0, pixels) != 0){
|
||||
fprint(2, "strans: popup: can't size font: %s\n", path);
|
||||
FT_Done_Face(face);
|
||||
return 0;
|
||||
}
|
||||
f = &fonts[nfonts];
|
||||
f->data = emalloc(sz);
|
||||
n = readn(fd, f->data, sz);
|
||||
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);
|
||||
f->face = face;
|
||||
f->base = (vlong)Fontsz * face->ascender / height;
|
||||
nfonts++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
fontinit(char *dir)
|
||||
static void
|
||||
clearfonts(void)
|
||||
{
|
||||
static char *names[] = {
|
||||
"NotoSans-Regular.ttf",
|
||||
"NotoSansMonoCJKjp-Regular.otf",
|
||||
"NotoEmoji-Regular.ttf",
|
||||
};
|
||||
int i, a;
|
||||
char path[256];
|
||||
int i;
|
||||
|
||||
for(i = 0; i < nfonts; i++){
|
||||
FT_Done_Face(fonts[i].face);
|
||||
fonts[i].face = nil;
|
||||
}
|
||||
nfonts = 0;
|
||||
memset(cache, 0, sizeof cache);
|
||||
for(i = 0; i < nelem(names); i++){
|
||||
if(strlen(dir) + 1 + strlen(names[i]) + 1 > sizeof path){
|
||||
fprint(2, "strans: popup: font path is too long\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
int
|
||||
fontinit(char **path, int npath)
|
||||
{
|
||||
int i, a;
|
||||
|
||||
if(path == nil || npath < 1 || npath > nelem(fonts)){
|
||||
fprint(2, "strans: popup: need 1-%d font files\n", nelem(fonts));
|
||||
clearfonts();
|
||||
return 0;
|
||||
}
|
||||
if(lib == nil && FT_Init_FreeType(&lib) != 0){
|
||||
fprint(2, "strans: popup: can't initialize FreeType\n");
|
||||
return 0;
|
||||
}
|
||||
clearfonts();
|
||||
for(i = 0; i < npath; i++){
|
||||
if(path[i] == nil || !loadfont(path[i])){
|
||||
clearfonts();
|
||||
return 0;
|
||||
}
|
||||
snprint(path, sizeof path, "%s/%s", dir, names[i]);
|
||||
loadfont(path);
|
||||
}
|
||||
for(a = 0; a < 256; a++){
|
||||
blendtab[0][a] = blend(Colbg, 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
|
||||
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
|
||||
{
|
||||
Glyph *g;
|
||||
int i, j, a, sel, f;
|
||||
int y0, j0, j1, x0, i0, i1;
|
||||
u32int *p;
|
||||
FT_UInt g;
|
||||
int f;
|
||||
|
||||
if(r >= Nglyphs)
|
||||
if(buf == nil || w <= 0 || h <= 0)
|
||||
return;
|
||||
g = &cache[r];
|
||||
if(g->bmp == nil){
|
||||
for(f = 0; f < nfonts; f++){
|
||||
if(stbtt_FindGlyphIndex(&fonts[f].info, r) == 0)
|
||||
continue;
|
||||
g->bmp = stbtt_GetCodepointBitmap(&fonts[f].info,
|
||||
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)
|
||||
for(f = 0; f < nfonts; f++){
|
||||
g = FT_Get_Char_Index(fonts[f].face, r);
|
||||
if(g == 0 || FT_Load_Glyph(fonts[f].face, g, FT_LOAD_DEFAULT) != 0 ||
|
||||
FT_Render_Glyph(fonts[f].face->glyph, FT_RENDER_MODE_NORMAL) != 0)
|
||||
continue;
|
||||
if(drawglyph(&fonts[f], buf, w, h, px, py))
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
2. NotoSansMonoCJKjp-Regular.otf
|
||||
@@ -13,25 +15,35 @@ NotoSans-Regular.ttf
|
||||
SHA-256: 9cb49a54e520423033f9727be2e53e4805a60656deb09c219740d8e5f3e033ac
|
||||
Internal family/version: Noto Sans Regular, version 2.005
|
||||
License: SIL Open Font License 1.1 (OFL-NotoSans.txt)
|
||||
Source revision: 7697007fcb3563290d73f41f56a70d5d559d828c
|
||||
Source: https://raw.githubusercontent.com/notofonts/noto-fonts/7697007fcb3563290d73f41f56a70d5d559d828c/hinted/ttf/NotoSans/NotoSans-Regular.ttf
|
||||
|
||||
This file entered the strans repository in commit 874b941. That commit did
|
||||
not record its download URL, and the current upstream build is not
|
||||
byte-identical. The checksum above identifies the bundled file; no claim is
|
||||
made that the current upstream URL regenerates it.
|
||||
This file entered the strans repository in commit 874b941. Its source URL was
|
||||
not recorded then; the immutable revision above was recovered later and is
|
||||
byte-identical to the historical file.
|
||||
|
||||
NotoSansMonoCJKjp-Regular.otf
|
||||
--------------------------------
|
||||
|
||||
SHA-256: 4d01725be822d144cf9a56ade981e6fb920cd7a610b8fc24cc601a920beea5b9
|
||||
Release: Noto Sans CJK 2.004, tag Sans2.004
|
||||
Source: 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 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
|
||||
---------------------
|
||||
|
||||
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
|
||||
License: Apache License 2.0 (Apache-2.0-NotoEmoji.txt)
|
||||
License source: https://raw.githubusercontent.com/googlei18n/noto-emoji/2f1ffdd6fbbd05d6f382138a3d3adcd89c5ce800/LICENSE
|
||||
Font license: SIL Open Font License 1.1
|
||||
Font license source: https://raw.githubusercontent.com/googlei18n/noto-emoji/2f1ffdd6fbbd05d6f382138a3d3adcd89c5ce800/fonts/LICENSE
|
||||
Font license SHA-256: 6a73f9541c2de74158c0e7cf6b0a58ef774f5a780bf191f2d7ec9cc53efe2bf2
|
||||
|
||||
The font-specific license is byte-identical to `OFL-NotoSansCJK.txt`. The
|
||||
previously recorded `Apache-2.0-NotoEmoji.txt` is retained as the license of
|
||||
the upstream project at that revision; it is not the license declared by this
|
||||
font binary.
|
||||
|
||||
188
ibus.c
188
ibus.c
@@ -12,30 +12,23 @@
|
||||
|
||||
enum
|
||||
{
|
||||
Maxwatches = 32,
|
||||
Maxconns = 16,
|
||||
Maxwatches = 2*Maxconns + 1,
|
||||
Maxcontexts = 64,
|
||||
Relmask = 1<<30,
|
||||
Ibussupermask = 1<<26,
|
||||
};
|
||||
|
||||
typedef struct Watch Watch;
|
||||
struct Watch
|
||||
{
|
||||
DBusWatch *w;
|
||||
};
|
||||
|
||||
typedef struct Ictx Ictx;
|
||||
struct Ictx
|
||||
{
|
||||
DBusConnection *conn;
|
||||
char path[64];
|
||||
uvlong owner;
|
||||
int focused;
|
||||
Caret caret;
|
||||
};
|
||||
|
||||
static Watch watches[Maxwatches];
|
||||
static DBusWatch *watches[Maxwatches];
|
||||
static int nwatches;
|
||||
static DBusConnection *conns[Maxconns];
|
||||
static int nconns;
|
||||
@@ -62,24 +55,37 @@ unlinkaddr(void)
|
||||
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
|
||||
machineid(char *buf, int sz)
|
||||
{
|
||||
int fd, n;
|
||||
char *path[] = {
|
||||
"/etc/machine-id",
|
||||
"/var/lib/dbus/machine-id",
|
||||
};
|
||||
|
||||
fd = open("/etc/machine-id", 0);
|
||||
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';
|
||||
machineidfiles(buf, sz, path, nelem(path));
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -176,14 +182,15 @@ writeaddr(char *path, char *addr)
|
||||
fd = -1;
|
||||
if(fprintf(fp, "IBUS_ADDRESS=%s\n", addr) < 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;
|
||||
if(fclose(fp) < 0){
|
||||
fp = nil;
|
||||
goto out;
|
||||
}
|
||||
fp = nil;
|
||||
if(rename(tmp, path) < 0 || lstat(path, &st) < 0)
|
||||
if(rename(tmp, path) < 0)
|
||||
goto out;
|
||||
addrdev = st.st_dev;
|
||||
addrino = st.st_ino;
|
||||
@@ -206,13 +213,13 @@ addwatch(DBusWatch *w, void *_)
|
||||
|
||||
USED(_);
|
||||
for(i = 0; i < nwatches; i++)
|
||||
if(watches[i].w == nil){
|
||||
watches[i].w = w;
|
||||
if(watches[i] == nil){
|
||||
watches[i] = w;
|
||||
return TRUE;
|
||||
}
|
||||
if(nwatches >= Maxwatches)
|
||||
return FALSE;
|
||||
watches[nwatches++].w = w;
|
||||
watches[nwatches++] = w;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -223,8 +230,8 @@ removewatch(DBusWatch *w, void *_)
|
||||
|
||||
USED(_);
|
||||
for(i = 0; i < nwatches; i++)
|
||||
if(watches[i].w == w){
|
||||
watches[i].w = nil;
|
||||
if(watches[i] == w){
|
||||
watches[i] = nil;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -282,7 +289,6 @@ newcontext(DBusConnection *conn, const char *path)
|
||||
if(contexts[i].conn == nil){
|
||||
memset(&contexts[i], 0, sizeof contexts[i]);
|
||||
contexts[i].conn = conn;
|
||||
contexts[i].owner = ownernew();
|
||||
strncpy(contexts[i].path, path, sizeof contexts[i].path);
|
||||
contexts[i].path[sizeof contexts[i].path-1] = '\0';
|
||||
return &contexts[i];
|
||||
@@ -296,12 +302,11 @@ sendrequest(Ictx *ctx, int op, u32int ks, u32int mod, Keyres *res)
|
||||
Keyreq kr;
|
||||
|
||||
memset(&kr, 0, sizeof kr);
|
||||
kr.owner = ctx->owner;
|
||||
kr.owner = ctx;
|
||||
kr.op = op;
|
||||
kr.ks = ks;
|
||||
kr.mod = mod;
|
||||
kr.caret = ctx->caret;
|
||||
kr.want = op == Keypress;
|
||||
kr.reply = replyc;
|
||||
chansend(keyc, &kr);
|
||||
chanrecv(replyc, res);
|
||||
@@ -312,11 +317,10 @@ releasecontext(Ictx *ctx)
|
||||
{
|
||||
Keyres res;
|
||||
|
||||
if(replyc != nil)
|
||||
if(ctx->focused)
|
||||
sendrequest(ctx, Keyrelease, 0, 0, &res);
|
||||
ctx->focused = 0;
|
||||
memset(&ctx->caret, 0, sizeof ctx->caret);
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -326,6 +330,38 @@ dropcontext(Ictx *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
|
||||
appendibustext(DBusMessageIter *it, const char *s)
|
||||
{
|
||||
@@ -468,7 +504,6 @@ handlekey(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
||||
dbus_uint32_t sym, code, state;
|
||||
Keyres res;
|
||||
char commit[Maxutf], preedit[Maxutf];
|
||||
u32int ks, mod;
|
||||
|
||||
dbus_error_init(&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;
|
||||
}
|
||||
USED(code);
|
||||
if(state & Relmask || !ctx->focused)
|
||||
if(!processkey(ctx, sym, state, &res))
|
||||
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.preedit, preedit, sizeof preedit);
|
||||
if(commit[0] != '\0')
|
||||
@@ -541,7 +573,6 @@ handlecursor(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
||||
{
|
||||
DBusError err;
|
||||
dbus_int32_t x, y, w, h;
|
||||
Keyres res;
|
||||
|
||||
dbus_error_init(&err);
|
||||
if(!dbus_message_get_args(m, &err,
|
||||
@@ -555,12 +586,7 @@ handlecursor(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
||||
if(w < 0 || h < 0)
|
||||
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
||||
"cursor dimensions must not be negative");
|
||||
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);
|
||||
setcursor(ctx, x, y, h);
|
||||
return handlenoop(c, m);
|
||||
}
|
||||
|
||||
@@ -670,18 +696,26 @@ newconn(DBusServer *s, DBusConnection *c, void *_)
|
||||
USED(_);
|
||||
if(nconns >= Maxconns)
|
||||
return;
|
||||
dbus_connection_ref(c);
|
||||
dbus_connection_set_watch_functions(c, addwatch, removewatch, togglewatch, nil, nil);
|
||||
if(!dbus_connection_set_watch_functions(c, addwatch, removewatch,
|
||||
togglewatch, nil, nil)){
|
||||
fprintf(stderr, "strans: ibus: cannot watch client connection\n");
|
||||
return;
|
||||
}
|
||||
memset(&vt, 0, sizeof(vt));
|
||||
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;
|
||||
}
|
||||
|
||||
static void
|
||||
pruneconns(void)
|
||||
{
|
||||
int i, j, k;
|
||||
int i, j;
|
||||
|
||||
j = 0;
|
||||
for(i = 0; i < nconns; i++){
|
||||
@@ -689,9 +723,7 @@ pruneconns(void)
|
||||
conns[j++] = conns[i];
|
||||
continue;
|
||||
}
|
||||
for(k = 0; k < nelem(contexts); k++)
|
||||
if(contexts[k].conn == conns[i])
|
||||
dropcontext(&contexts[k]);
|
||||
dropconncontexts(conns[i]);
|
||||
dbus_connection_unref(conns[i]);
|
||||
}
|
||||
nconns = j;
|
||||
@@ -703,7 +735,10 @@ ibusinit(void)
|
||||
DBusError err;
|
||||
char addr[128];
|
||||
char *full;
|
||||
int cleanupregistered;
|
||||
|
||||
full = nil;
|
||||
cleanupregistered = 0;
|
||||
if(buildaddrpath(addrfile, sizeof(addrfile)) < 0){
|
||||
fprintf(stderr, "strans: ibus: cannot build address path\n");
|
||||
return -1;
|
||||
@@ -722,20 +757,37 @@ ibusinit(void)
|
||||
return -1;
|
||||
}
|
||||
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);
|
||||
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){
|
||||
fprintf(stderr, "strans: ibus: cannot write %s\n", addrfile);
|
||||
dbus_free(full);
|
||||
dbus_server_disconnect(srv);
|
||||
dbus_server_unref(srv);
|
||||
srv = nil;
|
||||
addrfile[0] = '\0';
|
||||
return -1;
|
||||
goto fail;
|
||||
}
|
||||
dbus_free(full);
|
||||
atexit(unlinkaddr);
|
||||
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
|
||||
@@ -755,17 +807,17 @@ ibusthread(void *_)
|
||||
for(;;){
|
||||
n = 0;
|
||||
for(i = 0; i < nwatches && n < Maxwatches; i++){
|
||||
if(watches[i].w == nil)
|
||||
if(watches[i] == nil)
|
||||
continue;
|
||||
if(!dbus_watch_get_enabled(watches[i].w))
|
||||
if(!dbus_watch_get_enabled(watches[i]))
|
||||
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;
|
||||
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_WRITABLE) pfds[n].events |= POLLOUT;
|
||||
wi[n] = i;
|
||||
polled[n] = watches[i].w;
|
||||
polled[n] = watches[i];
|
||||
n++;
|
||||
}
|
||||
rv = poll(pfds, n, 200);
|
||||
@@ -774,7 +826,7 @@ ibusthread(void *_)
|
||||
for(i = 0; i < n; i++){
|
||||
if(pfds[i].revents == 0)
|
||||
continue;
|
||||
if(watches[wi[i]].w != polled[i])
|
||||
if(watches[wi[i]] != polled[i])
|
||||
continue;
|
||||
f = 0;
|
||||
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].
|
||||
* Flags request preedit and distinguish lifecycle reset from physical Escape.
|
||||
* The server assigns the connection's owner generation; it is not sent on
|
||||
* the wire.
|
||||
* The server identifies the connection as the engine owner; that identity is
|
||||
* not sent on the wire.
|
||||
* Only Mmask modifier bits are retained.
|
||||
* Response: [eaten, commit-length-low, commit-length-high, commit...],
|
||||
* followed, when requested, by
|
||||
|
||||
13
main.c
13
main.c
@@ -5,12 +5,13 @@ Channel *drawc;
|
||||
Channel *keyc;
|
||||
Channel *dictreqc;
|
||||
Channel *dictresc;
|
||||
char *fontdir;
|
||||
char **fontfiles;
|
||||
int nfontfiles;
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
fprint(2, "usage: strans mapdir fontdir\n");
|
||||
fprint(2, "usage: strans mapdir fontfile [fontfile ...]\n");
|
||||
threadexitsall("usage");
|
||||
}
|
||||
|
||||
@@ -50,24 +51,24 @@ erealloc(void *p, ulong n)
|
||||
void
|
||||
threadmain(int argc, char **argv)
|
||||
{
|
||||
if(argc != 3)
|
||||
if(argc < 3 || argc > Maxfonts + 2)
|
||||
usage();
|
||||
|
||||
fontdir = argv[2];
|
||||
fontfiles = argv + 2;
|
||||
nfontfiles = argc - 2;
|
||||
drawc = chancreate(sizeof(Drawcmd), 4);
|
||||
keyc = chancreate(sizeof(Keyreq), 0);
|
||||
dictreqc = chancreate(sizeof(Dictreq), 4);
|
||||
dictresc = chancreate(sizeof(Dictres), 0);
|
||||
mapinit(argv[1]);
|
||||
dictinit(argv[1]);
|
||||
srvinit();
|
||||
if(proccreate(drawthread, nil, 16384) < 0)
|
||||
die("can't create draw worker");
|
||||
if(proccreate(srvthread, nil, 16384) < 0)
|
||||
die("can't create server worker");
|
||||
if(proccreate(ibusthread, nil, 32768) < 0)
|
||||
die("can't create IBus worker");
|
||||
if(proccreate(waylandthread, nil, 32768) < 0)
|
||||
die("can't create Wayland worker");
|
||||
if(threadcreate(dictthread, nil, 16384) < 0)
|
||||
die("can't create dictionary worker");
|
||||
imthread(nil);
|
||||
|
||||
136
run.sh
136
run.sh
@@ -1,8 +1,134 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
pkill strans
|
||||
sleep 1
|
||||
./strans map font &
|
||||
sleep 1
|
||||
cd "$(dirname "$0")" || exit 1
|
||||
|
||||
if test "$#" -eq 0; then
|
||||
set --
|
||||
for font in \
|
||||
/usr/share/fonts/TTF/DejaVuSans.ttf \
|
||||
/usr/share/fonts/TTF/Jigmo.ttf \
|
||||
/usr/share/fonts/TTF/Jigmo2.ttf \
|
||||
/usr/share/fonts/noto/NotoSansCJK-Regular.ttc
|
||||
do
|
||||
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_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 Channel *clientc;
|
||||
|
||||
typedef struct Client Client;
|
||||
struct Client
|
||||
{
|
||||
int fd;
|
||||
uvlong owner;
|
||||
};
|
||||
|
||||
int
|
||||
static int
|
||||
srvreadkey(int fd, Keyreq *kr)
|
||||
{
|
||||
uchar req[Ipcreqsz];
|
||||
@@ -27,51 +20,46 @@ srvreadkey(int fd, Keyreq *kr)
|
||||
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
|
||||
kr->ks = ks;
|
||||
kr->mod = mod;
|
||||
kr->want = want;
|
||||
memset(&kr->caret, 0, sizeof kr->caret);
|
||||
return 0;
|
||||
return want;
|
||||
}
|
||||
|
||||
static void
|
||||
clientthread(void *arg)
|
||||
{
|
||||
Client *client;
|
||||
Channel *reply;
|
||||
int fd;
|
||||
Keyreq kr;
|
||||
Keyres res;
|
||||
uchar out[Ipcmaxresp];
|
||||
char commit[Maxutf], preedit[Maxutf];
|
||||
int n, ncommit, npreedit;
|
||||
int n, ncommit, npreedit, want;
|
||||
uchar token;
|
||||
|
||||
client = arg;
|
||||
fd = client->fd;
|
||||
fd = (int)(uintptr)arg;
|
||||
threadsetname("client %d", fd);
|
||||
reply = chancreate(sizeof(Keyres), 0);
|
||||
kr.reply = reply;
|
||||
kr.owner = client->owner;
|
||||
while(srvreadkey(fd, &kr) == 0){
|
||||
kr.owner = &fd;
|
||||
while((want = srvreadkey(fd, &kr)) >= 0){
|
||||
chansend(keyc, &kr);
|
||||
chanrecv(reply, &res);
|
||||
ncommit = stoutf(&res.commit, commit, sizeof commit);
|
||||
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
|
||||
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)
|
||||
break;
|
||||
}
|
||||
kr.op = Keyrelease;
|
||||
kr.want = 0;
|
||||
chansend(keyc, &kr);
|
||||
chanrecv(reply, &res);
|
||||
chanfree(reply);
|
||||
close(fd);
|
||||
free(client);
|
||||
chanrecv(clientc, &token);
|
||||
}
|
||||
|
||||
static void
|
||||
void
|
||||
srvinit(void)
|
||||
{
|
||||
char *addr, path[sizeof(((struct sockaddr_un*)0)->sun_path)];
|
||||
@@ -91,13 +79,11 @@ srvinit(void)
|
||||
void
|
||||
srvthread(void*)
|
||||
{
|
||||
Client *client;
|
||||
char ldir[40];
|
||||
int fd;
|
||||
uchar token;
|
||||
|
||||
threadsetname("srv");
|
||||
srvinit();
|
||||
token = 0;
|
||||
clientc = chancreate(sizeof token, Maxclients);
|
||||
for(;;){
|
||||
@@ -108,13 +94,9 @@ srvthread(void*)
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
client = emalloc(sizeof *client);
|
||||
client->fd = fd;
|
||||
client->owner = ownernew();
|
||||
if(proccreate(clientthread, client, 8192) < 0){
|
||||
if(proccreate(clientthread, (void*)(uintptr)fd, 8192) < 0){
|
||||
chanrecv(clientc, &token);
|
||||
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 int popup = 0;
|
||||
static int visible = 0;
|
||||
static Lock ownerlock;
|
||||
static uvlong ownerctr;
|
||||
static uvlong activeowner;
|
||||
static void *activeowner;
|
||||
static Caret caret;
|
||||
static void dictqmap(Im*);
|
||||
static void dictqjp(Im*);
|
||||
@@ -22,19 +20,6 @@ struct Search
|
||||
};
|
||||
static Search search;
|
||||
|
||||
uvlong
|
||||
ownernew(void)
|
||||
{
|
||||
uvlong n;
|
||||
|
||||
lock(&ownerlock);
|
||||
n = ++ownerctr;
|
||||
if(n == 0)
|
||||
n = ++ownerctr;
|
||||
unlock(&ownerlock);
|
||||
return n;
|
||||
}
|
||||
|
||||
Lang langs[] = {
|
||||
{LangEN, nil, nil, nil, nil, nil, nil, nil},
|
||||
{LangJP, "hira", "kanji", transmap, backjp, dictqjp, nil, nil},
|
||||
@@ -155,7 +140,7 @@ reset(void)
|
||||
show();
|
||||
}
|
||||
|
||||
void
|
||||
static void
|
||||
dictsend(Im *im, Str *key)
|
||||
{
|
||||
Dictreq old, req;
|
||||
@@ -792,7 +777,7 @@ init(void)
|
||||
im.l = getlang(LangEN);
|
||||
memset(&search, 0, sizeof search);
|
||||
memset(&caret, 0, sizeof caret);
|
||||
activeowner = 0;
|
||||
activeowner = nil;
|
||||
visible = 0;
|
||||
}
|
||||
|
||||
@@ -814,7 +799,7 @@ imhandlekey(Keyreq *kr)
|
||||
case Keyrelease:
|
||||
if(kr->owner == activeowner){
|
||||
reset();
|
||||
activeowner = 0;
|
||||
activeowner = nil;
|
||||
}
|
||||
break;
|
||||
case Keyreset:
|
||||
@@ -827,7 +812,7 @@ imhandlekey(Keyreq *kr)
|
||||
show();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
case Keypress:
|
||||
res.eaten = 0;
|
||||
if(meaningful(kr)){
|
||||
if(kr->owner != activeowner){
|
||||
@@ -840,7 +825,7 @@ imhandlekey(Keyreq *kr)
|
||||
}
|
||||
break;
|
||||
}
|
||||
if(kr->want && kr->owner == activeowner){
|
||||
if(kr->owner == activeowner){
|
||||
if(search.lang)
|
||||
res.preedit = search.text;
|
||||
else
|
||||
|
||||
@@ -1,28 +1,63 @@
|
||||
CC = 9c
|
||||
LD = 9l
|
||||
HOSTCC = cc
|
||||
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
|
||||
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 \
|
||||
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c wayland_test.c \
|
||||
popup_test.c
|
||||
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
||||
popup_test.c font_test.c ibus_test.c server_test.c
|
||||
TESTOBJ = $(TESTSRC:.c=.o)
|
||||
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c wayland_state.c \
|
||||
popup_layout.c
|
||||
PARENTSRC = str.c hash.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
|
||||
font.c
|
||||
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
||||
OBJS = $(TESTOBJ) $(PARENTOBJ)
|
||||
|
||||
all: $(PROG)
|
||||
all: $(PROG) $(LIVE)
|
||||
|
||||
check test: $(PROG)
|
||||
check test: $(PROG) $(LIVE)
|
||||
./$(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)
|
||||
$(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
|
||||
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
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
@@ -31,6 +66,6 @@ unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(PROG)
|
||||
rm -f $(OBJS) $(PROG) $(LIVE)
|
||||
|
||||
.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*);
|
||||
|
||||
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
|
||||
vietnamese_state_lifetime(struct ct *t)
|
||||
{
|
||||
@@ -161,7 +192,7 @@ engine_dictionary_queue_latest_wins(struct ct *t)
|
||||
}
|
||||
|
||||
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;
|
||||
Keyreq req;
|
||||
@@ -175,7 +206,6 @@ ownerrequestat(uvlong owner, int op, Rune key, u32int mod, Caret *caret)
|
||||
req.mod = mod;
|
||||
if(caret != nil)
|
||||
req.caret = *caret;
|
||||
req.want = 1;
|
||||
req.reply = reply;
|
||||
imhandlekey(&req);
|
||||
chanrecv(reply, &res);
|
||||
@@ -184,7 +214,7 @@ ownerrequestat(uvlong owner, int op, Rune key, u32int mod, Caret *caret)
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -194,57 +224,75 @@ engine_active_owner_lifecycle(struct ct *t)
|
||||
{
|
||||
Keyres res;
|
||||
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();
|
||||
im.l = getlang(LangJP);
|
||||
res = ownerrequest(a, Keypress, 'k', 0);
|
||||
res = ownerrequest(&a, Keypress, 'k', 0);
|
||||
CT_CHECK(t, res.eaten);
|
||||
res = ownerrequest(a, Keypress, 'a', 0);
|
||||
res = ownerrequest(&a, Keypress, 'a', 0);
|
||||
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);
|
||||
CT_EQ_UINT(t, b, activeowner);
|
||||
res = ownerrequest(a, Keyreset, 0, 0);
|
||||
CT_EQ_PTR(t, &b, activeowner);
|
||||
res = ownerrequest(&a, Keyreset, 0, 0);
|
||||
CT_EQ_INT(t, 0, res.preedit.n);
|
||||
shown = shownpre(&im);
|
||||
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, 'a', 0);
|
||||
res = ownerrequest(&b, Keypress, 'y', 0);
|
||||
res = ownerrequest(&b, Keypress, 'a', 0);
|
||||
checkstr(t, "current owner continues", "にゃ", &res.preedit);
|
||||
ownerrequest(a, Keyrelease, 0, 0);
|
||||
ownerrequest(&a, Keyrelease, 0, 0);
|
||||
shown = shownpre(&im);
|
||||
checkstr(t, "stale release preserves owner", "にゃ", &shown);
|
||||
CT_EQ_UINT(t, b, activeowner);
|
||||
CT_EQ_PTR(t, &b, activeowner);
|
||||
|
||||
ownerrequest(b, Keyrelease, 0, 0);
|
||||
CT_EQ_UINT(t, 0, activeowner);
|
||||
ownerrequest(&b, Keyrelease, 0, 0);
|
||||
CT_EQ_PTR(t, nil, activeowner);
|
||||
CT_EQ_INT(t, 0, im.pre.n);
|
||||
CT_EQ_INT(t, 0, im.raw.n);
|
||||
ownerrequest(c, Keypress, Kmodfirst, 0);
|
||||
CT_EQ_UINT(t, 0, activeowner);
|
||||
res = ownerrequest(c, Keypress, 'k', 0);
|
||||
CT_EQ_UINT(t, c, activeowner);
|
||||
ownerrequest(&c, Keypress, Kmodfirst, 0);
|
||||
CT_EQ_PTR(t, nil, activeowner);
|
||||
res = ownerrequest(&c, Keypress, 'k', 0);
|
||||
CT_EQ_PTR(t, &c, activeowner);
|
||||
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
|
||||
engine_active_owner_caret(struct ct *t)
|
||||
{
|
||||
Caret a, moved;
|
||||
uvlong one, two;
|
||||
char one, two;
|
||||
|
||||
one = ownernew();
|
||||
two = ownernew();
|
||||
memset(&a, 0, sizeof a);
|
||||
a.valid = 1;
|
||||
a.x = 10;
|
||||
@@ -254,15 +302,15 @@ engine_active_owner_caret(struct ct *t)
|
||||
moved.x = 80;
|
||||
init();
|
||||
im.l = getlang(LangJP);
|
||||
ownerrequestat(one, Keypress, 'k', 0, &a);
|
||||
ownerrequestat(&one, Keypress, 'k', 0, &a);
|
||||
CT_CHECK(t, caret.valid);
|
||||
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);
|
||||
ownerrequestat(one, Keycaret, 0, 0, &moved);
|
||||
ownerrequestat(&one, Keycaret, 0, 0, &moved);
|
||||
CT_EQ_INT(t, 80, caret.x);
|
||||
ownerrequest(two, Keypress, 'n', 0);
|
||||
CT_EQ_UINT(t, two, activeowner);
|
||||
ownerrequest(&two, Keypress, 'n', 0);
|
||||
CT_EQ_PTR(t, &two, activeowner);
|
||||
CT_CHECK(t, !caret.valid);
|
||||
}
|
||||
|
||||
@@ -1430,7 +1478,8 @@ engine_randomized_stress(struct ct *t)
|
||||
Keyres res;
|
||||
Caret pos;
|
||||
u32int rnd, mod;
|
||||
uvlong owners[3], owner;
|
||||
char contexts[3];
|
||||
void *owner, *owners[3];
|
||||
int i, j, op;
|
||||
|
||||
vi = getlang(LangVI);
|
||||
@@ -1439,9 +1488,9 @@ engine_randomized_stress(struct ct *t)
|
||||
vimap = vi->map;
|
||||
vi->map = testvi.map;
|
||||
emojibegin(&f, 0);
|
||||
owners[0] = ownernew();
|
||||
owners[1] = ownernew();
|
||||
owners[2] = ownernew();
|
||||
owners[0] = &contexts[0];
|
||||
owners[1] = &contexts[1];
|
||||
owners[2] = &contexts[2];
|
||||
rnd = 0x5eed1234U;
|
||||
for(i = 0; i < 16000; i++){
|
||||
rnd = stressrand(&rnd);
|
||||
@@ -1506,7 +1555,7 @@ engine_randomized_stress(struct ct *t)
|
||||
i = 16000;
|
||||
break;
|
||||
}
|
||||
if(activeowner != 0 && activeowner != owners[0] &&
|
||||
if(activeowner != nil && activeowner != owners[0] &&
|
||||
activeowner != owners[1] && activeowner != owners[2]){
|
||||
CT_ERRORF(t, "unknown active owner after stress operation %d", i);
|
||||
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]
|
||||
MKEMOJI = ROOT / "map" / "mkemoji"
|
||||
TIMEOUT = 10
|
||||
|
||||
|
||||
def generate(source=None):
|
||||
args = [sys.executable, "-B", str(MKEMOJI)]
|
||||
if source is not None:
|
||||
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):
|
||||
|
||||
@@ -11,6 +11,7 @@ from pathlib import Path
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
IMPORT = ROOT / "map" / "libhangul2hanja"
|
||||
GENERATE = ROOT / "map" / "mkhanja"
|
||||
TIMEOUT = 10
|
||||
|
||||
|
||||
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",
|
||||
env=env,
|
||||
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]
|
||||
CONVERTER = ROOT / "map" / "skk2ktrans"
|
||||
FIXTURES = Path(__file__).parent / "data" / "skk"
|
||||
TIMEOUT = 10
|
||||
|
||||
|
||||
class Skk2KtransTest(unittest.TestCase):
|
||||
@@ -19,27 +20,31 @@ class Skk2KtransTest(unittest.TestCase):
|
||||
input_path = Path(directory) / "source.skk"
|
||||
input_path.write_bytes(source.encode("euc_jp"))
|
||||
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.stdout, expected)
|
||||
|
||||
def test_invalid_euc_jp_is_rejected(self):
|
||||
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.assertTrue(result.stderr)
|
||||
|
||||
def test_escaped_candidate_is_rejected(self):
|
||||
source = b"key /escaped\\/slash/\n"
|
||||
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.assertIn(b"escaped candidates are unsupported", result.stderr)
|
||||
|
||||
def test_expression_candidate_is_rejected(self):
|
||||
source = b'key /(concat "a/b")/literal/\n'
|
||||
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.assertIn(b"expression candidates are unsupported", result.stderr)
|
||||
|
||||
@@ -50,7 +55,8 @@ class Skk2KtransTest(unittest.TestCase):
|
||||
first.write_bytes(b"a /A/")
|
||||
second.write_bytes(b"b /B/\n")
|
||||
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.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 trie_exact_prefix_and_duplicate(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 font_render(struct ct*);
|
||||
void production_maps_load(struct ct*);
|
||||
void transmap_states(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_dictionary_queue_latest_wins(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_commit_contract(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_fragmented_and_truncated(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 },
|
||||
{ "trie/exact-prefix-duplicate", trie_exact_prefix_and_duplicate },
|
||||
{ "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 },
|
||||
{ "font/render", font_render },
|
||||
{ "map/production-lifecycle", production_maps_load },
|
||||
{ "transmap/states", transmap_states },
|
||||
{ "hangul/sequences", korean_sequences },
|
||||
@@ -90,6 +88,7 @@ static const struct ct_test tests[] = {
|
||||
{ "engine/candidate-shortcut-modifiers", engine_candidate_shortcut_modifiers },
|
||||
{ "engine/dictionary-queue-latest", engine_dictionary_queue_latest_wins },
|
||||
{ "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/commit-contract", engine_commit_contract },
|
||||
{ "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-fragmented-truncated", ipc_response_fragmented_and_truncated },
|
||||
{ "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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
extern char *fontdir;
|
||||
extern char **fontfiles;
|
||||
extern int nfontfiles;
|
||||
|
||||
static xcb_connection_t *conn;
|
||||
static xcb_screen_t *scr;
|
||||
@@ -140,7 +141,7 @@ wininit(void)
|
||||
mask = XCB_CW_BACK_PIXMAP;
|
||||
xcb_change_window_attributes(conn, win, mask, &pix);
|
||||
img = emalloc(Imgw * Imgh * sizeof(img[0]));
|
||||
if(!fontinit(fontdir)){
|
||||
if(!fontinit(fontfiles, nfontfiles)){
|
||||
fprint(2, "strans: popup disabled: no usable fonts\n");
|
||||
wincleanup();
|
||||
return 0;
|
||||
@@ -170,7 +171,7 @@ fill(u32int *buf, int n, u32int color)
|
||||
}
|
||||
|
||||
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;
|
||||
Str *s;
|
||||
@@ -184,7 +185,7 @@ drawkouho(Drawcmd *dc, int first, int n, int w, int h)
|
||||
fill(img + sely * w, Fontsz * w, Colsel);
|
||||
}
|
||||
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);
|
||||
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,
|
||||
vals);
|
||||
xcb_map_window(conn, win);
|
||||
drawkouho(dc, 0, n, w, h);
|
||||
drawkouho(dc, n, w, h);
|
||||
putimage(w, h);
|
||||
return xcb_flush(conn) > 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user