Compare commits

...

28 Commits

Author SHA1 Message Date
c4b0141413 build: avoid xkbcommon in D-Bus-only tests 2026-08-14 02:52:10 +09:00
46c8ccd779 tests: bound generator subprocesses 2026-08-14 02:50:58 +09:00
d7115c5eb5 tests: close inherited descriptors before exec 2026-08-14 02:49:57 +09:00
f395f29918 ibus: complete setup before publication 2026-08-14 02:48:49 +09:00
6a2b2bccc4 run: make launcher ownership explicit 2026-08-14 02:43:42 +09:00
05ff88c74f run: use installed default fonts 2026-08-14 02:10:45 +09:00
6c0a533c4e ibus: fall back from unreadable machine id 2026-08-14 02:10:16 +09:00
e44fc005da tests: cover stale daemon endpoint recovery 2026-08-14 01:54:25 +09:00
469278209f tests: verify failed startup publishes no endpoints 2026-08-14 01:29:00 +09:00
9569c2eaaf tests: cover daemon endpoint collision ownership 2026-08-14 01:03:13 +09:00
335783ebe5 daemon: claim ipc endpoint before ibus publication 2026-08-14 01:02:17 +09:00
0f8396915b tests: cover ibus connection capacity recovery 2026-08-14 00:44:49 +09:00
91b3042e66 ibus: provision connection watch capacity 2026-08-14 00:44:16 +09:00
b44663096f tests: verify live daemon cleanup 2026-08-14 00:11:03 +09:00
685d78bab5 tests: keep headless environments minimal 2026-08-14 00:04:32 +09:00
f8ebfcdc86 tests: cover ipc listener capacity and recovery 2026-08-14 00:02:23 +09:00
f399eb0af6 tests: cover live ibus transport lifecycle 2026-08-13 23:55:27 +09:00
88d26c598d renderer: remove obsolete candidate offset 2026-08-13 23:21:59 +09:00
9f4f9ceb14 ibus: cover context ownership through the engine 2026-08-13 23:21:12 +09:00
bb2a970db1 tests: cover ownership across ipc connections 2026-08-13 23:07:25 +09:00
21a94d447c server: remove client ownership wrapper 2026-08-13 22:09:54 +09:00
367cbb6cc3 ibus: make context release explicit 2026-08-13 22:03:27 +09:00
b6a9ccd9d9 cleanup: simplify frontend request ownership 2026-08-13 21:57:32 +09:00
13f0b436b8 headers: make server key reader private 2026-08-13 21:26:56 +09:00
c07673ad4c build: list daemon sources explicitly 2026-08-13 21:26:00 +09:00
e01a991de4 remove: drop Wayland frontend 2026-08-13 21:25:12 +09:00
fffbb9d8de fonts: remove bundled binary payloads 2026-08-13 20:40:50 +09:00
f46d51b539 renderer: use direct FreeType with explicit font files 2026-08-13 20:40:38 +09:00
44 changed files with 7479 additions and 7689 deletions

5
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

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

View File

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

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

View File

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

View File

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

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

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

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

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

635
tests/daemon_failure_test.c Normal file
View 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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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