Compare commits
55 Commits
544ed835b7
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f77e0e9ac | |||
| 761428dbc4 | |||
| 1bc0ba0491 | |||
| 74607afef8 | |||
| 591029237e | |||
| bdf05e3c4a | |||
| 4802b6593e | |||
| ba0a08abe2 | |||
| 08d0e0fec8 | |||
| d3d46c8cb1 | |||
| b08f3bb7a2 | |||
| cf0eba6642 | |||
| 5d9a2a614e | |||
| 921e632eae | |||
| 910bf51347 | |||
| f7277f54a3 | |||
| 997e4c8d93 | |||
| dab80b3a77 | |||
| d9e27e8b01 | |||
| 5abd885e11 | |||
| a351f50308 | |||
| c9c01fdeb9 | |||
| bfcd9f6786 | |||
| 3f8f51adf4 | |||
| 8ebe6b32df | |||
| b61c04b280 | |||
| dadda19b3a | |||
| 62ddc2177b | |||
| 82078a60e7 | |||
| 46ab39ab95 | |||
| 65066eef53 | |||
| 0f128a46b3 | |||
| 650f00812d | |||
| e64e4ea6af | |||
| 867c6ebe40 | |||
| 8a08efe2bb | |||
| 5ff54237ff | |||
| bbc0c97656 | |||
| aef577026d | |||
| 8472474332 | |||
| 33710178d4 | |||
| 5cb9f69590 | |||
| 6d511919e8 | |||
| 40fd4ab551 | |||
| 841d93ac35 | |||
| f6dd056ffb | |||
| ad2f2fc5eb | |||
| 2e1e0b9645 | |||
| 5664a008de | |||
| 4ac0625c6b | |||
| 233884e51f | |||
| 27180bc0fe | |||
| f6477cb97d | |||
| 059e8bbc75 | |||
| e93ec43706 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,5 +1,9 @@
|
|||||||
/*.o
|
/*.o
|
||||||
/strans
|
/strans
|
||||||
|
/imv2.c
|
||||||
|
/imv2.h
|
||||||
|
/vkv1.c
|
||||||
|
/vkv1.h
|
||||||
/tests/*.o
|
/tests/*.o
|
||||||
/tests/unit_test
|
/tests/unit_test
|
||||||
/tests/stress_test
|
/tests/stress_test
|
||||||
@@ -14,3 +18,4 @@
|
|||||||
/gtk/im-strans.so
|
/gtk/im-strans.so
|
||||||
/bench/bench
|
/bench/bench
|
||||||
/bench/perf.data*
|
/bench/perf.data*
|
||||||
|
/ref-*/
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ RUN pacman -Syu --noconfirm --needed \
|
|||||||
ttf-dejavu \
|
ttf-dejavu \
|
||||||
ttf-jigmo \
|
ttf-jigmo \
|
||||||
valgrind \
|
valgrind \
|
||||||
|
wayland \
|
||||||
which \
|
which \
|
||||||
xorg-server-xvfb \
|
xorg-server-xvfb \
|
||||||
xcb-imdkit \
|
xcb-imdkit \
|
||||||
@@ -41,4 +42,9 @@ RUN dbus-uuidgen --ensure=/etc/machine-id
|
|||||||
ENV PLAN9=/usr/lib/plan9
|
ENV PLAN9=/usr/lib/plan9
|
||||||
ENV PATH=$PATH:/usr/lib/plan9/bin
|
ENV PATH=$PATH:/usr/lib/plan9/bin
|
||||||
|
|
||||||
|
# 9c and 9l filter their own output through $egrep, which plan9port never
|
||||||
|
# assigns, so the pipeline dies and takes every warning and error with it.
|
||||||
|
# Both source $PLAN9/config, which the distribution does not ship.
|
||||||
|
RUN echo 'egrep="grep -E"' > $PLAN9/config
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# License inventory
|
# License inventory
|
||||||
|
|
||||||
- `BSD-3-Clause-libhangul-hanja.txt` contains the terms for the
|
- `BSD-3-Clause-libhangul-hanja.txt` contains the terms for the Hanja data
|
||||||
single-character Hanja data in `map/hanja.src` and generated
|
in `map/hanja.src` and the symbol data in `map/mssymbol.src`, and for the
|
||||||
`map/hanja.dict`, derived from libhangul's `data/hanja/hanja.txt`.
|
generated `map/hanja.dict` they share, derived from libhangul's
|
||||||
|
`data/hanja/hanja.txt` and `data/hanja/mssymbol.txt`.
|
||||||
- `GPL-2.0-or-later.txt` contains GNU GPL version 2. The header of
|
- `GPL-2.0-or-later.txt` contains GNU GPL version 2. The header of
|
||||||
`map/kanji.dict` grants the option to use GPL version 2 or any later
|
`map/kanji.dict` grants the option to use GPL version 2 or any later
|
||||||
version.
|
version.
|
||||||
@@ -10,4 +11,7 @@
|
|||||||
emoji names and keywords in `map/emoji.src` and generated
|
emoji names and keywords in `map/emoji.src` and generated
|
||||||
`map/emoji.dict`, derived from Unicode's `emoji-test.txt` and CLDR's
|
`map/emoji.dict`, derived from Unicode's `emoji-test.txt` and CLDR's
|
||||||
annotations.
|
annotations.
|
||||||
|
- The two protocol descriptions in `proto/` are vendored verbatim from
|
||||||
|
wlroots `012ca825` and carry their own MIT terms inside the XML.
|
||||||
|
|
||||||
No repository-wide license has been declared for original strans source.
|
No repository-wide license has been declared for original strans source.
|
||||||
|
|||||||
27
Makefile
27
Makefile
@@ -1,6 +1,7 @@
|
|||||||
CC = 9c
|
CC = 9c
|
||||||
LD = 9l
|
LD = 9l
|
||||||
PKG_CONFIG ?= pkg-config
|
PKG_CONFIG ?= pkg-config
|
||||||
|
SCANNER ?= wayland-scanner
|
||||||
CFLAGS ?= -O2 -g
|
CFLAGS ?= -O2 -g
|
||||||
WARN_CFLAGS = -Wall -Wextra
|
WARN_CFLAGS = -Wall -Wextra
|
||||||
IBUS_CFLAGS = $(shell $(PKG_CONFIG) --cflags dbus-1 xkbcommon)
|
IBUS_CFLAGS = $(shell $(PKG_CONFIG) --cflags dbus-1 xkbcommon)
|
||||||
@@ -11,34 +12,50 @@ TEXT_CFLAGS = $(shell $(PKG_CONFIG) --cflags pangocairo cairo fontconfig)
|
|||||||
TEXT_LIBS = $(shell $(PKG_CONFIG) --libs pangocairo cairo fontconfig)
|
TEXT_LIBS = $(shell $(PKG_CONFIG) --libs pangocairo cairo fontconfig)
|
||||||
POPUP_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-aux xcb-randr)
|
POPUP_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-aux xcb-randr)
|
||||||
POPUP_LIBS = $(shell $(PKG_CONFIG) --libs xcb-aux xcb-randr)
|
POPUP_LIBS = $(shell $(PKG_CONFIG) --libs xcb-aux xcb-randr)
|
||||||
|
WL_CFLAGS = $(shell $(PKG_CONFIG) --cflags wayland-client xkbcommon)
|
||||||
|
WL_LIBS = $(shell $(PKG_CONFIG) --libs wayland-client xkbcommon)
|
||||||
PROJECT_CPPFLAGS =
|
PROJECT_CPPFLAGS =
|
||||||
PROJECT_LDLIBS = -lthread -lbio -lxcb $(IBUS_LIBS) $(TEXT_LIBS) \
|
PROJECT_LDLIBS = -lthread -lbio -lxcb $(IBUS_LIBS) $(TEXT_LIBS) \
|
||||||
$(XIM_LIBS) $(POPUP_LIBS)
|
$(XIM_LIBS) $(POPUP_LIBS) $(WL_LIBS)
|
||||||
PROG = strans
|
PROG = strans
|
||||||
DOCKER_IMAGE = strans-build
|
DOCKER_IMAGE = strans-build
|
||||||
DOCKER_RUN = docker run --rm --user "$$(id -u):$$(id -g)" \
|
DOCKER_RUN = docker run --rm --user "$$(id -u):$$(id -g)" \
|
||||||
-v "$(CURDIR):/src" $(DOCKER_IMAGE)
|
-v "$(CURDIR):/src" $(DOCKER_IMAGE)
|
||||||
|
|
||||||
SRCS = compose.c dict.c font.c ibus.c ipc.c ko.c main.c popup_layout.c \
|
SRCS = compose.c dict.c font.c ibus.c ipc.c ko.c main.c popup_layout.c \
|
||||||
srv.c str.c strans.c trie.c vi.c win.c xim.c
|
srv.c str.c strans.c trie.c vi.c win.c wl.c xim.c
|
||||||
OBJS = $(SRCS:.c=.o)
|
OBJS = $(SRCS:.c=.o)
|
||||||
|
# wayland-scanner writes these from the XML in proto/; only the XML is tracked.
|
||||||
|
PROTO = imv2.c imv2.h vkv1.c vkv1.h
|
||||||
|
PROTOOBJ = imv2.o vkv1.o
|
||||||
|
|
||||||
all: $(PROG) gtk
|
all: $(PROG) gtk
|
||||||
|
|
||||||
$(PROG): $(OBJS)
|
$(PROG): $(OBJS) $(PROTOOBJ)
|
||||||
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(PROJECT_LDLIBS) $(LDLIBS)
|
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(PROTOOBJ) $(PROJECT_LDLIBS) $(LDLIBS)
|
||||||
|
|
||||||
$(OBJS): dat.h fn.h ipc.h
|
$(OBJS): dat.h fn.h ipc.h
|
||||||
|
wl.o: imv2.h vkv1.h
|
||||||
compose.o ibus.o: PROJECT_CPPFLAGS += $(IBUS_CFLAGS)
|
compose.o ibus.o: PROJECT_CPPFLAGS += $(IBUS_CFLAGS)
|
||||||
font.o: PROJECT_CPPFLAGS += $(TEXT_CFLAGS)
|
font.o: PROJECT_CPPFLAGS += $(TEXT_CFLAGS)
|
||||||
win.o: PROJECT_CPPFLAGS += $(POPUP_CFLAGS)
|
win.o: PROJECT_CPPFLAGS += $(POPUP_CFLAGS)
|
||||||
|
wl.o $(PROTOOBJ): PROJECT_CPPFLAGS += $(WL_CFLAGS)
|
||||||
xim.o: PROJECT_CPPFLAGS += $(XIM_CFLAGS)
|
xim.o: PROJECT_CPPFLAGS += $(XIM_CFLAGS)
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
$(CC) $(CPPFLAGS) $(PROJECT_CPPFLAGS) $(WARN_CFLAGS) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CPPFLAGS) $(PROJECT_CPPFLAGS) $(WARN_CFLAGS) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
imv2.h: proto/input-method-unstable-v2.xml
|
||||||
|
$(SCANNER) client-header $< $@
|
||||||
|
imv2.c: proto/input-method-unstable-v2.xml
|
||||||
|
$(SCANNER) private-code $< $@
|
||||||
|
vkv1.h: proto/virtual-keyboard-unstable-v1.xml
|
||||||
|
$(SCANNER) client-header $< $@
|
||||||
|
vkv1.c: proto/virtual-keyboard-unstable-v1.xml
|
||||||
|
$(SCANNER) private-code $< $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJS) $(PROG)
|
rm -f $(OBJS) $(PROG) $(PROTO) $(PROTOOBJ)
|
||||||
$(MAKE) -C tests clean
|
$(MAKE) -C tests clean
|
||||||
$(MAKE) -C gtk/ clean
|
$(MAKE) -C gtk/ clean
|
||||||
$(MAKE) -C bench/ clean
|
$(MAKE) -C bench/ clean
|
||||||
|
|||||||
290
README.md
290
README.md
@@ -1,213 +1,176 @@
|
|||||||
# strans
|
# strans
|
||||||
|
|
||||||
strans is a small, single-user input method for Korean, Japanese, English,
|
strans is a small, single-user input method for Korean, Japanese, English,
|
||||||
emoji, and symbols. It provides one engine for IBus (which GTK 4 and
|
emoji and symbols, with Vietnamese Telex as a compatibility mode. One
|
||||||
Qt use), XIM, and GTK 3. Vietnamese Telex is also available as a
|
engine serves four frontends: `zwp_input_method_v2` on Wayland — sway and
|
||||||
compatibility mode.
|
any other compositor that offers it — IBus, which GTK 4 and Qt use, XIM,
|
||||||
|
and a GTK 3 module.
|
||||||
|
|
||||||
## Input modes
|
## Modes
|
||||||
|
|
||||||
| Key | Mode |
|
| Key | Mode |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `Ctrl+N` | Japanese Hiragana and Kanji conversion |
|
|
||||||
| `Ctrl+K` | Japanese Katakana |
|
|
||||||
| `Ctrl+S` | Korean Hangul (2-beolsik) |
|
| `Ctrl+S` | Korean Hangul (2-beolsik) |
|
||||||
|
| `Ctrl+N` | Japanese Hiragana, converting to Kanji |
|
||||||
|
| `Ctrl+K` | Japanese Katakana |
|
||||||
| `Ctrl+T` | English |
|
| `Ctrl+T` | English |
|
||||||
| `Ctrl+V` | Vietnamese Telex |
|
| `Ctrl+V` | Vietnamese Telex |
|
||||||
| `Ctrl+E` | Emoji and symbol search |
|
| `Ctrl+E` | Emoji and symbol search |
|
||||||
| `Ctrl+H` | One-shot Hanja search |
|
| `Ctrl+H` | One-shot Hanja and symbol search |
|
||||||
|
|
||||||
Switching mode shows the mode switched to — `A`, `한`, `あ`, `ア`, `ă` — in
|
Only a plain `Ctrl` chord is a strans key: with `Shift`, `Alt` or `Super`
|
||||||
the popup until the next key.
|
held it commits what is pending and goes to the application, so
|
||||||
|
`Ctrl+Shift+V` still pastes. `Backspace`, `Enter`, `Tab`, `Esc` and the
|
||||||
|
arrow and page keys do the same under `Ctrl`, so `Ctrl+Backspace` still
|
||||||
|
deletes a word. The mode switched to — `한`, `あ`, `ア`, `ă`, `A` — shows
|
||||||
|
in the popup until the next key. A Korean keyboard's 한/영 and 한자 keys
|
||||||
|
stand for `Ctrl+S`/`Ctrl+T` and `Ctrl+H`; a Japanese one's 半角/全角,
|
||||||
|
ひらがな/カタカナ, 変換 and 無変換 for the kana modes, `Space` and English.
|
||||||
|
|
||||||
A Korean keyboard's 한/영 key toggles Korean and English, and its 한자 key
|
## Composing
|
||||||
is `Ctrl+H`; a Japanese keyboard's 半角/全角 toggles Japanese and English,
|
|
||||||
ひらがな/カタカナ switches between the two kana modes, 変換 turns Japanese
|
|
||||||
on and then converts like `Space`, and 無変換 turns it off. Only a plain
|
|
||||||
`Ctrl` chord is a strans key: with `Shift`, `Alt`, or `Super` held it
|
|
||||||
commits what is pending and goes to the application, so `Ctrl+Shift+V`
|
|
||||||
still pastes.
|
|
||||||
|
|
||||||
Hiragana mode composes a complete reading, then shows its Kanji candidates
|
Japanese composes a whole reading and then offers its Kanji with none
|
||||||
with none chosen: the exact readings of the dictionary, then the verbs and
|
chosen. `Space` and `Tab` step through the candidates and wrap (`Shift`
|
||||||
adjectives SKK keys by stem and okurigana (かく offers 確 and 書く), and
|
reverses), the reading in Katakana last, so a word the dictionary lacks
|
||||||
under `Space` the reading in Katakana last, so a word the dictionary lacks
|
converts with one `Space`; `Up`/`Down` and `PageUp`/`PageDown` move without
|
||||||
converts to Katakana with one `Space`. `Space` or `Tab` chooses the first
|
wrapping. A chosen candidate takes the reading's place in the preedit, so
|
||||||
candidate and then steps on, wrapping (with `Shift`, back); `Up`/`Down` and
|
it stands where it will land, and `Enter` commits it, or the reading, as
|
||||||
`PageUp`/`PageDown` move without wrapping. Once a candidate is chosen, an unmodified `1`-`9` commits that row of the
|
`0` and typing on always do. Once a candidate is chosen `1`-`9` take that
|
||||||
current page; before that the digits type. `Enter` commits the chosen candidate, or the
|
row of the page shown; before that the digits type. `Backspace` deletes
|
||||||
reading when none is chosen, as `0` always does; so does typing on, or any
|
the last kana shown, and romaji that made no kana stays as typed until it
|
||||||
key that ends the composition. `Backspace` deletes the last kana shown;
|
is mended; `Esc` cancels the reading, and on a chosen candidate both go
|
||||||
romaji that made no kana stays in the reading as typed until it is mended.
|
back to it. The dictionary keys verbs and adjectives by stem and
|
||||||
The dictionary keys a verb or adjective by its stem, so `kaku` offers the
|
okurigana, so `kaku` offers かく's readings and then 書く and its kin —
|
||||||
readings of かく and then 書く and its kin; typing the okurigana with
|
typing the okurigana with `Shift`, as SKK does, `kaKu`, asks for that
|
||||||
`Shift`, as SKK does — `kaKu` — asks for that split first. Caps Lock is
|
split first. Katakana mode does not convert, so there `Space` and `Tab`
|
||||||
not `Shift` and marks nothing.
|
commit the reading and go on to the application.
|
||||||
`Backspace` and `Esc` on a chosen candidate go back to the reading, and
|
|
||||||
`Esc` on the reading cancels it. Katakana mode does not perform Kanji
|
|
||||||
conversion.
|
|
||||||
|
|
||||||
Korean composes one syllable at a time; `Enter`, `Tab`, `Esc`, and any key
|
Korean composes one syllable at a time. `Enter`, `Tab`, `Esc` and any key
|
||||||
that is not a jamo commit the syllable and go on to the application, so
|
that is not a jamo commit the syllable and go on to the application, so
|
||||||
`Esc` still leaves insert mode. Two lone consonants join into the compound
|
`Esc` still leaves insert mode. Two lone consonants join into the compound
|
||||||
final they make (rt → ㄳ) and a vowel typed before its consonant is
|
final they make (rt → ㄳ), and a vowel typed before its consonant is
|
||||||
reordered under it (kr → 가), as in libhangul.
|
reordered under it (kr → 가), as in libhangul.
|
||||||
|
|
||||||
Emoji and Hanja searches take over the keys until `Enter`, `Space`, or
|
A search takes the keys until `Enter`, `Space` or `1`-`9` picks a result or
|
||||||
`1`-`9` picks a result or `Esc` cancels; they return to the previous mode
|
`Esc` cancels, then returns to the previous mode. Emoji matches the typed
|
||||||
after use, and switching language during a search commits the shown
|
keys, and what they spell in the current mode, against a prefix of every
|
||||||
query. `Tab`/`Shift-Tab` wrap through the results, and a search with
|
emoji's CLDR name and keywords in English, Korean and Japanese, and against
|
||||||
nothing typed yet shows ☺ or 漢 in the popup. The Emoji search matches the
|
ASCII aliases such as `->` and `<=`. `Ctrl+H` takes the syllable being
|
||||||
typed keys, and what they spell in the current language, against a prefix
|
composed as its query and composes on from it, converting a word as well as
|
||||||
of every emoji's CLDR name and keywords in English, Korean, and Japanese,
|
a syllable — 한자 gives 漢字, 대한민국 gives 大韓民國 — and `Esc` gives the
|
||||||
and against ASCII symbol aliases such as `->` and `<=`. `Ctrl+H` takes the
|
syllable back. A word is committed a syllable at a time, so the reading
|
||||||
syllable being composed as its query and composes on from it; `Esc` gives
|
also reaches back into the text the application already holds, as far as
|
||||||
the syllable back. The Hanja dictionary converts a word as well as a
|
the dictionary knows the whole of it: type 한자 and then `Ctrl+H` and the
|
||||||
syllable, so `Ctrl+H` and then 한자 gives 漢字, and 대한민국 gives 大韓民國;
|
query is 한자, not 자, and picking 漢字 takes the 한 back. `Backspace`
|
||||||
a syllable already committed is the application's text, not the engine's,
|
gives that reach back before it deletes what was typed, so 상 written
|
||||||
and cannot be converted. Leaving the field or
|
with 태 typed answers 狀態, and one `Backspace` answers 態 for the
|
||||||
clicking elsewhere commits what is pending.
|
syllable alone. A reading is also the start of the longer words it
|
||||||
|
begins, whose conversions follow its own, so 대한 answers 大寒 first and
|
||||||
|
大韓民國 further down. A lone consonant is a reading too, and answers
|
||||||
|
with the symbol table a Korean keyboard's 한자 key has always offered:
|
||||||
|
ㅁ gives ※ ○ △ ㈜, ㄴ the brackets 「」『』, ㄹ the units ℃ ㎏ ℓ, ㅇ the
|
||||||
|
circled numbers ①②③.
|
||||||
|
|
||||||
|
Reaching back needs the application to hand over the text around its
|
||||||
|
cursor and to take some of it away again. The Wayland input method, IBus
|
||||||
|
and the GTK 3 module all can; XIM has no such request, so there the
|
||||||
|
reading is what is still being composed and nothing else, and an IBus
|
||||||
|
client that reads a key's effects only after the call returns is not
|
||||||
|
asked for its text either, since a deletion could not be ordered before
|
||||||
|
the commit.
|
||||||
|
|
||||||
|
Dead keys and Compose sequences are composed by strans itself for the
|
||||||
|
Wayland, XIM and IBus frontends, from `XCOMPOSEFILE` or the locale; the
|
||||||
|
GTK 3 module leaves them to GtkIMContextSimple.
|
||||||
|
|
||||||
## Preedit and candidates
|
## Preedit and candidates
|
||||||
|
|
||||||
| Frontend | Preedit | Candidates |
|
| Frontend | Preedit | Candidates | Reaches back |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| GTK 3 and IBus | inline in the client | strans popup |
|
| Wayland input method | inline in the client | popup | yes |
|
||||||
| XIM PreeditCallbacks | inline through XIM callbacks | strans popup |
|
| GTK 3 and IBus | inline in the client | popup | yes |
|
||||||
| XIM PreeditPosition | strans popup | strans popup |
|
| XIM PreeditCallbacks | inline through XIM callbacks | popup | no |
|
||||||
| XIM PreeditNothing | strans popup | strans popup |
|
| XIM PreeditPosition, PreeditNothing | popup | popup | no |
|
||||||
|
|
||||||
Each XIM style is offered with StatusNothing and with StatusNone.
|
On Wayland the popup is a surface the compositor places at the text cursor,
|
||||||
|
flipping it above the line when there is no room below. Elsewhere it is an
|
||||||
Dead keys and Compose sequences are composed by strans itself for the XIM
|
X11 window, placed from the XIM spot or from the GTK caret, and XIM text
|
||||||
and IBus frontends, from the table `XCOMPOSEFILE` or the locale names; the
|
travels as `COMPOUND_TEXT`. Either way `GDK_SCALE` sizes it for HiDPI
|
||||||
GTK 3 module leaves them to GtkIMContextSimple. Their text follows
|
displays.
|
||||||
whatever was pending.
|
|
||||||
|
|
||||||
XIM text travels as `COMPOUND_TEXT`, which carries any UTF-8. A client that
|
|
||||||
sends `XNSpotLocation` gets the popup under that spot whatever its preedit
|
|
||||||
style, and above the line when there is no room below; otherwise the popup
|
|
||||||
sits under the focus window, or under the client window when no focus
|
|
||||||
window is set.
|
|
||||||
|
|
||||||
The popup is an X11 window sized by `GDK_SCALE` on HiDPI displays, XIM is
|
|
||||||
X11-only, and GTK popup placement converts the GTK caret to X11 root
|
|
||||||
coordinates. There is no native Wayland popup surface or Wayland caret
|
|
||||||
positioning. GTK and IBus clients can still use inline preedit where their
|
|
||||||
display environment supports it.
|
|
||||||
|
|
||||||
## Build and test
|
## Build and test
|
||||||
|
|
||||||
Initialize the bundled test library, then build in the Docker environment:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
make docker-image
|
make docker-image
|
||||||
make docker-build
|
make docker-build
|
||||||
```
|
```
|
||||||
|
|
||||||
Build output:
|
That leaves `strans`, the daemon, and `gtk/im-strans.so`, the GTK 3 module.
|
||||||
|
Tests come in three tiers:
|
||||||
```text
|
|
||||||
strans daemon with IBus and XIM frontends
|
|
||||||
gtk/im-strans.so GTK 3 frontend module
|
|
||||||
```
|
|
||||||
|
|
||||||
Tests have three tiers:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make check # generated-map validation and focused unit tests
|
make check # generated-map validation and unit tests
|
||||||
make check-live # one IBus, GTK, XIM, and IPC daemon smoke
|
make check-live # one IBus, GTK, XIM and IPC daemon smoke each
|
||||||
make check-stress # randomized, capacity, collision, failure, and restart tests
|
make check-stress # randomized, capacity, collision, failure and restart
|
||||||
```
|
```
|
||||||
|
|
||||||
`UNITARGS` filters only the focused C unit suite, for example
|
`make docker-check` runs all three in the container, and
|
||||||
`make check UNITARGS=hangul`. It does not select or skip live and stress
|
`make docker-valgrind` the unit suite under Valgrind; rerun
|
||||||
tests. `make docker-check` rebuilds and runs all three tiers in the
|
`make docker-image` after changing `Dockerfile`. `UNITARGS=hangul` filters
|
||||||
container, and `make docker-valgrind` runs the unit suite under Valgrind
|
the unit suite alone, not the other tiers.
|
||||||
there; run `make docker-image` first after changing `Dockerfile`.
|
|
||||||
|
|
||||||
Native Linux source builds require a C toolchain, Make, and pkg-config, plus
|
A native build wants a C toolchain, Make, pkg-config and Plan 9 port, plus
|
||||||
development files for:
|
development files for D-Bus, XCB and xcb-imdkit, Wayland and
|
||||||
|
`wayland-scanner`, xkbcommon, Pango and Cairo, and GTK 3; the tests also
|
||||||
- Plan 9 port (`9c`, `9l`, `libthread`, and `libbio`);
|
want Xvfb, Python 3 and fonts covering Latin, CJK and emoji.
|
||||||
- D-Bus development files;
|
[`Dockerfile`](Dockerfile) names the exact packages.
|
||||||
- XCB, XCB RandR, XCB XKB, xcb-util, xcb-imdkit, xkbcommon, and xkbcommon-x11;
|
|
||||||
- Pango, PangoCairo, Cairo, and Fontconfig;
|
|
||||||
- GTK 3, plus libibus and Xlib for the live clients;
|
|
||||||
- Xvfb for the X11 live tests;
|
|
||||||
- Python 3 and `cmp` for `make check` and the map generators;
|
|
||||||
- a working `C.UTF-8` locale and fonts covering Latin, CJK, and emoji.
|
|
||||||
|
|
||||||
The package names used by the canonical Arch Linux image are listed in
|
|
||||||
[`Dockerfile`](Dockerfile).
|
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
The popup uses the normal system fonts found by Pango and Fontconfig. Run
|
|
||||||
it directly from the build tree:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./run.sh
|
./run.sh # restart the daemon in the background
|
||||||
# equivalently
|
./strans map # or run it in the foreground
|
||||||
./strans map
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`run.sh` stops every same-user process whose exact name is `strans`, starts one
|
`./strans DIR` reads `hira.map`, `kata.map`, `telex.map`, `kanji.dict`,
|
||||||
build-tree daemon in the background, and returns to the shell. It requires
|
`emoji.dict` and `hanja.dict` from `DIR` at runtime; nothing but the GTK
|
||||||
`pkill` from procps. Startup and frontend failures remain visible on the
|
module is installed. A service supervisor wants the session's
|
||||||
caller's standard error. A service supervisor should instead run
|
`XDG_RUNTIME_DIR` and its `WAYLAND_DISPLAY` or `DISPLAY`: the GTK module
|
||||||
`./strans map` directly in the foreground, with the session's
|
finds the daemon at `$XDG_RUNTIME_DIR/strans.sock`, else
|
||||||
`XDG_RUNTIME_DIR` and `DISPLAY`: the GTK module finds the daemon at
|
`/tmp/strans.UID`, and IBus clients through the address file libibus
|
||||||
`$XDG_RUNTIME_DIR/strans.sock` (else `/tmp/strans.UID`), and IBus clients
|
expects under `~/.config/ibus/bus/`, which strans writes as `ibus-daemon`
|
||||||
through the address file libibus expects under `~/.config/ibus/bus/`,
|
would.
|
||||||
which strans writes as `ibus-daemon` would, named after `WAYLAND_DISPLAY`
|
|
||||||
when there is one and `DISPLAY` otherwise.
|
|
||||||
|
|
||||||
The build does not copy or install the daemon or its data. `./strans DIR`
|
strans shows one popup per session, so it picks a frontend at startup: a
|
||||||
opens `hira.map`, `kata.map`, `telex.map`, `kanji.dict`, `emoji.dict`, and
|
compositor offering `zwp_input_method_v2` gets the Wayland frontend, and
|
||||||
`hanja.dict` from `DIR` at runtime; `run.sh` passes the tracked `map/`
|
then neither XIM nor the X11 popup runs; every other session gets XIM. The
|
||||||
directory. Only the GTK subdirectory has install and uninstall targets.
|
IBus endpoint and the GTK 3 module are served in both.
|
||||||
|
|
||||||
Configure clients as needed:
|
On Wayland, then, set none of the variables below. GTK, Qt and Firefox
|
||||||
|
speak text-input-v3 themselves — GTK 4 binds it with `GTK_IM_MODULE` unset
|
||||||
|
— and setting these is what pushes them off the path that works: such a
|
||||||
|
client still composes inline but shows no candidates, because the popup
|
||||||
|
belongs to the Wayland frontend. Chromium is beyond help either way, since
|
||||||
|
it asks for `text-input-v1`, which wlroots does not implement.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# XIM
|
export XMODIFIERS=@im=strans # XIM
|
||||||
export XMODIFIERS=@im=strans
|
doas make -C gtk install # GTK 3 module
|
||||||
|
|
||||||
# GTK 3 module
|
|
||||||
doas make -C gtk install
|
|
||||||
export GTK_IM_MODULE=strans
|
export GTK_IM_MODULE=strans
|
||||||
|
GLFW_IM_MODULE=ibus kitty # IBus client example
|
||||||
# IBus client example
|
|
||||||
GLFW_IM_MODULE=ibus kitty
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Building `gtk/im-strans.so` requires GTK development files; installing one
|
strans provides its own IBus endpoint, so `ibus-daemon` and fcitx are not
|
||||||
built elsewhere, in the container for instance, does not. The install
|
needed. Building the GTK module wants GTK development files; installing
|
||||||
target uses `GTK_MODULE_DIR` when it is set, else GTK's pkg-config
|
one built elsewhere does not. The install target takes `GTK_MODULE_DIR`
|
||||||
variables, else the directory the installed `gtk-query-immodules-3.0`
|
when set, else asks GTK where its modules live; `doas make -C gtk uninstall`
|
||||||
reports. Packagers and cross builds should set `GTK_MODULE_DIR`, and a
|
removes it. With neither `WAYLAND_DISPLAY` nor `DISPLAY` the daemon still
|
||||||
prebuilt module must still match the target architecture and library ABI.
|
serves IBus and its socket, but nothing draws a popup.
|
||||||
|
|
||||||
`DESTDIR` staging is supported and deliberately skips the host module-cache
|
|
||||||
update. A native install needs the GTK runtime query utility so it can refresh
|
|
||||||
that cache. Remove a native installation with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
doas make -C gtk uninstall
|
|
||||||
```
|
|
||||||
|
|
||||||
strans provides its own IBus endpoint; `ibus-daemon` and fcitx are not
|
|
||||||
required. IBus clients that process keys synchronously — GTK 4, or GTK 3
|
|
||||||
with `IBUS_ENABLE_SYNC_MODE=1` — get their commits and preedits through
|
|
||||||
`PostProcessKeyEvent`, in order with the key. Without `DISPLAY`, the daemon and IBus frontend still work, but XIM
|
|
||||||
is not started and the popup is disabled.
|
|
||||||
|
|
||||||
## Dictionary data
|
## Dictionary data
|
||||||
|
|
||||||
After changing an input source, regenerate and verify the maps and
|
After changing an input source, regenerate and verify:
|
||||||
dictionaries:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
python3 map/mktelex.py >map/telex.map
|
python3 map/mktelex.py >map/telex.map
|
||||||
@@ -216,17 +179,16 @@ map/mkhanja >map/hanja.dict
|
|||||||
make verify-map
|
make verify-map
|
||||||
```
|
```
|
||||||
|
|
||||||
See [`map/README`](map/README) for the emoji, Hanja, and Japanese data and
|
[`map/README`](map/README) says where the emoji, Hanja and Japanese data
|
||||||
where it comes from.
|
comes from.
|
||||||
|
|
||||||
## Benchmark
|
## Benchmark
|
||||||
|
|
||||||
`make bench` builds `bench/bench`. With the daemon stopped, `./bench.sh` starts
|
`make bench` builds `bench/bench`. With the daemon stopped, `./bench.sh`
|
||||||
one build-tree instance, warms it up, and records the workload with Linux
|
starts one, warms it up and records the workload with Linux `perf` into
|
||||||
`perf`. It requires `perf` and permission to profile the daemon, and writes
|
|
||||||
`bench/perf.data`.
|
`bench/perf.data`.
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
Third-party license notices are in [`LICENSES`](LICENSES). The repository does
|
Third-party notices are in [`LICENSES`](LICENSES). The repository declares
|
||||||
not currently declare a license for the strans source as a whole.
|
no license for the strans source as a whole.
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ iterations(char *s)
|
|||||||
goto Bad;
|
goto Bad;
|
||||||
errno = 0;
|
errno = 0;
|
||||||
n = strtoumax(s, &end, 10);
|
n = strtoumax(s, &end, 10);
|
||||||
if(errno != 0 || *end != '\0' || n == 0 || n > UINT64_MAX)
|
if(errno != 0 || *end != '\0' || n == 0)
|
||||||
goto Bad;
|
goto Bad;
|
||||||
return n;
|
return n;
|
||||||
Bad:
|
Bad:
|
||||||
@@ -157,7 +157,7 @@ main(int argc, char **argv)
|
|||||||
|
|
||||||
loadkeys(argv[1]);
|
loadkeys(argv[1]);
|
||||||
niter = argc == 3 ? iterations(argv[2]) : 1000;
|
niter = argc == 3 ? iterations(argv[2]) : 1000;
|
||||||
if(nkeys > UINT64_MAX || niter > UINT64_MAX / nkeys){
|
if(niter > UINT64_MAX / nkeys){
|
||||||
fprintf(stderr, "iteration count overflows key total\n");
|
fprintf(stderr, "iteration count overflows key total\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
44
compose.c
44
compose.c
@@ -5,7 +5,13 @@
|
|||||||
#include <xkbcommon/xkbcommon.h>
|
#include <xkbcommon/xkbcommon.h>
|
||||||
#include <xkbcommon/xkbcommon-compose.h>
|
#include <xkbcommon/xkbcommon-compose.h>
|
||||||
|
|
||||||
static struct xkb_compose_state *cstate;
|
/*
|
||||||
|
* One state per frontend, all made before the procs are: xkbcommon locks
|
||||||
|
* nothing, and even its table refcount is a plain increment. cowner is
|
||||||
|
* the context whose sequence a state holds.
|
||||||
|
*/
|
||||||
|
static struct xkb_compose_state *cstate[Ncompose];
|
||||||
|
static void *cowner[Ncompose];
|
||||||
|
|
||||||
/* Dead keys and Compose: no frontend's client does them for us. */
|
/* Dead keys and Compose: no frontend's client does them for us. */
|
||||||
void
|
void
|
||||||
@@ -14,6 +20,7 @@ composeinit(void)
|
|||||||
struct xkb_context *ctx;
|
struct xkb_context *ctx;
|
||||||
struct xkb_compose_table *table;
|
struct xkb_compose_table *table;
|
||||||
char *locale;
|
char *locale;
|
||||||
|
int i;
|
||||||
|
|
||||||
locale = setlocale(LC_CTYPE, "");
|
locale = setlocale(LC_CTYPE, "");
|
||||||
ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||||
@@ -22,30 +29,51 @@ composeinit(void)
|
|||||||
table = xkb_compose_table_new_from_locale(ctx,
|
table = xkb_compose_table_new_from_locale(ctx,
|
||||||
locale != nil ? locale : "C", XKB_COMPOSE_COMPILE_NO_FLAGS);
|
locale != nil ? locale : "C", XKB_COMPOSE_COMPILE_NO_FLAGS);
|
||||||
if(table != nil)
|
if(table != nil)
|
||||||
cstate = xkb_compose_state_new(table, XKB_COMPOSE_STATE_NO_FLAGS);
|
for(i = 0; i < Ncompose; i++)
|
||||||
|
cstate[i] = xkb_compose_state_new(table,
|
||||||
|
XKB_COMPOSE_STATE_NO_FLAGS);
|
||||||
xkb_compose_table_unref(table);
|
xkb_compose_table_unref(table);
|
||||||
xkb_context_unref(ctx);
|
xkb_context_unref(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The context is going, and the sequence it left half typed with it. */
|
||||||
|
void
|
||||||
|
composedrop(int who, void *owner)
|
||||||
|
{
|
||||||
|
if(cstate[who] == nil || owner != cowner[who])
|
||||||
|
return;
|
||||||
|
xkb_compose_state_reset(cstate[who]);
|
||||||
|
cowner[who] = nil;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Feeds sym to the Compose table. Returns 1 while a sequence is
|
* Feeds sym to the Compose table. Returns 1 while a sequence is
|
||||||
* unfinished: the key belongs to the sequence, not to the engine. A
|
* unfinished: the key belongs to the sequence, not to the engine. A
|
||||||
* finished sequence leaves its text in buf, which the caller commits
|
* finished sequence leaves its text in buf, which the caller commits
|
||||||
* after whatever the engine had pending.
|
* after whatever the engine had pending. A sequence belongs to the
|
||||||
|
* context that started it: a key from another one starts over.
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
composekey(u32int sym, char *buf, int n)
|
composekey(int who, void *owner, u32int sym, char *buf, int n)
|
||||||
{
|
{
|
||||||
|
struct xkb_compose_state *cs;
|
||||||
|
|
||||||
buf[0] = '\0';
|
buf[0] = '\0';
|
||||||
if(cstate == nil ||
|
cs = cstate[who];
|
||||||
xkb_compose_state_feed(cstate, sym) != XKB_COMPOSE_FEED_ACCEPTED)
|
if(cs == nil)
|
||||||
return 0;
|
return 0;
|
||||||
switch(xkb_compose_state_get_status(cstate)){
|
if(owner != cowner[who]){
|
||||||
|
xkb_compose_state_reset(cs);
|
||||||
|
cowner[who] = owner;
|
||||||
|
}
|
||||||
|
if(xkb_compose_state_feed(cs, sym) != XKB_COMPOSE_FEED_ACCEPTED)
|
||||||
|
return 0;
|
||||||
|
switch(xkb_compose_state_get_status(cs)){
|
||||||
case XKB_COMPOSE_COMPOSING:
|
case XKB_COMPOSE_COMPOSING:
|
||||||
case XKB_COMPOSE_CANCELLED:
|
case XKB_COMPOSE_CANCELLED:
|
||||||
return 1;
|
return 1;
|
||||||
case XKB_COMPOSE_COMPOSED:
|
case XKB_COMPOSE_COMPOSED:
|
||||||
xkb_compose_state_get_utf8(cstate, buf, n);
|
xkb_compose_state_get_utf8(cs, buf, n);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
23
dat.h
23
dat.h
@@ -7,6 +7,13 @@
|
|||||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* plan9port runs the note handlers for a note it goes on to ignore, and
|
||||||
|
* the process lives through it, so what strans announced must outlive
|
||||||
|
* such a note. A daemon that forks nothing gets only the one.
|
||||||
|
*/
|
||||||
|
#define notefatal(note) (strcmp(note, "sys: write on closed pipe") != 0)
|
||||||
|
|
||||||
/* A language id is the control code of the Ctrl key that selects it. */
|
/* A language id is the control code of the Ctrl key that selects it. */
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
@@ -29,6 +36,11 @@ enum
|
|||||||
Maxkouho = 128,
|
Maxkouho = 128,
|
||||||
Maxdisp = 9,
|
Maxdisp = 9,
|
||||||
|
|
||||||
|
/* Content purposes whose keys the engine never sees; IBus and
|
||||||
|
* text-input-v3 number them alike, both after GTK. */
|
||||||
|
Purposepassword = 8,
|
||||||
|
Purposepin = 9,
|
||||||
|
|
||||||
Colfg = 0x000000,
|
Colfg = 0x000000,
|
||||||
Colbg = 0xffffff,
|
Colbg = 0xffffff,
|
||||||
Colsep = 0xd0d0d0,
|
Colsep = 0xd0d0d0,
|
||||||
@@ -36,6 +48,15 @@ enum
|
|||||||
Colselfg = 0xffffff,
|
Colselfg = 0xffffff,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Each frontend composes on its own: they run in different procs. */
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Composeibus,
|
||||||
|
Composexim,
|
||||||
|
Composewl,
|
||||||
|
Ncompose,
|
||||||
|
};
|
||||||
|
|
||||||
/* Popup metrics in pixels; popupscale is GDK_SCALE, for HiDPI. */
|
/* Popup metrics in pixels; popupscale is GDK_SCALE, for HiDPI. */
|
||||||
extern int popupscale;
|
extern int popupscale;
|
||||||
#define Fontsz (32*popupscale)
|
#define Fontsz (32*popupscale)
|
||||||
@@ -186,6 +207,7 @@ enum
|
|||||||
struct Keyres
|
struct Keyres
|
||||||
{
|
{
|
||||||
int eaten; /* Keycap: the caller is the owner and clientpre took. */
|
int eaten; /* Keycap: the caller is the owner and clientpre took. */
|
||||||
|
int del; /* runes of the client's own text to take back first */
|
||||||
Str commit;
|
Str commit;
|
||||||
Str preedit;
|
Str preedit;
|
||||||
};
|
};
|
||||||
@@ -198,6 +220,7 @@ struct Keyreq
|
|||||||
u32int ks;
|
u32int ks;
|
||||||
u32int mod;
|
u32int mod;
|
||||||
Caret caret;
|
Caret caret;
|
||||||
|
Str surround; /* the client's own text just before the cursor */
|
||||||
Channel *reply;
|
Channel *reply;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
6
fn.h
6
fn.h
@@ -1,6 +1,7 @@
|
|||||||
void die(char*, ...);
|
void die(char*, ...);
|
||||||
|
|
||||||
int sinit(Str*, char*, int);
|
int sinit(Str*, char*, int);
|
||||||
|
void stail(Str*, char*, int);
|
||||||
void sclear(Str*);
|
void sclear(Str*);
|
||||||
void sputr(Str*, Rune);
|
void sputr(Str*, Rune);
|
||||||
void spopr(Str*);
|
void spopr(Str*);
|
||||||
@@ -43,10 +44,13 @@ void srvinit(void);
|
|||||||
void srvthread(void*);
|
void srvthread(void*);
|
||||||
void ibusthread(void*);
|
void ibusthread(void*);
|
||||||
void ximthread(void*);
|
void ximthread(void*);
|
||||||
|
int wlinit(void);
|
||||||
|
void wlthread(void*);
|
||||||
int keymeaningful(u32int);
|
int keymeaningful(u32int);
|
||||||
|
|
||||||
void composeinit(void);
|
void composeinit(void);
|
||||||
int composekey(u32int, char*, int);
|
void composedrop(int, void*);
|
||||||
|
int composekey(int, void*, u32int, char*, int);
|
||||||
|
|
||||||
void* emalloc(ulong);
|
void* emalloc(ulong);
|
||||||
void* erealloc(void*, ulong);
|
void* erealloc(void*, ulong);
|
||||||
|
|||||||
77
gtk/main.c
77
gtk/main.c
@@ -24,6 +24,9 @@ struct Im
|
|||||||
int cursorvalid;
|
int cursorvalid;
|
||||||
int caretsent;
|
int caretsent;
|
||||||
unsigned char sent[Ipccaretsz];
|
unsigned char sent[Ipccaretsz];
|
||||||
|
char surround[Ipcfieldmax];
|
||||||
|
int nsurround;
|
||||||
|
int surroundsent;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct ImClass ImClass;
|
typedef struct ImClass ImClass;
|
||||||
@@ -65,6 +68,7 @@ srvdrop(Im *im, int notify)
|
|||||||
im->fd = -1;
|
im->fd = -1;
|
||||||
im->ext = 0;
|
im->ext = 0;
|
||||||
im->caretsent = 0;
|
im->caretsent = 0;
|
||||||
|
im->surroundsent = 0;
|
||||||
if(notify)
|
if(notify)
|
||||||
setpreedit(im, "", 0);
|
setpreedit(im, "", 0);
|
||||||
else{
|
else{
|
||||||
@@ -157,6 +161,58 @@ sendcaret(Im *im)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The text just before the cursor, as much of it as a reading can use,
|
||||||
|
* cut on a rune boundary. A widget that keeps none leaves it empty, and
|
||||||
|
* then the daemon never asks for any of it back.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
readsurround(Im *im, GtkIMContext *ctx)
|
||||||
|
{
|
||||||
|
char *text, *p;
|
||||||
|
gint cursor;
|
||||||
|
int start;
|
||||||
|
|
||||||
|
im->nsurround = 0;
|
||||||
|
if(!gtk_im_context_get_surrounding(ctx, &text, &cursor))
|
||||||
|
return;
|
||||||
|
if(cursor < 0 || cursor > (gint)strlen(text)){
|
||||||
|
g_free(text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
start = cursor > Ipcfieldmax ? cursor - Ipcfieldmax : 0;
|
||||||
|
for(p = text + start; p < text + cursor && (*p & 0xc0) == 0x80; p++)
|
||||||
|
;
|
||||||
|
im->nsurround = text + cursor - p;
|
||||||
|
memcpy(im->surround, p, im->nsurround);
|
||||||
|
g_free(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sent only when it changes, as the caret is. */
|
||||||
|
static int
|
||||||
|
sendsurround(Im *im, GtkIMContext *ctx)
|
||||||
|
{
|
||||||
|
unsigned char hdr[Ipcreqsz];
|
||||||
|
char was[Ipcfieldmax];
|
||||||
|
int nwas;
|
||||||
|
|
||||||
|
if(im->fd < 0 || !im->ext)
|
||||||
|
return 0;
|
||||||
|
nwas = im->nsurround;
|
||||||
|
memcpy(was, im->surround, nwas);
|
||||||
|
readsurround(im, ctx);
|
||||||
|
if(im->surroundsent && nwas == im->nsurround &&
|
||||||
|
memcmp(was, im->surround, nwas) == 0)
|
||||||
|
return 0;
|
||||||
|
ipcpacksurround(hdr, im->nsurround);
|
||||||
|
if(ipcsend(im->fd, hdr, sizeof hdr) < 0 ||
|
||||||
|
(im->nsurround > 0 &&
|
||||||
|
ipcsend(im->fd, im->surround, im->nsurround) < 0))
|
||||||
|
return -1;
|
||||||
|
im->surroundsent = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
srvconnect(Im *im)
|
srvconnect(Im *im)
|
||||||
{
|
{
|
||||||
@@ -243,6 +299,18 @@ sendreset(Im *im)
|
|||||||
g_signal_emit_by_name(im, "commit", commit);
|
g_signal_emit_by_name(im, "commit", commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A password entry hidden the older way sets no purpose and signals nothing. */
|
||||||
|
static int
|
||||||
|
ishidden(Im *im)
|
||||||
|
{
|
||||||
|
GtkWidget *w;
|
||||||
|
|
||||||
|
if(im->win == NULL)
|
||||||
|
return 0;
|
||||||
|
gdk_window_get_user_data(im->win, (gpointer*)&w);
|
||||||
|
return GTK_IS_ENTRY(w) && !gtk_entry_get_visibility(GTK_ENTRY(w));
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
||||||
{
|
{
|
||||||
@@ -264,7 +332,7 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
|||||||
return simplefilter(ctx, ev, 0);
|
return simplefilter(ctx, ev, 0);
|
||||||
key = ipckeysym(ev->keyval, gdk_keyval_to_unicode(ev->keyval));
|
key = ipckeysym(ev->keyval, gdk_keyval_to_unicode(ev->keyval));
|
||||||
mod = ipcmod(ev->state);
|
mod = ipcmod(ev->state);
|
||||||
if(im->private || key == 0){
|
if(im->private || ishidden(im) || key == 0){
|
||||||
r = simplefilter(ctx, ev, 0);
|
r = simplefilter(ctx, ev, 0);
|
||||||
/* A dead key starts a compose: pending text was typed first. */
|
/* A dead key starts a compose: pending text was typed first. */
|
||||||
if(im->simpleactive)
|
if(im->simpleactive)
|
||||||
@@ -274,7 +342,7 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
|||||||
if(srvconnect(im) < 0)
|
if(srvconnect(im) < 0)
|
||||||
return simplefilter(ctx, ev, 0);
|
return simplefilter(ctx, ev, 0);
|
||||||
/* A retained GDK window may have moved since the last cursor report. */
|
/* A retained GDK window may have moved since the last cursor report. */
|
||||||
if(sendcaret(im) < 0){
|
if(sendcaret(im) < 0 || sendsurround(im, ctx) < 0){
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
return simplefilter(ctx, ev, 0);
|
return simplefilter(ctx, ev, 0);
|
||||||
}
|
}
|
||||||
@@ -286,6 +354,11 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
|||||||
}
|
}
|
||||||
if(im->usepreedit && commit[0] != '\0' && im->prelen > 0)
|
if(im->usepreedit && commit[0] != '\0' && im->prelen > 0)
|
||||||
setpreedit(im, "", 0);
|
setpreedit(im, "", 0);
|
||||||
|
/* The reading reached into the widget's own text: give it back. */
|
||||||
|
if(resp.del > 0){
|
||||||
|
gtk_im_context_delete_surrounding(ctx, -resp.del, resp.del);
|
||||||
|
im->surroundsent = 0;
|
||||||
|
}
|
||||||
if(commit[0] != '\0')
|
if(commit[0] != '\0')
|
||||||
g_signal_emit_by_name(ctx, "commit", commit);
|
g_signal_emit_by_name(ctx, "commit", commit);
|
||||||
if(im->usepreedit)
|
if(im->usepreedit)
|
||||||
|
|||||||
110
ibus.c
110
ibus.c
@@ -16,8 +16,7 @@ enum
|
|||||||
Relmask = 1<<30,
|
Relmask = 1<<30,
|
||||||
/* IBus wire constants; the daemon does not link libibus. */
|
/* IBus wire constants; the daemon does not link libibus. */
|
||||||
Ibuscappreedit = 1<<0,
|
Ibuscappreedit = 1<<0,
|
||||||
Ibuspurposepassword = 8,
|
Ibuscapsurround = 1<<5,
|
||||||
Ibuspurposepin = 9,
|
|
||||||
Ibusattrunderline = 1,
|
Ibusattrunderline = 1,
|
||||||
Ibusunderlinesingle = 1,
|
Ibusunderlinesingle = 1,
|
||||||
Ibuspreeditclear = 0,
|
Ibuspreeditclear = 0,
|
||||||
@@ -53,6 +52,7 @@ struct Ictx
|
|||||||
int npost;
|
int npost;
|
||||||
Post post[Maxpost];
|
Post post[Maxpost];
|
||||||
Caret caret;
|
Caret caret;
|
||||||
|
Str surround; /* the client's own text just before the cursor */
|
||||||
};
|
};
|
||||||
|
|
||||||
static DBusWatch *watches[Maxwatches];
|
static DBusWatch *watches[Maxwatches];
|
||||||
@@ -85,8 +85,9 @@ unlinkaddr(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
addrnote(void*, char*)
|
addrnote(void*, char *note)
|
||||||
{
|
{
|
||||||
|
if(notefatal(note))
|
||||||
unlinkaddr();
|
unlinkaddr();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -305,6 +306,11 @@ sendrequest(Ictx *ctx, int op, u32int ks, u32int mod, Keyres *res)
|
|||||||
kr.ks = ks;
|
kr.ks = ks;
|
||||||
kr.mod = mod;
|
kr.mod = mod;
|
||||||
kr.caret = ctx->caret;
|
kr.caret = ctx->caret;
|
||||||
|
/* A client that reads the key's effects back afterwards takes a
|
||||||
|
* DeleteSurroundingText signal after them, which is too late to be
|
||||||
|
* a taking back: it is never asked for text it cannot lose. */
|
||||||
|
if(!ctx->postprocess)
|
||||||
|
kr.surround = ctx->surround;
|
||||||
kr.reply = replyc;
|
kr.reply = replyc;
|
||||||
chansend(keyc, &kr);
|
chansend(keyc, &kr);
|
||||||
chanrecv(replyc, res);
|
chanrecv(replyc, res);
|
||||||
@@ -313,8 +319,8 @@ sendrequest(Ictx *ctx, int op, u32int ks, u32int mod, Keyres *res)
|
|||||||
static int
|
static int
|
||||||
hidden(Ictx *ctx)
|
hidden(Ictx *ctx)
|
||||||
{
|
{
|
||||||
return ctx->purpose == Ibuspurposepassword ||
|
return ctx->purpose == Purposepassword ||
|
||||||
ctx->purpose == Ibuspurposepin;
|
ctx->purpose == Purposepin;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -465,6 +471,34 @@ emitpreedit(Ictx *ctx, const char *text)
|
|||||||
preowner = nil;
|
preowner = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The client is to take back the runes the reading reached into. */
|
||||||
|
static void
|
||||||
|
emitdelete(Ictx *ctx, int n)
|
||||||
|
{
|
||||||
|
DBusMessage *sig;
|
||||||
|
dbus_int32_t off;
|
||||||
|
dbus_uint32_t nrune;
|
||||||
|
|
||||||
|
off = -n;
|
||||||
|
nrune = n;
|
||||||
|
sig = newsignal(ctx->path, "DeleteSurroundingText");
|
||||||
|
dbus_message_append_args(sig, DBUS_TYPE_INT32, &off,
|
||||||
|
DBUS_TYPE_UINT32, &nrune, DBUS_TYPE_INVALID);
|
||||||
|
dbus_connection_send(ctx->conn, sig, nil);
|
||||||
|
dbus_message_unref(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nothing arrives until the client is asked to send it. */
|
||||||
|
static void
|
||||||
|
emitrequiresurround(Ictx *ctx)
|
||||||
|
{
|
||||||
|
DBusMessage *sig;
|
||||||
|
|
||||||
|
sig = newsignal(ctx->path, "RequireSurroundingText");
|
||||||
|
dbus_connection_send(ctx->conn, sig, nil);
|
||||||
|
dbus_message_unref(sig);
|
||||||
|
}
|
||||||
|
|
||||||
/* Another frontend may have taken the engine; then our preedit is stale. */
|
/* Another frontend may have taken the engine; then our preedit is stale. */
|
||||||
static void
|
static void
|
||||||
checkpreowner(void)
|
checkpreowner(void)
|
||||||
@@ -511,6 +545,7 @@ static void
|
|||||||
dropcontext(Ictx *ctx)
|
dropcontext(Ictx *ctx)
|
||||||
{
|
{
|
||||||
releasecontext(ctx);
|
releasecontext(ctx);
|
||||||
|
composedrop(Composeibus, ctx);
|
||||||
if(preowner == ctx)
|
if(preowner == ctx)
|
||||||
preowner = nil;
|
preowner = nil;
|
||||||
memset(ctx, 0, sizeof *ctx);
|
memset(ctx, 0, sizeof *ctx);
|
||||||
@@ -538,7 +573,7 @@ processkey(Ictx *ctx, u32int sym, u32int state, char *text, int n, Keyres *res)
|
|||||||
return 0;
|
return 0;
|
||||||
ctx->focused = 1;
|
ctx->focused = 1;
|
||||||
/* An IBus client drops a dead key we do not take: compose it here. */
|
/* An IBus client drops a dead key we do not take: compose it here. */
|
||||||
if(composekey(sym, text, n))
|
if(composekey(Composeibus, ctx, sym, text, n))
|
||||||
return -1;
|
return -1;
|
||||||
if(text[0] != '\0')
|
if(text[0] != '\0')
|
||||||
/* Composed text follows whatever was pending. */
|
/* Composed text follows whatever was pending. */
|
||||||
@@ -665,6 +700,8 @@ handlekey(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|||||||
ctx->keying = 1;
|
ctx->keying = 1;
|
||||||
if(restart)
|
if(restart)
|
||||||
emitpreedit(ctx, "");
|
emitpreedit(ctx, "");
|
||||||
|
if(res.del > 0)
|
||||||
|
emitdelete(ctx, res.del);
|
||||||
if(commit[0] != '\0')
|
if(commit[0] != '\0')
|
||||||
emitcommit(ctx, commit);
|
emitcommit(ctx, commit);
|
||||||
if(text[0] != '\0')
|
if(text[0] != '\0')
|
||||||
@@ -732,6 +769,7 @@ handlefocusout(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|||||||
flushcontext(ctx, Keyrelease);
|
flushcontext(ctx, Keyrelease);
|
||||||
ctx->focused = 0;
|
ctx->focused = 0;
|
||||||
memset(&ctx->caret, 0, sizeof ctx->caret);
|
memset(&ctx->caret, 0, sizeof ctx->caret);
|
||||||
|
sclear(&ctx->surround);
|
||||||
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,6 +784,8 @@ handlecap(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|||||||
dbus_message_get_args(m, nil, DBUS_TYPE_UINT32, &cap, DBUS_TYPE_INVALID);
|
dbus_message_get_args(m, nil, DBUS_TYPE_UINT32, &cap, DBUS_TYPE_INVALID);
|
||||||
old = ctx->cap;
|
old = ctx->cap;
|
||||||
ctx->cap = cap;
|
ctx->cap = cap;
|
||||||
|
if((cap & ~old) & Ibuscapsurround)
|
||||||
|
emitrequiresurround(ctx);
|
||||||
if(ctx->focused){
|
if(ctx->focused){
|
||||||
sendrequest(ctx, Keycap, 0, 0, &res);
|
sendrequest(ctx, Keycap, 0, 0, &res);
|
||||||
if((old & Ibuscappreedit) != (ctx->cap & Ibuscappreedit)){
|
if((old & Ibuscappreedit) != (ctx->cap & Ibuscappreedit)){
|
||||||
@@ -759,6 +799,57 @@ handlecap(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|||||||
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* IBusText is (sa{sv}sv); the string in it is all we want. */
|
||||||
|
static int
|
||||||
|
getibustext(DBusMessageIter *value, const char **s)
|
||||||
|
{
|
||||||
|
DBusMessageIter st;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if(dbus_message_iter_get_arg_type(value) != DBUS_TYPE_STRUCT)
|
||||||
|
return 0;
|
||||||
|
dbus_message_iter_recurse(value, &st);
|
||||||
|
for(i = 0; i < 2; i++)
|
||||||
|
if(!dbus_message_iter_next(&st))
|
||||||
|
return 0;
|
||||||
|
if(dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_STRING)
|
||||||
|
return 0;
|
||||||
|
dbus_message_iter_get_basic(&st, s);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The bytes the first n runes of s take; IBus counts a cursor in runes. */
|
||||||
|
static int
|
||||||
|
runebytes(const char *s, int n)
|
||||||
|
{
|
||||||
|
Rune r;
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
for(p = (char*)s; n > 0 && *p != '\0'; n--)
|
||||||
|
p += chartorune(&r, p);
|
||||||
|
return p - s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The text around the cursor; a reading can use what comes before it. */
|
||||||
|
static DBusHandlerResult
|
||||||
|
handlesurround(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
||||||
|
{
|
||||||
|
DBusMessageIter it, v;
|
||||||
|
dbus_uint32_t cursor;
|
||||||
|
const char *s;
|
||||||
|
|
||||||
|
if(!dbus_message_iter_init(m, &it))
|
||||||
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
||||||
|
"SetSurroundingText expects text, cursor, and anchor");
|
||||||
|
dbus_message_iter_recurse(&it, &v);
|
||||||
|
if(!getibustext(&v, &s) || !dbus_message_iter_next(&it))
|
||||||
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
||||||
|
"SetSurroundingText expects an IBusText");
|
||||||
|
dbus_message_iter_get_basic(&it, &cursor);
|
||||||
|
stail(&ctx->surround, (char*)s, runebytes(s, cursor));
|
||||||
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
getproperty(DBusMessage *m, const char **iface, const char **name,
|
getproperty(DBusMessage *m, const char **iface, const char **name,
|
||||||
DBusMessageIter *value)
|
DBusMessageIter *value)
|
||||||
@@ -889,7 +980,10 @@ handlecursor(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|||||||
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SetEngine: there is one engine, so any name is fine. */
|
/*
|
||||||
|
* Accepted and thrown away: there is one engine, so any name is fine,
|
||||||
|
* and a relative cursor location is surface local, not a caret.
|
||||||
|
*/
|
||||||
static DBusHandlerResult
|
static DBusHandlerResult
|
||||||
handleok(DBusConnection *c, DBusMessage *m, Ictx*)
|
handleok(DBusConnection *c, DBusMessage *m, Ictx*)
|
||||||
{
|
{
|
||||||
@@ -908,7 +1002,9 @@ static struct {
|
|||||||
{"Reset", "", handlereset},
|
{"Reset", "", handlereset},
|
||||||
{"Destroy", "", handledestroy},
|
{"Destroy", "", handledestroy},
|
||||||
{"SetCursorLocation", "iiii", handlecursor},
|
{"SetCursorLocation", "iiii", handlecursor},
|
||||||
|
{"SetCursorLocationRelative", "iiii", handleok},
|
||||||
{"SetCapabilities", "u", handlecap},
|
{"SetCapabilities", "u", handlecap},
|
||||||
|
{"SetSurroundingText", "vuu", handlesurround},
|
||||||
{"SetEngine", "s", handleok},
|
{"SetEngine", "s", handleok},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
38
ipc.c
38
ipc.c
@@ -252,6 +252,22 @@ ipcpackcaret(unsigned char req[Ipccaretsz], int valid, int32_t x,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ipcpacksurround(unsigned char req[Ipcreqsz], size_t n)
|
||||||
|
{
|
||||||
|
memset(req, 0, Ipcreqsz);
|
||||||
|
req[0] = Ipcext;
|
||||||
|
req[1] = Ipcversion;
|
||||||
|
req[2] = Ipcopsurround;
|
||||||
|
putlen(req + 4, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
ipcsurroundlen(const unsigned char req[Ipcreqsz])
|
||||||
|
{
|
||||||
|
return getlen(req + 4);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
||||||
uint32_t *key)
|
uint32_t *key)
|
||||||
@@ -281,6 +297,11 @@ ipcreqtype(const unsigned char req[Ipcreqsz])
|
|||||||
if(req[0] != Ipcext || req[3] > 1)
|
if(req[0] != Ipcext || req[3] > 1)
|
||||||
return Ipcunknown;
|
return Ipcunknown;
|
||||||
return Ipccaret;
|
return Ipccaret;
|
||||||
|
case Ipcopsurround:
|
||||||
|
if(req[0] != Ipcext || req[3] != 0 ||
|
||||||
|
getlen(req + 4) > Ipcfieldmax)
|
||||||
|
return Ipcunknown;
|
||||||
|
return Ipcsurround;
|
||||||
}
|
}
|
||||||
return Ipcunknown;
|
return Ipcunknown;
|
||||||
}
|
}
|
||||||
@@ -305,19 +326,21 @@ ipcreqreset(const unsigned char req[Ipcreqsz])
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
ipcpackresp(unsigned char *dst, size_t cap, int eaten,
|
ipcpackresp(unsigned char *dst, size_t cap, int eaten, int del,
|
||||||
const char *commit, size_t ncommit, const char *preedit, size_t npreedit,
|
const char *commit, size_t ncommit, const char *preedit, size_t npreedit,
|
||||||
int want)
|
int want)
|
||||||
{
|
{
|
||||||
size_t n;
|
size_t n;
|
||||||
|
|
||||||
if(ncommit > Ipcfieldmax || npreedit > Ipcfieldmax)
|
if(ncommit > Ipcfieldmax || npreedit > Ipcfieldmax ||
|
||||||
|
del < 0 || del > 0xff)
|
||||||
return -1;
|
return -1;
|
||||||
n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0);
|
n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0);
|
||||||
if(cap < n)
|
if(cap < n)
|
||||||
return -1;
|
return -1;
|
||||||
dst[0] = eaten != 0;
|
dst[0] = eaten != 0;
|
||||||
putlen(dst + 1, ncommit);
|
dst[1] = del;
|
||||||
|
putlen(dst + 2, ncommit);
|
||||||
memcpy(dst + Ipcresphdrsz, commit, ncommit);
|
memcpy(dst + Ipcresphdrsz, commit, ncommit);
|
||||||
if(want){
|
if(want){
|
||||||
putlen(dst + Ipcresphdrsz + ncommit, npreedit);
|
putlen(dst + Ipcresphdrsz + ncommit, npreedit);
|
||||||
@@ -398,7 +421,7 @@ readfield(int fd, char *dst, int64_t until)
|
|||||||
int
|
int
|
||||||
ipcreadresp(int fd, int want, char *commit, char *preedit, Ipcresp *resp)
|
ipcreadresp(int fd, int want, char *commit, char *preedit, Ipcresp *resp)
|
||||||
{
|
{
|
||||||
unsigned char eaten;
|
unsigned char head[2];
|
||||||
int64_t until;
|
int64_t until;
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
@@ -406,13 +429,14 @@ ipcreadresp(int fd, int want, char *commit, char *preedit, Ipcresp *resp)
|
|||||||
preedit[0] = '\0';
|
preedit[0] = '\0';
|
||||||
memset(resp, 0, sizeof *resp);
|
memset(resp, 0, sizeof *resp);
|
||||||
until = deadline();
|
until = deadline();
|
||||||
if(readwait(fd, &eaten, 1, until) < 0)
|
if(readwait(fd, head, sizeof head, until) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
if(eaten > 1){
|
if(head[0] > 1){
|
||||||
errno = EPROTO;
|
errno = EPROTO;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
resp->eaten = eaten;
|
resp->eaten = head[0];
|
||||||
|
resp->del = head[1];
|
||||||
n = readfield(fd, commit, until);
|
n = readfield(fd, commit, until);
|
||||||
if(n < 0)
|
if(n < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
21
ipc.h
21
ipc.h
@@ -8,9 +8,11 @@
|
|||||||
* The server identifies the connection as the engine owner; that identity is
|
* The server identifies the connection as the engine owner; that identity is
|
||||||
* not sent on the wire.
|
* not sent on the wire.
|
||||||
* Only Mmask modifier bits are retained.
|
* Only Mmask modifier bits are retained.
|
||||||
* Response: [eaten, commit-length-low, commit-length-high, commit...],
|
* Response: [eaten, take-back, commit-length-low, commit-length-high,
|
||||||
* followed, when requested, by
|
* commit...], followed, when requested, by
|
||||||
* [preedit-length-low, preedit-length-high, preedit...].
|
* [preedit-length-low, preedit-length-high, preedit...].
|
||||||
|
* Take-back is the count of runes of the client's own text, just before the
|
||||||
|
* cursor, to remove before inserting the commit.
|
||||||
* Lengths are little-endian byte counts; fields are at most Ipcfieldmax bytes,
|
* Lengths are little-endian byte counts; fields are at most Ipcfieldmax bytes,
|
||||||
* so callers read them into char[Ipcfieldmax+1].
|
* so callers read them into char[Ipcfieldmax+1].
|
||||||
*
|
*
|
||||||
@@ -19,6 +21,10 @@
|
|||||||
* frames are understood.
|
* frames are understood.
|
||||||
* Caret control is sixteen bytes: [0x80, version, 1, valid, x, y, h],
|
* Caret control is sixteen bytes: [0x80, version, 1, valid, x, y, h],
|
||||||
* with the signed coordinates and height encoded as little-endian 32-bit words.
|
* with the signed coordinates and height encoded as little-endian 32-bit words.
|
||||||
|
* Surrounding-text control is six bytes, [0x80, version, 2, 0, length-low,
|
||||||
|
* length-high], and then that many bytes of the text just before the cursor.
|
||||||
|
* It changes what rides along with the requests that follow and is not
|
||||||
|
* answered.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
enum
|
enum
|
||||||
@@ -26,9 +32,10 @@ enum
|
|||||||
Ipcreqwant = 1<<0,
|
Ipcreqwant = 1<<0,
|
||||||
Ipcreqreset = 1<<1,
|
Ipcreqreset = 1<<1,
|
||||||
Ipcext = 1<<7,
|
Ipcext = 1<<7,
|
||||||
Ipcversion = 1,
|
Ipcversion = 2,
|
||||||
Ipcopcap = 0,
|
Ipcopcap = 0,
|
||||||
Ipcopcaret = 1,
|
Ipcopcaret = 1,
|
||||||
|
Ipcopsurround = 2,
|
||||||
|
|
||||||
Kspec = 0x110000,
|
Kspec = 0x110000,
|
||||||
Kback = Kspec|0x08,
|
Kback = Kspec|0x08,
|
||||||
@@ -57,7 +64,7 @@ enum
|
|||||||
Ipcreqsz = 6,
|
Ipcreqsz = 6,
|
||||||
Ipccaretsz = 16,
|
Ipccaretsz = 16,
|
||||||
Ipclensz = 2,
|
Ipclensz = 2,
|
||||||
Ipcresphdrsz = 1 + Ipclensz,
|
Ipcresphdrsz = 2 + Ipclensz,
|
||||||
Ipcfieldmax = 256,
|
Ipcfieldmax = 256,
|
||||||
Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax,
|
Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax,
|
||||||
Ipcwaitms = 250,
|
Ipcwaitms = 250,
|
||||||
@@ -69,12 +76,14 @@ enum
|
|||||||
Ipckey,
|
Ipckey,
|
||||||
Ipccap,
|
Ipccap,
|
||||||
Ipccaret,
|
Ipccaret,
|
||||||
|
Ipcsurround,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct Ipcresp Ipcresp;
|
typedef struct Ipcresp Ipcresp;
|
||||||
struct Ipcresp
|
struct Ipcresp
|
||||||
{
|
{
|
||||||
int eaten;
|
int eaten;
|
||||||
|
int del; /* runes of the client's own text to take back first */
|
||||||
/* Bytes copied to each caller buffer, excluding its trailing NUL. */
|
/* Bytes copied to each caller buffer, excluding its trailing NUL. */
|
||||||
size_t commitlen;
|
size_t commitlen;
|
||||||
size_t preeditlen;
|
size_t preeditlen;
|
||||||
@@ -86,11 +95,13 @@ void ipcpackreq(unsigned char[Ipcreqsz], int, uint32_t, uint32_t);
|
|||||||
void ipcpackreset(unsigned char[Ipcreqsz], int);
|
void ipcpackreset(unsigned char[Ipcreqsz], int);
|
||||||
void ipcpackcap(unsigned char[Ipcreqsz], int);
|
void ipcpackcap(unsigned char[Ipcreqsz], int);
|
||||||
void ipcpackcaret(unsigned char[Ipccaretsz], int, int32_t, int32_t, int32_t);
|
void ipcpackcaret(unsigned char[Ipccaretsz], int, int32_t, int32_t, int32_t);
|
||||||
|
void ipcpacksurround(unsigned char[Ipcreqsz], size_t);
|
||||||
|
size_t ipcsurroundlen(const unsigned char[Ipcreqsz]);
|
||||||
void ipcunpackreq(const unsigned char[Ipcreqsz], int*, uint32_t*, uint32_t*);
|
void ipcunpackreq(const unsigned char[Ipcreqsz], int*, uint32_t*, uint32_t*);
|
||||||
int ipcunpackcaret(const unsigned char[Ipccaretsz], int*, int32_t*, int32_t*, int32_t*);
|
int ipcunpackcaret(const unsigned char[Ipccaretsz], int*, int32_t*, int32_t*, int32_t*);
|
||||||
int ipcreqtype(const unsigned char[Ipcreqsz]);
|
int ipcreqtype(const unsigned char[Ipcreqsz]);
|
||||||
int ipcreqreset(const unsigned char[Ipcreqsz]);
|
int ipcreqreset(const unsigned char[Ipcreqsz]);
|
||||||
int ipcpackresp(unsigned char*, size_t, int, const char*, size_t, const char*, size_t, int);
|
int ipcpackresp(unsigned char*, size_t, int, int, const char*, size_t, const char*, size_t, int);
|
||||||
int ipcreadn(int, void*, size_t);
|
int ipcreadn(int, void*, size_t);
|
||||||
int ipcsend(int, const void*, size_t);
|
int ipcsend(int, const void*, size_t);
|
||||||
int ipcreadresp(int, int, char*, char*, Ipcresp*);
|
int ipcreadresp(int, int, char*, char*, Ipcresp*);
|
||||||
|
|||||||
14
main.c
14
main.c
@@ -47,21 +47,31 @@ erealloc(void *p, ulong n)
|
|||||||
void
|
void
|
||||||
threadmain(int argc, char **argv)
|
threadmain(int argc, char **argv)
|
||||||
{
|
{
|
||||||
char *display;
|
char *display, *scale;
|
||||||
|
|
||||||
if(argc != 2)
|
if(argc != 2)
|
||||||
usage();
|
usage();
|
||||||
|
|
||||||
|
/* Whichever popup runs is sized from it, and setfont reads Fontsz. */
|
||||||
|
scale = getenv("GDK_SCALE");
|
||||||
|
if(scale != nil && atoi(scale) > 1)
|
||||||
|
popupscale = min(atoi(scale), 4);
|
||||||
drawc = chancreate(sizeof(Drawcmd), 4);
|
drawc = chancreate(sizeof(Drawcmd), 4);
|
||||||
keyc = chancreate(sizeof(Keyreq), 0);
|
keyc = chancreate(sizeof(Keyreq), 0);
|
||||||
langinit(argv[1]);
|
langinit(argv[1]);
|
||||||
composeinit();
|
composeinit();
|
||||||
srvinit();
|
srvinit();
|
||||||
proccreate(drawthread, nil, 16384);
|
|
||||||
proccreate(srvthread, nil, 16384);
|
proccreate(srvthread, nil, 16384);
|
||||||
proccreate(ibusthread, nil, 32768);
|
proccreate(ibusthread, nil, 32768);
|
||||||
|
/* One popup per session: the Wayland frontend draws its own, so the
|
||||||
|
* X11 one and the XIM it serves stand down. */
|
||||||
|
if(getenv("WAYLAND_DISPLAY") != nil && wlinit())
|
||||||
|
proccreate(wlthread, nil, 32768);
|
||||||
|
else{
|
||||||
|
proccreate(drawthread, nil, 16384);
|
||||||
display = getenv("DISPLAY");
|
display = getenv("DISPLAY");
|
||||||
if(display != nil && display[0] != '\0')
|
if(display != nil && display[0] != '\0')
|
||||||
proccreate(ximthread, nil, 32768);
|
proccreate(ximthread, nil, 32768);
|
||||||
|
}
|
||||||
imthread(nil);
|
imthread(nil);
|
||||||
}
|
}
|
||||||
|
|||||||
49
map/README
49
map/README
@@ -1,42 +1,53 @@
|
|||||||
# Dictionary data
|
# Dictionary data
|
||||||
|
|
||||||
## Korean Hanja data
|
## Korean Hanja and symbol data
|
||||||
|
|
||||||
`hanja.src` is the tracked, reviewable source for Korean Hanja conversion.
|
`hanja.src` and `mssymbol.src` are the tracked, reviewable sources for what
|
||||||
Every data row is Hanja, a tab, and its modern Hangul reading, a character
|
the Hanja search converts. Every data row is a result, a tab, and its
|
||||||
or a word:
|
Hangul reading. A Hanja reads as a syllable or a word; a symbol reads as
|
||||||
|
the lone consonant a Korean keyboard's 한자 key offers it under:
|
||||||
|
|
||||||
```
|
```
|
||||||
漢 한
|
漢 한
|
||||||
漢字 한자
|
漢字 한자
|
||||||
|
※ ㅁ
|
||||||
```
|
```
|
||||||
|
|
||||||
`mkhanja` validates that contract and groups rows by their reading to
|
`mkhanja` validates that contract and groups the rows of both sources by
|
||||||
produce the runtime dictionary. Candidate order follows source order. The
|
their reading to produce the runtime dictionary. The two keyspaces do not
|
||||||
source keeps all retained pairs for review; the generated dictionary stores
|
meet: a reading is either syllables or one jamo. Candidate order follows
|
||||||
the first 128 candidates per reading because that is the engine's lookup
|
source order. The sources keep all retained pairs for review; the
|
||||||
limit.
|
generated dictionary stores the first 128 candidates per reading because
|
||||||
|
that is the engine's lookup limit.
|
||||||
|
|
||||||
The source is derived from libhangul release tag `libhangul-0.2.0`. The
|
The sources are derived from libhangul release tag `libhangul-0.2.0`. The
|
||||||
annotated tag object is `20afc38922e3595ee3ed5b186f2ea05afe663763`, its
|
annotated tag object is `20afc38922e3595ee3ed5b186f2ea05afe663763`, its
|
||||||
peeled commit is `41c702f5d3581325b646ef6249f1f641b0427ae0`, and
|
peeled commit is `41c702f5d3581325b646ef6249f1f641b0427ae0`, and
|
||||||
`data/hanja/hanja.txt` has blob ID
|
`data/hanja/hanja.txt` and `data/hanja/mssymbol.txt` have blob IDs
|
||||||
`199cfd70c4b306257ac2e018714a1285f7cf0ed3`. Import and regenerate it from
|
`199cfd70c4b306257ac2e018714a1285f7cf0ed3` and
|
||||||
the repository root with:
|
`31c4e63d74293b6a759d4a2005cd1e7333746631`. Import and regenerate them
|
||||||
|
from the repository root with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl -L https://raw.githubusercontent.com/libhangul/libhangul/41c702f5d3581325b646ef6249f1f641b0427ae0/data/hanja/hanja.txt -o upstream
|
curl -L https://raw.githubusercontent.com/libhangul/libhangul/41c702f5d3581325b646ef6249f1f641b0427ae0/data/hanja/hanja.txt -o upstream
|
||||||
printf '%s %s\n' dd44dcc856cf542b1022d0f39c2e9b9f8805fdcc5923be80f04849ed97ce0996 upstream | sha256sum -c -
|
printf '%s %s\n' dd44dcc856cf542b1022d0f39c2e9b9f8805fdcc5923be80f04849ed97ce0996 upstream | sha256sum -c -
|
||||||
map/libhangul2hanja upstream >map/hanja.src
|
map/libhangul2hanja upstream >map/hanja.src
|
||||||
|
curl -L https://raw.githubusercontent.com/libhangul/libhangul/41c702f5d3581325b646ef6249f1f641b0427ae0/data/hanja/mssymbol.txt -o upstream
|
||||||
|
printf '%s %s\n' b685a4ebe2716b25eb29c42f6da6493716ecd78b604b44a33420987d21948e99 upstream | sha256sum -c -
|
||||||
|
map/libhangul2hanja upstream >map/mssymbol.src
|
||||||
map/mkhanja >map/hanja.dict
|
map/mkhanja >map/hanja.dict
|
||||||
```
|
```
|
||||||
|
|
||||||
`libhangul2hanja` retains only rows whose reading is modern Hangul syllables
|
`libhangul2hanja` keeps a syllable reading only when its value is Hanja the
|
||||||
throughout and whose value is Hanja the popup can draw: U+3400–U+4DBF,
|
popup can draw: U+3400–U+4DBF, U+4E00–U+9FFF, or U+F900–U+FAFF. Thus mixed
|
||||||
U+4E00–U+9FFF, or U+F900–U+FAFF. Thus jamo readings, mixed values, and
|
values and supplementary-plane ideographs are omitted. It keeps a jamo
|
||||||
supplementary-plane ideographs are omitted. The import preserves the upstream license header and row order.
|
reading, U+3131–U+314E, only when its value is one rune the popup can draw
|
||||||
The retained data is BSD 3-Clause licensed by Choe Hwanjin; the complete text
|
and a candidate row can carry — so `mssymbol.txt`'s ideographic space and
|
||||||
is in `LICENSES/BSD-3-Clause-libhangul-hanja.txt`.
|
soft hyphen are omitted, leaving 985 of its 987 rows, and a Hanja under a
|
||||||
|
jamo reading is omitted as noise. The import preserves each upstream
|
||||||
|
license header and row order. The retained data is BSD 3-Clause licensed
|
||||||
|
by Choe Hwanjin; the complete text is in
|
||||||
|
`LICENSES/BSD-3-Clause-libhangul-hanja.txt`.
|
||||||
|
|
||||||
## Emoji and symbol data
|
## Emoji and symbol data
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
:( ☹
|
:( ☹
|
||||||
:) ☺
|
:) ☺
|
||||||
<- ←
|
<- ←
|
||||||
|
< ← ≤ ♥ ≠
|
||||||
<= ≤
|
<= ≤
|
||||||
<3 ♥ 🫰 🫶
|
<3 ♥ 🫰 🫶
|
||||||
<> ≠
|
<> ≠
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
=> ⇒
|
=> ⇒
|
||||||
>= ≥
|
>= ≥
|
||||||
^1 ¹
|
^1 ¹
|
||||||
|
^ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹
|
||||||
^2 ²
|
^2 ²
|
||||||
^3 ³
|
^3 ³
|
||||||
^4 ⁴
|
^4 ⁴
|
||||||
@@ -25,6 +27,7 @@
|
|||||||
^8 ⁸
|
^8 ⁸
|
||||||
^9 ⁹
|
^9 ⁹
|
||||||
_1 ₁
|
_1 ₁
|
||||||
|
_ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉
|
||||||
_2 ₂
|
_2 ₂
|
||||||
_3 ₃
|
_3 ₃
|
||||||
_4 ₄
|
_4 ₄
|
||||||
|
|||||||
@@ -24,6 +24,32 @@
|
|||||||
;; CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
;; CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
;; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
;; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
;; POSSIBILITY OF SUCH DAMAGE.
|
;; POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
;; Copyright (c) 2005-2014 Choe Hwanjin
|
||||||
|
;; All rights reserved.
|
||||||
|
;;
|
||||||
|
;; Redistribution and use in source and binary forms, with or without
|
||||||
|
;; modification, are permitted provided that the following conditions are met:
|
||||||
|
;;
|
||||||
|
;; 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
;; this list of conditions and the following disclaimer.
|
||||||
|
;; 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
;; this list of conditions and the following disclaimer in the documentation
|
||||||
|
;; and/or other materials provided with the distribution.
|
||||||
|
;; 3. Neither the name of the author nor the names of its contributors
|
||||||
|
;; may be used to endorse or promote products derived from this software
|
||||||
|
;; without specific prior written permission.
|
||||||
|
;;
|
||||||
|
;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
;; ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
;; LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
;; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
;; CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
;; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
;; POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
가 可 家 加 歌 價 街 假 佳 暇 架 价 賈 伽 柯 迦 軻 嘉 駕 嫁 稼 哥 呵 苛 袈 訶 枷 珂 茄 痂 跏 斝 葭 舸 笳 哿 坷 檟 謌 珈 砢 佉 呿 榎 仮 傢 咖 瘕 耞 宊 椵 叚 価 猳 髂 瘸 鴐 䴥 拁 㤎 榢 毠 溊 牁 豭 槚 诃 贾 轲 镓 驾 鲄 婽 尜 岢 幏 彁 徦 愘 戓 戨 斚 樖 泇 渮 滒 炣 牫 牱 犌 玍 癿 笴 糘 胢 腵 蚵 貑 跒 酠 鉫 鉲 鎵 鎶 魺 鴚 麚 㗎 㚙 㞹 㢦 㤉 㧝 㪃 㪼 㱒 㹢 䂟 䈔 䑝 䔅 䕒 䪪 䯊 䶗 嘏
|
가 可 家 加 歌 價 街 假 佳 暇 架 价 賈 伽 柯 迦 軻 嘉 駕 嫁 稼 哥 呵 苛 袈 訶 枷 珂 茄 痂 跏 斝 葭 舸 笳 哿 坷 檟 謌 珈 砢 佉 呿 榎 仮 傢 咖 瘕 耞 宊 椵 叚 価 猳 髂 瘸 鴐 䴥 拁 㤎 榢 毠 溊 牁 豭 槚 诃 贾 轲 镓 驾 鲄 婽 尜 岢 幏 彁 徦 愘 戓 戨 斚 樖 泇 渮 滒 炣 牫 牱 犌 玍 癿 笴 糘 胢 腵 蚵 貑 跒 酠 鉫 鉲 鎵 鎶 魺 鴚 麚 㗎 㚙 㞹 㢦 㤉 㧝 㪃 㪼 㱒 㹢 䂟 䈔 䑝 䔅 䕒 䪪 䯊 䶗 嘏
|
||||||
가가 假家 可呵 可嘉 家家 呵呵
|
가가 假家 可呵 可嘉 家家 呵呵
|
||||||
@@ -187311,3 +187337,21 @@
|
|||||||
힐책 詰責
|
힐책 詰責
|
||||||
힐척 詰斥
|
힐척 詰斥
|
||||||
힐항 詰抗
|
힐항 詰抗
|
||||||
|
ㄱ ! ' , . / : ; ? ^ _ ` |  ̄ 、 。 · ‥ … ¨ 〃 ― ∥ \ ∼ ´ ~ ˇ ˘ ˝ ˚ ˙ ¸ ˛ ¡ ¿ ː
|
||||||
|
ㄲ Æ Ð Ħ IJ Ŀ Ł Ø Œ Þ Ŧ Ŋ æ đ ð ħ ı ij ĸ ŀ ł ø œ ß þ ŧ ŋ ʼn
|
||||||
|
ㄴ " ( ) [ ] { } ‘ ’ “ ” 〔 〕 〈 〉 《 》 「 」 『 』 【 】
|
||||||
|
ㄷ + - < = > ± × ÷ ≠ ≤ ≥ ∞ ∴ ♂ ♀ ∠ ⊥ ⌒ ∂ ∇ ≡ ≒ ≪ ≫ √ ∽ ∝ ∵ ∫ ∬ ∈ ∋ ⊆ ⊇ ⊂ ⊃ ∪ ∩ ∧ ∨ ¬ ⇒ ⇔ ∀ ∃ ∮ ∑ ∏
|
||||||
|
ㄸ ぁ あ ぃ い ぅ う ぇ え ぉ お か が き ぎ く ぐ け げ こ ご さ ざ し じ す ず せ ぜ そ ぞ た だ ち ぢ っ つ づ て で と ど な に ぬ ね の は ば ぱ ひ び ぴ ふ ぶ ぷ へ べ ぺ ほ ぼ ぽ ま み む め も ゃ や ゅ ゆ ょ よ ら り る れ ろ ゎ わ ゐ ゑ を ん
|
||||||
|
ㄹ $ % ₩ F ′ ″ ℃ Å ¢ £ ¥ ¤ ℉ ‰ € ㎕ ㎖ ㎗ ℓ ㎘ ㏄ ㎣ ㎤ ㎥ ㎦ ㎙ ㎚ ㎛ ㎜ ㎝ ㎞ ㎟ ㎠ ㎡ ㎢ ㏊ ㎍ ㎎ ㎏ ㏏ ㎈ ㎉ ㏈ ㎧ ㎨ ㎰ ㎱ ㎲ ㎳ ㎴ ㎵ ㎶ ㎷ ㎸ ㎹ ㎀ ㎁ ㎂ ㎃ ㎄ ㎺ ㎻ ㎼ ㎽ ㎾ ㎿ ㎐ ㎑ ㎒ ㎓ ㎔ Ω ㏀ ㏁ ㎊ ㎋ ㎌ ㏖ ㏅ ㎭ ㎮ ㎯ ㏛ ㎩ ㎪ ㎫ ㎬ ㏝ ㏐ ㏓ ㏃ ㏉ ㏜ ㏆
|
||||||
|
ㅁ # & * @ § ※ ☆ ★ ○ ● ◎ ◇ ◆ □ ■ △ ▲ ▽ ▼ → ← ↑ ↓ ↔ 〓 ◁ ◀ ▷ ▶ ♤ ♠ ♡ ♥ ♧ ♣ ⊙ ◈ ▣ ◐ ◑ ▒ ▤ ▥ ▨ ▧ ▦ ▩ ♨ ☏ ☎ ☜ ☞ ¶ † ‡ ↕ ↗ ↙ ↖ ↘ ♭ ♩ ♪ ♬ ㉿ ㈜ № ㏇ ™ ㏂ ㏘ ℡ ® ª º
|
||||||
|
ㅂ ─ │ ┌ ┐ ┘ └ ├ ┬ ┤ ┴ ┼ ━ ┃ ┏ ┓ ┛ ┗ ┣ ┳ ┫ ┻ ╋ ┠ ┯ ┨ ┷ ┿ ┝ ┰ ┥ ┸ ╂ ┒ ┑ ┚ ┙ ┖ ┕ ┎ ┍ ┞ ┟ ┡ ┢ ┦ ┧ ┩ ┪ ┭ ┮ ┱ ┲ ┵ ┶ ┹ ┺ ┽ ┾ ╀ ╁ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊
|
||||||
|
ㅃ ァ ア ィ イ ゥ ウ ェ エ ォ オ カ ガ キ ギ ク グ ケ ゲ コ ゴ サ ザ シ ジ ス ズ セ ゼ ソ ゾ タ ダ チ ヂ ッ ツ ヅ テ デ ト ド ナ ニ ヌ ネ ノ ハ バ パ ヒ ビ ピ フ ブ プ ヘ ベ ペ ホ ボ ポ マ ミ ム メ モ ャ ヤ ュ ユ ョ ヨ ラ リ ル レ ロ ヮ ワ ヰ ヱ ヲ ン ヴ ヵ ヶ
|
||||||
|
ㅅ ㉠ ㉡ ㉢ ㉣ ㉤ ㉥ ㉦ ㉧ ㉨ ㉩ ㉪ ㉫ ㉬ ㉭ ㉮ ㉯ ㉰ ㉱ ㉲ ㉳ ㉴ ㉵ ㉶ ㉷ ㉸ ㉹ ㉺ ㉻ ㈀ ㈁ ㈂ ㈃ ㈄ ㈅ ㈆ ㈇ ㈈ ㈉ ㈊ ㈋ ㈌ ㈍ ㈎ ㈏ ㈐ ㈑ ㈒ ㈓ ㈔ ㈕ ㈖ ㈗ ㈘ ㈙ ㈚ ㈛
|
||||||
|
ㅆ А Б В Г Д Е Ё Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я а б в г д е ё ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я
|
||||||
|
ㅇ ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵ ⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂
|
||||||
|
ㅈ 0 1 2 3 4 5 6 7 8 9 ⅰ ⅱ ⅲ ⅳ ⅴ ⅵ ⅶ ⅷ ⅸ ⅹ Ⅰ Ⅱ Ⅲ Ⅳ Ⅴ Ⅵ Ⅶ Ⅷ Ⅸ Ⅹ
|
||||||
|
ㅊ ½ ⅓ ⅔ ¼ ¾ ⅛ ⅜ ⅝ ⅞ ¹ ² ³ ⁴ ⁿ ₁ ₂ ₃ ₄
|
||||||
|
ㅋ ㄱ ㄲ ㄳ ㄴ ㄵ ㄶ ㄷ ㄸ ㄹ ㄺ ㄻ ㄼ ㄽ ㄾ ㄿ ㅀ ㅁ ㅂ ㅃ ㅄ ㅅ ㅆ ㅇ ㅈ ㅉ ㅊ ㅋ ㅌ ㅍ ㅎ ㅏ ㅐ ㅑ ㅒ ㅓ ㅔ ㅕ ㅖ ㅗ ㅘ ㅙ ㅚ ㅛ ㅜ ㅝ ㅞ ㅟ ㅠ ㅡ ㅢ ㅣ
|
||||||
|
ㅌ ㅥ ㅦ ㅧ ㅨ ㅩ ㅪ ㅫ ㅬ ㅭ ㅮ ㅯ ㅰ ㅱ ㅲ ㅳ ㅴ ㅵ ㅶ ㅷ ㅸ ㅹ ㅺ ㅻ ㅼ ㅽ ㅾ ㅿ ㆀ ㆁ ㆂ ㆃ ㆄ ㆅ ㆆ ㆇ ㆈ ㆉ ㆊ ㆋ ㆌ ㆍ ㆎ
|
||||||
|
ㅍ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z
|
||||||
|
ㅎ Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ φ χ ψ ω
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Extract Hanja readings from libhangul data."""
|
"""Extract Hanja and symbol readings from libhangul data."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import unicodedata
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
@@ -9,12 +10,31 @@ def ishangul(s):
|
|||||||
return s != "" and all(0xAC00 <= ord(c) <= 0xD7A3 for c in s)
|
return s != "" and all(0xAC00 <= ord(c) <= 0xD7A3 for c in s)
|
||||||
|
|
||||||
|
|
||||||
|
def isjamo(s):
|
||||||
|
return len(s) == 1 and 0x3131 <= ord(s) <= 0x314E
|
||||||
|
|
||||||
|
|
||||||
def ishanja(s):
|
def ishanja(s):
|
||||||
return s != "" and all(0x3400 <= ord(c) <= 0x4DBF
|
return s != "" and all(0x3400 <= ord(c) <= 0x4DBF
|
||||||
or 0x4E00 <= ord(c) <= 0x9FFF
|
or 0x4E00 <= ord(c) <= 0x9FFF
|
||||||
or 0xF900 <= ord(c) <= 0xFAFF for c in s)
|
or 0xF900 <= ord(c) <= 0xFAFF for c in s)
|
||||||
|
|
||||||
|
|
||||||
|
def issymbol(s):
|
||||||
|
"""One rune the popup can draw and a candidate row can carry: not a
|
||||||
|
space, which the row separates candidates with, and not a formatting
|
||||||
|
character, which would leave a blank candidate to pick."""
|
||||||
|
return (len(s) == 1 and not s.isspace() and not ishanja(s)
|
||||||
|
and not unicodedata.category(s).startswith("C"))
|
||||||
|
|
||||||
|
|
||||||
|
def keeps(reading, value):
|
||||||
|
"""A syllable reading gives Hanja; a jamo reading gives a symbol."""
|
||||||
|
if ishangul(reading):
|
||||||
|
return ishanja(value)
|
||||||
|
return isjamo(reading) and issymbol(value)
|
||||||
|
|
||||||
|
|
||||||
def extract(src, name):
|
def extract(src, name):
|
||||||
comments = []
|
comments = []
|
||||||
entries = []
|
entries = []
|
||||||
@@ -32,16 +52,16 @@ def extract(src, name):
|
|||||||
fields = line.split(":")
|
fields = line.split(":")
|
||||||
if len(fields) != 3:
|
if len(fields) != 3:
|
||||||
raise ValueError(f"{name}:{lineno}: need key:value:comment")
|
raise ValueError(f"{name}:{lineno}: need key:value:comment")
|
||||||
reading, hanja, _ = fields
|
reading, value, _ = fields
|
||||||
if not ishangul(reading) or not ishanja(hanja):
|
if not keeps(reading, value):
|
||||||
continue
|
continue
|
||||||
pair = (hanja, reading)
|
pair = (value, reading)
|
||||||
if pair in seen:
|
if pair in seen:
|
||||||
raise ValueError(f"{name}:{lineno}: duplicate Hanja reading")
|
raise ValueError(f"{name}:{lineno}: duplicate reading")
|
||||||
seen.add(pair)
|
seen.add(pair)
|
||||||
entries.append(pair)
|
entries.append(pair)
|
||||||
if not entries:
|
if not entries:
|
||||||
raise ValueError(f"{name}: no Hanja readings")
|
raise ValueError(f"{name}: no readings")
|
||||||
return comments, entries
|
return comments, entries
|
||||||
|
|
||||||
|
|
||||||
@@ -63,8 +83,8 @@ def main():
|
|||||||
print(line)
|
print(line)
|
||||||
if comments:
|
if comments:
|
||||||
print()
|
print()
|
||||||
for hanja, reading in entries:
|
for value, reading in entries:
|
||||||
print(f"{hanja}\t{reading}")
|
print(f"{value}\t{reading}")
|
||||||
except (OSError, UnicodeError, ValueError) as error:
|
except (OSError, UnicodeError, ValueError) as error:
|
||||||
print(error, file=sys.stderr)
|
print(error, file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
61
map/mkhanja
61
map/mkhanja
@@ -1,11 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Write hanja.dict from a Hanja-per-row UTF-8 source."""
|
"""Write hanja.dict from result-per-row UTF-8 sources."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import unicodedata
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
SOURCE = Path(__file__).with_name("hanja.src")
|
SOURCES = [Path(__file__).with_name(name)
|
||||||
|
for name in ("hanja.src", "mssymbol.src")]
|
||||||
MAXCANDIDATES = 128
|
MAXCANDIDATES = 128
|
||||||
|
|
||||||
|
|
||||||
@@ -13,17 +15,29 @@ def ishangul(s):
|
|||||||
return s != "" and all(0xAC00 <= ord(c) <= 0xD7A3 for c in s)
|
return s != "" and all(0xAC00 <= ord(c) <= 0xD7A3 for c in s)
|
||||||
|
|
||||||
|
|
||||||
|
def isjamo(s):
|
||||||
|
return len(s) == 1 and 0x3131 <= ord(s) <= 0x314E
|
||||||
|
|
||||||
|
|
||||||
def ishanja(s):
|
def ishanja(s):
|
||||||
return s != "" and all(0x3400 <= ord(c) <= 0x4DBF
|
return s != "" and all(0x3400 <= ord(c) <= 0x4DBF
|
||||||
or 0x4E00 <= ord(c) <= 0x9FFF
|
or 0x4E00 <= ord(c) <= 0x9FFF
|
||||||
or 0xF900 <= ord(c) <= 0xFAFF for c in s)
|
or 0xF900 <= ord(c) <= 0xFAFF for c in s)
|
||||||
|
|
||||||
|
|
||||||
def read(path):
|
def issymbol(s):
|
||||||
|
"""One rune the popup can draw and a candidate row can carry: not a
|
||||||
|
space, which the row separates candidates with, and not a formatting
|
||||||
|
character, which would leave a blank candidate to pick."""
|
||||||
|
return (len(s) == 1 and not s.isspace() and not ishanja(s)
|
||||||
|
and not unicodedata.category(s).startswith("C"))
|
||||||
|
|
||||||
|
|
||||||
|
def read(path, table, seen):
|
||||||
|
"""Adds path's rows to table, keyed by reading, and returns its header."""
|
||||||
comments = []
|
comments = []
|
||||||
table = {}
|
|
||||||
seen = set()
|
|
||||||
leading = True
|
leading = True
|
||||||
|
found = False
|
||||||
with path.open(encoding="utf-8") as src:
|
with path.open(encoding="utf-8") as src:
|
||||||
for lineno, raw in enumerate(src, 1):
|
for lineno, raw in enumerate(src, 1):
|
||||||
line = raw.rstrip("\r\n")
|
line = raw.rstrip("\r\n")
|
||||||
@@ -36,31 +50,36 @@ def read(path):
|
|||||||
leading = False
|
leading = False
|
||||||
fields = line.split("\t")
|
fields = line.split("\t")
|
||||||
if len(fields) != 2:
|
if len(fields) != 2:
|
||||||
raise ValueError(f"{path}:{lineno}: need Hanja<TAB>reading")
|
raise ValueError(f"{path}:{lineno}: need result<TAB>reading")
|
||||||
hanja, reading = fields
|
value, reading = fields
|
||||||
if not ishanja(hanja):
|
if ishangul(reading):
|
||||||
|
if not ishanja(value):
|
||||||
raise ValueError(f"{path}:{lineno}: need BMP Hanja")
|
raise ValueError(f"{path}:{lineno}: need BMP Hanja")
|
||||||
if not ishangul(reading):
|
elif isjamo(reading):
|
||||||
raise ValueError(f"{path}:{lineno}: need Hangul syllables")
|
if not issymbol(value):
|
||||||
pair = (hanja, reading)
|
raise ValueError(f"{path}:{lineno}: need one symbol rune")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"{path}:{lineno}: need a syllable or jamo "
|
||||||
|
"reading")
|
||||||
|
pair = (value, reading)
|
||||||
if pair in seen:
|
if pair in seen:
|
||||||
raise ValueError(f"{path}:{lineno}: duplicate Hanja reading")
|
raise ValueError(f"{path}:{lineno}: duplicate reading")
|
||||||
seen.add(pair)
|
seen.add(pair)
|
||||||
table.setdefault(reading, []).append(hanja)
|
found = True
|
||||||
if not seen:
|
table.setdefault(reading, []).append(value)
|
||||||
raise ValueError(f"{path}: no Hanja readings")
|
if not found:
|
||||||
return comments, table
|
raise ValueError(f"{path}: no readings")
|
||||||
|
return comments
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
if len(sys.argv) > 2:
|
paths = [Path(arg) for arg in sys.argv[1:]] or SOURCES
|
||||||
print(f"usage: {sys.argv[0]} [hanja.src]", file=sys.stderr)
|
table = {}
|
||||||
return 2
|
seen = set()
|
||||||
path = Path(sys.argv[1]) if len(sys.argv) == 2 else SOURCE
|
|
||||||
try:
|
try:
|
||||||
comments, table = read(path)
|
comments = [line for path in paths for line in read(path, table, seen)]
|
||||||
for line in comments:
|
for line in comments:
|
||||||
print(line)
|
print(line)
|
||||||
if comments:
|
if comments:
|
||||||
|
|||||||
1012
map/mssymbol.src
Normal file
1012
map/mssymbol.src
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,31 +10,31 @@
|
|||||||
☺ :)
|
☺ :)
|
||||||
|
|
||||||
# Bare 1-9 choose from a prefix search; keep digit aliases in matching slots.
|
# Bare 1-9 choose from a prefix search; keep digit aliases in matching slots.
|
||||||
← <-
|
← <- <
|
||||||
≤ <=
|
≤ <= <
|
||||||
♥ <3
|
♥ <3 <
|
||||||
≠ <> !=
|
≠ <> != <
|
||||||
≡ ==
|
≡ ==
|
||||||
⇒ =>
|
⇒ =>
|
||||||
≥ >=
|
≥ >=
|
||||||
¹ ^1
|
¹ ^1 ^
|
||||||
² ^2
|
² ^2 ^
|
||||||
³ ^3
|
³ ^3 ^
|
||||||
⁴ ^4
|
⁴ ^4 ^
|
||||||
⁵ ^5
|
⁵ ^5 ^
|
||||||
⁶ ^6
|
⁶ ^6 ^
|
||||||
⁷ ^7
|
⁷ ^7 ^
|
||||||
⁸ ^8
|
⁸ ^8 ^
|
||||||
⁹ ^9
|
⁹ ^9 ^
|
||||||
₁ _1
|
₁ _1 _
|
||||||
₂ _2
|
₂ _2 _
|
||||||
₃ _3
|
₃ _3 _
|
||||||
₄ _4
|
₄ _4 _
|
||||||
₅ _5
|
₅ _5 _
|
||||||
₆ _6
|
₆ _6 _
|
||||||
₇ _7
|
₇ _7 _
|
||||||
₈ _8
|
₈ _8 _
|
||||||
₉ _9
|
₉ _9 _
|
||||||
|
|
||||||
⁽ ^(
|
⁽ ^(
|
||||||
⁾ ^)
|
⁾ ^)
|
||||||
|
|||||||
494
proto/input-method-unstable-v2.xml
Normal file
494
proto/input-method-unstable-v2.xml
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="input_method_unstable_v2">
|
||||||
|
|
||||||
|
<copyright>
|
||||||
|
Copyright © 2008-2011 Kristian Høgsberg
|
||||||
|
Copyright © 2010-2011 Intel Corporation
|
||||||
|
Copyright © 2012-2013 Collabora, Ltd.
|
||||||
|
Copyright © 2012, 2013 Intel Corporation
|
||||||
|
Copyright © 2015, 2016 Jan Arne Petersen
|
||||||
|
Copyright © 2017, 2018 Red Hat, Inc.
|
||||||
|
Copyright © 2018 Purism SPC
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next
|
||||||
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<description summary="Protocol for creating input methods">
|
||||||
|
This protocol allows applications to act as input methods for compositors.
|
||||||
|
|
||||||
|
An input method context is used to manage the state of the input method.
|
||||||
|
|
||||||
|
Text strings are UTF-8 encoded, their indices and lengths are in bytes.
|
||||||
|
|
||||||
|
This document adheres to the RFC 2119 when using words like "must",
|
||||||
|
"should", "may", etc.
|
||||||
|
|
||||||
|
Warning! The protocol described in this file is experimental and
|
||||||
|
backward incompatible changes may be made. Backward compatible changes
|
||||||
|
may be added together with the corresponding interface version bump.
|
||||||
|
Backward incompatible changes are done by bumping the version number in
|
||||||
|
the protocol and interface names and resetting the interface version.
|
||||||
|
Once the protocol is to be declared stable, the 'z' prefix and the
|
||||||
|
version number in the protocol and interface names are removed and the
|
||||||
|
interface version number is reset.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<interface name="zwp_input_method_v2" version="1">
|
||||||
|
<description summary="input method">
|
||||||
|
An input method object allows for clients to compose text.
|
||||||
|
|
||||||
|
The objects connects the client to a text input in an application, and
|
||||||
|
lets the client to serve as an input method for a seat.
|
||||||
|
|
||||||
|
The zwp_input_method_v2 object can occupy two distinct states: active and
|
||||||
|
inactive. In the active state, the object is associated to and
|
||||||
|
communicates with a text input. In the inactive state, there is no
|
||||||
|
associated text input, and the only communication is with the compositor.
|
||||||
|
Initially, the input method is in the inactive state.
|
||||||
|
|
||||||
|
Requests issued in the inactive state must be accepted by the compositor.
|
||||||
|
Because of the serial mechanism, and the state reset on activate event,
|
||||||
|
they will not have any effect on the state of the next text input.
|
||||||
|
|
||||||
|
There must be no more than one input method object per seat.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="role" value="0" summary="wl_surface has another role"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<event name="activate">
|
||||||
|
<description summary="input method has been requested">
|
||||||
|
Notification that a text input focused on this seat requested the input
|
||||||
|
method to be activated.
|
||||||
|
|
||||||
|
This event serves the purpose of providing the compositor with an
|
||||||
|
active input method.
|
||||||
|
|
||||||
|
This event resets all state associated with previous
|
||||||
|
surrounding_text, text_change_cause, and content_type events, as well
|
||||||
|
as the state associated with set_preedit_string, commit_string, and
|
||||||
|
delete_surrounding_text requests. In addition, it marks the
|
||||||
|
zwp_input_method_v2 object as active, and makes any existing
|
||||||
|
zwp_input_popup_surface_v2 objects visible.
|
||||||
|
|
||||||
|
The surrounding_text, and content_type events must follow before the
|
||||||
|
next done event if the text input supports the respective
|
||||||
|
functionality.
|
||||||
|
|
||||||
|
State set with this event is double-buffered. It will get applied on
|
||||||
|
the next zwp_input_method_v2.done event, and stay valid until changed.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="deactivate">
|
||||||
|
<description summary="deactivate event">
|
||||||
|
Notification that no focused text input currently needs an active
|
||||||
|
input method on this seat.
|
||||||
|
|
||||||
|
This event marks the zwp_input_method_v2 object as inactive. The
|
||||||
|
compositor must make all existing zwp_input_popup_surface_v2 objects
|
||||||
|
invisible until the next activate event.
|
||||||
|
|
||||||
|
State set with this event is double-buffered. It will get applied on
|
||||||
|
the next zwp_input_method_v2.done event, and stay valid until changed.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="surrounding_text">
|
||||||
|
<description summary="surrounding text event">
|
||||||
|
Updates the surrounding plain text around the cursor, excluding the
|
||||||
|
preedit text.
|
||||||
|
|
||||||
|
If any preedit text is present, it is replaced with the cursor for the
|
||||||
|
purpose of this event.
|
||||||
|
|
||||||
|
The argument text is a buffer containing the surrounding text, and must
|
||||||
|
include the cursor position, and the complete selection. It should
|
||||||
|
contain additional characters before and after these. There is a
|
||||||
|
maximum length of wayland messages, so text can not be longer than 4000
|
||||||
|
bytes.
|
||||||
|
|
||||||
|
cursor is the byte offset of the cursor within the text buffer.
|
||||||
|
|
||||||
|
anchor is the byte offset of the selection anchor within the text
|
||||||
|
buffer. If there is no selected text, anchor must be the same as
|
||||||
|
cursor.
|
||||||
|
|
||||||
|
If this event does not arrive before the first done event, the input
|
||||||
|
method may assume that the text input does not support this
|
||||||
|
functionality and ignore following surrounding_text events.
|
||||||
|
|
||||||
|
Values set with this event are double-buffered. They will get applied
|
||||||
|
and set to initial values on the next zwp_input_method_v2.done
|
||||||
|
event.
|
||||||
|
|
||||||
|
The initial state for affected fields is empty, meaning that the text
|
||||||
|
input does not support sending surrounding text. If the empty values
|
||||||
|
get applied, subsequent attempts to change them may have no effect.
|
||||||
|
</description>
|
||||||
|
<arg name="text" type="string"/>
|
||||||
|
<arg name="cursor" type="uint"/>
|
||||||
|
<arg name="anchor" type="uint"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="text_change_cause">
|
||||||
|
<description summary="indicates the cause of surrounding text change">
|
||||||
|
Tells the input method why the text surrounding the cursor changed.
|
||||||
|
|
||||||
|
Whenever the client detects an external change in text, cursor, or
|
||||||
|
anchor position, it must issue this request to the compositor. This
|
||||||
|
request is intended to give the input method a chance to update the
|
||||||
|
preedit text in an appropriate way, e.g. by removing it when the user
|
||||||
|
starts typing with a keyboard.
|
||||||
|
|
||||||
|
cause describes the source of the change.
|
||||||
|
|
||||||
|
The value set with this event is double-buffered. It will get applied
|
||||||
|
and set to its initial value on the next zwp_input_method_v2.done
|
||||||
|
event.
|
||||||
|
|
||||||
|
The initial value of cause is input_method.
|
||||||
|
</description>
|
||||||
|
<arg name="cause" type="uint" enum="zwp_text_input_v3.change_cause"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="content_type">
|
||||||
|
<description summary="content purpose and hint">
|
||||||
|
Indicates the content type and hint for the current
|
||||||
|
zwp_input_method_v2 instance.
|
||||||
|
|
||||||
|
Values set with this event are double-buffered. They will get applied
|
||||||
|
on the next zwp_input_method_v2.done event.
|
||||||
|
|
||||||
|
The initial value for hint is none, and the initial value for purpose
|
||||||
|
is normal.
|
||||||
|
</description>
|
||||||
|
<arg name="hint" type="uint" enum="zwp_text_input_v3.content_hint"/>
|
||||||
|
<arg name="purpose" type="uint" enum="zwp_text_input_v3.content_purpose"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="done">
|
||||||
|
<description summary="apply state">
|
||||||
|
Atomically applies state changes recently sent to the client.
|
||||||
|
|
||||||
|
The done event establishes and updates the state of the client, and
|
||||||
|
must be issued after any changes to apply them.
|
||||||
|
|
||||||
|
Text input state (content purpose, content hint, surrounding text, and
|
||||||
|
change cause) is conceptually double-buffered within an input method
|
||||||
|
context.
|
||||||
|
|
||||||
|
Events modify the pending state, as opposed to the current state in use
|
||||||
|
by the input method. A done event atomically applies all pending state,
|
||||||
|
replacing the current state. After done, the new pending state is as
|
||||||
|
documented for each related request.
|
||||||
|
|
||||||
|
Events must be applied in the order of arrival.
|
||||||
|
|
||||||
|
Neither current nor pending state are modified unless noted otherwise.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="commit_string">
|
||||||
|
<description summary="commit string">
|
||||||
|
Send the commit string text for insertion to the application.
|
||||||
|
|
||||||
|
Inserts a string at current cursor position (see commit event
|
||||||
|
sequence). The string to commit could be either just a single character
|
||||||
|
after a key press or the result of some composing.
|
||||||
|
|
||||||
|
The argument text is a buffer containing the string to insert. There is
|
||||||
|
a maximum length of wayland messages, so text can not be longer than
|
||||||
|
4000 bytes.
|
||||||
|
|
||||||
|
Values set with this event are double-buffered. They must be applied
|
||||||
|
and reset to initial on the next zwp_text_input_v3.commit request.
|
||||||
|
|
||||||
|
The initial value of text is an empty string.
|
||||||
|
</description>
|
||||||
|
<arg name="text" type="string"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_preedit_string">
|
||||||
|
<description summary="pre-edit string">
|
||||||
|
Send the pre-edit string text to the application text input.
|
||||||
|
|
||||||
|
Place a new composing text (pre-edit) at the current cursor position.
|
||||||
|
Any previously set composing text must be removed. Any previously
|
||||||
|
existing selected text must be removed. The cursor is moved to a new
|
||||||
|
position within the preedit string.
|
||||||
|
|
||||||
|
The argument text is a buffer containing the preedit string. There is
|
||||||
|
a maximum length of wayland messages, so text can not be longer than
|
||||||
|
4000 bytes.
|
||||||
|
|
||||||
|
The arguments cursor_begin and cursor_end are counted in bytes relative
|
||||||
|
to the beginning of the submitted string buffer. Cursor should be
|
||||||
|
hidden by the text input when both are equal to -1.
|
||||||
|
|
||||||
|
cursor_begin indicates the beginning of the cursor. cursor_end
|
||||||
|
indicates the end of the cursor. It may be equal or different than
|
||||||
|
cursor_begin.
|
||||||
|
|
||||||
|
Values set with this event are double-buffered. They must be applied on
|
||||||
|
the next zwp_input_method_v2.commit event.
|
||||||
|
|
||||||
|
The initial value of text is an empty string. The initial value of
|
||||||
|
cursor_begin, and cursor_end are both 0.
|
||||||
|
</description>
|
||||||
|
<arg name="text" type="string"/>
|
||||||
|
<arg name="cursor_begin" type="int"/>
|
||||||
|
<arg name="cursor_end" type="int"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="delete_surrounding_text">
|
||||||
|
<description summary="delete text">
|
||||||
|
Remove the surrounding text.
|
||||||
|
|
||||||
|
before_length and after_length are the number of bytes before and after
|
||||||
|
the current cursor index (excluding the preedit text) to delete.
|
||||||
|
|
||||||
|
If any preedit text is present, it is replaced with the cursor for the
|
||||||
|
purpose of this event. In effect before_length is counted from the
|
||||||
|
beginning of preedit text, and after_length from its end (see commit
|
||||||
|
event sequence).
|
||||||
|
|
||||||
|
Values set with this event are double-buffered. They must be applied
|
||||||
|
and reset to initial on the next zwp_input_method_v2.commit request.
|
||||||
|
|
||||||
|
The initial values of both before_length and after_length are 0.
|
||||||
|
</description>
|
||||||
|
<arg name="before_length" type="uint"/>
|
||||||
|
<arg name="after_length" type="uint"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="commit">
|
||||||
|
<description summary="apply state">
|
||||||
|
Apply state changes from commit_string, set_preedit_string and
|
||||||
|
delete_surrounding_text requests.
|
||||||
|
|
||||||
|
The state relating to these events is double-buffered, and each one
|
||||||
|
modifies the pending state. This request replaces the current state
|
||||||
|
with the pending state.
|
||||||
|
|
||||||
|
The connected text input is expected to proceed by evaluating the
|
||||||
|
changes in the following order:
|
||||||
|
|
||||||
|
1. Replace existing preedit string with the cursor.
|
||||||
|
2. Delete requested surrounding text.
|
||||||
|
3. Insert commit string with the cursor at its end.
|
||||||
|
4. Calculate surrounding text to send.
|
||||||
|
5. Insert new preedit text in cursor position.
|
||||||
|
6. Place cursor inside preedit text.
|
||||||
|
|
||||||
|
The serial number reflects the last state of the zwp_input_method_v2
|
||||||
|
object known to the client. The value of the serial argument must be
|
||||||
|
equal to the number of done events already issued by that object. When
|
||||||
|
the compositor receives a commit request with a serial different than
|
||||||
|
the number of past done events, it must proceed as normal, except it
|
||||||
|
should not change the current state of the zwp_input_method_v2 object.
|
||||||
|
</description>
|
||||||
|
<arg name="serial" type="uint"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="get_input_popup_surface">
|
||||||
|
<description summary="create popup surface">
|
||||||
|
Creates a new zwp_input_popup_surface_v2 object wrapping a given
|
||||||
|
surface.
|
||||||
|
|
||||||
|
The surface gets assigned the "input_popup" role. If the surface
|
||||||
|
already has an assigned role, the compositor must issue a protocol
|
||||||
|
error.
|
||||||
|
</description>
|
||||||
|
<arg name="id" type="new_id" interface="zwp_input_popup_surface_v2"/>
|
||||||
|
<arg name="surface" type="object" interface="wl_surface"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="grab_keyboard">
|
||||||
|
<description summary="grab hardware keyboard">
|
||||||
|
Allow an input method to receive hardware keyboard input and process
|
||||||
|
key events to generate text events (with pre-edit) over the wire. This
|
||||||
|
allows input methods which compose multiple key events for inputting
|
||||||
|
text like it is done for CJK languages.
|
||||||
|
|
||||||
|
The compositor should send all keyboard events on the seat to the grab
|
||||||
|
holder via the returned wl_keyboard object. Nevertheless, the
|
||||||
|
compositor may decide not to forward any particular event. The
|
||||||
|
compositor must not further process any event after it has been
|
||||||
|
forwarded to the grab holder.
|
||||||
|
|
||||||
|
Releasing the resulting wl_keyboard object releases the grab.
|
||||||
|
</description>
|
||||||
|
<arg name="keyboard" type="new_id"
|
||||||
|
interface="zwp_input_method_keyboard_grab_v2"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="unavailable">
|
||||||
|
<description summary="input method unavailable">
|
||||||
|
The input method ceased to be available.
|
||||||
|
|
||||||
|
The compositor must issue this event as the only event on the object if
|
||||||
|
there was another input_method object associated with the same seat at
|
||||||
|
the time of its creation.
|
||||||
|
|
||||||
|
The compositor must issue this request when the object is no longer
|
||||||
|
usable, e.g. due to seat removal.
|
||||||
|
|
||||||
|
The input method context becomes inert and should be destroyed after
|
||||||
|
deactivation is handled. Any further requests and events except for the
|
||||||
|
destroy request must be ignored.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the text input">
|
||||||
|
Destroys the zwp_text_input_v2 object and any associated child
|
||||||
|
objects, i.e. zwp_input_popup_surface_v2 and
|
||||||
|
zwp_input_method_keyboard_grab_v2.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwp_input_popup_surface_v2" version="1">
|
||||||
|
<description summary="popup surface">
|
||||||
|
This interface marks a surface as a popup for interacting with an input
|
||||||
|
method.
|
||||||
|
|
||||||
|
The compositor should place it near the active text input area. It must
|
||||||
|
be visible if and only if the input method is in the active state.
|
||||||
|
|
||||||
|
The client must not destroy the underlying wl_surface while the
|
||||||
|
zwp_input_popup_surface_v2 object exists.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<event name="text_input_rectangle">
|
||||||
|
<description summary="set text input area position">
|
||||||
|
Notify about the position of the area of the text input expressed as a
|
||||||
|
rectangle in surface local coordinates.
|
||||||
|
|
||||||
|
This is a hint to the input method telling it the relative position of
|
||||||
|
the text being entered.
|
||||||
|
</description>
|
||||||
|
<arg name="x" type="int"/>
|
||||||
|
<arg name="y" type="int"/>
|
||||||
|
<arg name="width" type="int"/>
|
||||||
|
<arg name="height" type="int"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor"/>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwp_input_method_keyboard_grab_v2" version="1">
|
||||||
|
<!-- Closely follows wl_keyboard version 6 -->
|
||||||
|
<description summary="keyboard grab">
|
||||||
|
The zwp_input_method_keyboard_grab_v2 interface represents an exclusive
|
||||||
|
grab of the wl_keyboard interface associated with the seat.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<event name="keymap">
|
||||||
|
<description summary="keyboard mapping">
|
||||||
|
This event provides a file descriptor to the client which can be
|
||||||
|
memory-mapped to provide a keyboard mapping description.
|
||||||
|
</description>
|
||||||
|
<arg name="format" type="uint" enum="wl_keyboard.keymap_format"
|
||||||
|
summary="keymap format"/>
|
||||||
|
<arg name="fd" type="fd" summary="keymap file descriptor"/>
|
||||||
|
<arg name="size" type="uint" summary="keymap size, in bytes"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="key">
|
||||||
|
<description summary="key event">
|
||||||
|
A key was pressed or released.
|
||||||
|
The time argument is a timestamp with millisecond granularity, with an
|
||||||
|
undefined base.
|
||||||
|
</description>
|
||||||
|
<arg name="serial" type="uint" summary="serial number of the key event"/>
|
||||||
|
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||||
|
<arg name="key" type="uint" summary="key that produced the event"/>
|
||||||
|
<arg name="state" type="uint" enum="wl_keyboard.key_state"
|
||||||
|
summary="physical state of the key"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="modifiers">
|
||||||
|
<description summary="modifier and group state">
|
||||||
|
Notifies clients that the modifier and/or group state has changed, and
|
||||||
|
it should update its local state.
|
||||||
|
</description>
|
||||||
|
<arg name="serial" type="uint" summary="serial number of the modifiers event"/>
|
||||||
|
<arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
|
||||||
|
<arg name="mods_latched" type="uint" summary="latched modifiers"/>
|
||||||
|
<arg name="mods_locked" type="uint" summary="locked modifiers"/>
|
||||||
|
<arg name="group" type="uint" summary="keyboard layout"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="release" type="destructor">
|
||||||
|
<description summary="release the grab object"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="repeat_info">
|
||||||
|
<description summary="repeat rate and delay">
|
||||||
|
Informs the client about the keyboard's repeat rate and delay.
|
||||||
|
|
||||||
|
This event is sent as soon as the zwp_input_method_keyboard_grab_v2
|
||||||
|
object has been created, and is guaranteed to be received by the
|
||||||
|
client before any key press event.
|
||||||
|
|
||||||
|
Negative values for either rate or delay are illegal. A rate of zero
|
||||||
|
will disable any repeating (regardless of the value of delay).
|
||||||
|
|
||||||
|
This event can be sent later on as well with a new value if necessary,
|
||||||
|
so clients should continue listening for the event past the creation
|
||||||
|
of zwp_input_method_keyboard_grab_v2.
|
||||||
|
</description>
|
||||||
|
<arg name="rate" type="int"
|
||||||
|
summary="the rate of repeating keys in characters per second"/>
|
||||||
|
<arg name="delay" type="int"
|
||||||
|
summary="delay in milliseconds since key down until repeating starts"/>
|
||||||
|
</event>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwp_input_method_manager_v2" version="1">
|
||||||
|
<description summary="input method manager">
|
||||||
|
The input method manager allows the client to become the input method on
|
||||||
|
a chosen seat.
|
||||||
|
|
||||||
|
No more than one input method must be associated with any seat at any
|
||||||
|
given time.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="get_input_method">
|
||||||
|
<description summary="request an input method object">
|
||||||
|
Request a new input zwp_input_method_v2 object associated with a given
|
||||||
|
seat.
|
||||||
|
</description>
|
||||||
|
<arg name="seat" type="object" interface="wl_seat"/>
|
||||||
|
<arg name="input_method" type="new_id" interface="zwp_input_method_v2"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the input method manager">
|
||||||
|
Destroys the zwp_input_method_manager_v2 object.
|
||||||
|
|
||||||
|
The zwp_input_method_v2 objects originating from it remain valid.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
||||||
112
proto/virtual-keyboard-unstable-v1.xml
Normal file
112
proto/virtual-keyboard-unstable-v1.xml
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="virtual_keyboard_unstable_v1">
|
||||||
|
<copyright>
|
||||||
|
Copyright © 2008-2011 Kristian Høgsberg
|
||||||
|
Copyright © 2010-2013 Intel Corporation
|
||||||
|
Copyright © 2012-2013 Collabora, Ltd.
|
||||||
|
Copyright © 2018 Purism SPC
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next
|
||||||
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<interface name="zwp_virtual_keyboard_v1" version="1">
|
||||||
|
<description summary="virtual keyboard">
|
||||||
|
The virtual keyboard provides an application with requests which emulate
|
||||||
|
the behaviour of a physical keyboard.
|
||||||
|
|
||||||
|
This interface can be used by clients on its own to provide raw input
|
||||||
|
events, or it can accompany the input method protocol.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="keymap">
|
||||||
|
<description summary="keyboard mapping">
|
||||||
|
Provide a file descriptor to the compositor which can be
|
||||||
|
memory-mapped to provide a keyboard mapping description.
|
||||||
|
</description>
|
||||||
|
<arg name="format" type="uint" enum="wl_keyboard.keymap_format" summary="keymap format"/>
|
||||||
|
<arg name="fd" type="fd" summary="keymap file descriptor"/>
|
||||||
|
<arg name="size" type="uint" summary="keymap size, in bytes"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="no_keymap" value="0" summary="No keymap was set"/>
|
||||||
|
<entry name="invalid_keymap_format" value="1" summary="Invalid keymap format"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<request name="key">
|
||||||
|
<description summary="key event">
|
||||||
|
A key was pressed or released.
|
||||||
|
The time argument is a timestamp with millisecond granularity, with an
|
||||||
|
undefined base. All requests regarding a single object must share the
|
||||||
|
same clock.
|
||||||
|
|
||||||
|
Keymap must be set before issuing this request.
|
||||||
|
|
||||||
|
State carries a value from the key_state enumeration.
|
||||||
|
</description>
|
||||||
|
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||||
|
<arg name="key" type="uint" summary="key that produced the event"/>
|
||||||
|
<arg name="state" type="uint" summary="physical state of the key"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="modifiers">
|
||||||
|
<description summary="modifier and group state">
|
||||||
|
Notifies the compositor that the modifier and/or group state has
|
||||||
|
changed, and it should update state.
|
||||||
|
|
||||||
|
The client should use wl_keyboard.modifiers event to synchronize its
|
||||||
|
internal state with seat state.
|
||||||
|
|
||||||
|
Keymap must be set before issuing this request.
|
||||||
|
</description>
|
||||||
|
<arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
|
||||||
|
<arg name="mods_latched" type="uint" summary="latched modifiers"/>
|
||||||
|
<arg name="mods_locked" type="uint" summary="locked modifiers"/>
|
||||||
|
<arg name="group" type="uint" summary="keyboard layout"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor" since="1">
|
||||||
|
<description summary="destroy the virtual keyboard keyboard object"/>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwp_virtual_keyboard_manager_v1" version="1">
|
||||||
|
<description summary="virtual keyboard manager">
|
||||||
|
A virtual keyboard manager allows an application to provide keyboard
|
||||||
|
input events as if they came from a physical keyboard.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="unauthorized" value="0" summary="client not authorized to use the interface"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<request name="create_virtual_keyboard">
|
||||||
|
<description summary="Create a new virtual keyboard">
|
||||||
|
Creates a new virtual keyboard associated to a seat.
|
||||||
|
|
||||||
|
If the compositor enables a keyboard to perform arbitrary actions, it
|
||||||
|
should present an error when an untrusted client requests a new
|
||||||
|
keyboard.
|
||||||
|
</description>
|
||||||
|
<arg name="seat" type="object" interface="wl_seat"/>
|
||||||
|
<arg name="id" type="new_id" interface="zwp_virtual_keyboard_v1"/>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
||||||
22
srv.c
22
srv.c
@@ -26,16 +26,22 @@ static int
|
|||||||
srvnote(void *v, char *note)
|
srvnote(void *v, char *note)
|
||||||
{
|
{
|
||||||
USED(v);
|
USED(v);
|
||||||
USED(note);
|
if(notefatal(note))
|
||||||
srvunlink();
|
srvunlink();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 0 for a request to make, 1 for a frame that only changes what rides
|
||||||
|
* along with the requests after it, -1 for anything else.
|
||||||
|
*/
|
||||||
static int
|
static int
|
||||||
srvreadreq(int fd, Keyreq *kr, int *want)
|
srvreadreq(int fd, Keyreq *kr, int *want)
|
||||||
{
|
{
|
||||||
uchar req[Ipccaretsz];
|
uchar req[Ipccaretsz];
|
||||||
|
char text[Ipcfieldmax];
|
||||||
int valid;
|
int valid;
|
||||||
|
size_t n;
|
||||||
int32_t x, y, h;
|
int32_t x, y, h;
|
||||||
|
|
||||||
if(ipcreadn(fd, req, Ipcreqsz) < 0)
|
if(ipcreadn(fd, req, Ipcreqsz) < 0)
|
||||||
@@ -62,6 +68,12 @@ srvreadreq(int fd, Keyreq *kr, int *want)
|
|||||||
kr->caret.y = y;
|
kr->caret.y = y;
|
||||||
kr->caret.h = h;
|
kr->caret.h = h;
|
||||||
return 0;
|
return 0;
|
||||||
|
case Ipcsurround:
|
||||||
|
n = ipcsurroundlen(req);
|
||||||
|
if(n > 0 && ipcreadn(fd, text, n) < 0)
|
||||||
|
return -1;
|
||||||
|
stail(&kr->surround, text, n);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -78,7 +90,7 @@ clientthread(void *arg)
|
|||||||
Keyres res;
|
Keyres res;
|
||||||
uchar out[Ipcmaxresp], token;
|
uchar out[Ipcmaxresp], token;
|
||||||
char commit[Maxutf], preedit[Maxutf];
|
char commit[Maxutf], preedit[Maxutf];
|
||||||
int fd, n, ncommit, npreedit, want;
|
int fd, n, ncommit, npreedit, rv, want;
|
||||||
|
|
||||||
fd = (int)(uintptr)arg;
|
fd = (int)(uintptr)arg;
|
||||||
threadsetname("client %d", fd);
|
threadsetname("client %d", fd);
|
||||||
@@ -86,7 +98,9 @@ clientthread(void *arg)
|
|||||||
kr.reply = chancreate(sizeof(Keyres), 0);
|
kr.reply = chancreate(sizeof(Keyres), 0);
|
||||||
kr.owner = &fd;
|
kr.owner = &fd;
|
||||||
kr.clientpre = 1;
|
kr.clientpre = 1;
|
||||||
while(srvreadreq(fd, &kr, &want) >= 0){
|
while((rv = srvreadreq(fd, &kr, &want)) >= 0){
|
||||||
|
if(rv != 0)
|
||||||
|
continue;
|
||||||
if(kr.op != Keycaret)
|
if(kr.op != Keycaret)
|
||||||
kr.clientpre = want;
|
kr.clientpre = want;
|
||||||
chansend(keyc, &kr);
|
chansend(keyc, &kr);
|
||||||
@@ -97,7 +111,7 @@ clientthread(void *arg)
|
|||||||
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
|
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
|
||||||
/* A capability reply is always eaten: it marks the extension. */
|
/* A capability reply is always eaten: it marks the extension. */
|
||||||
n = ipcpackresp(out, sizeof out, kr.op == Keycap || res.eaten,
|
n = ipcpackresp(out, sizeof out, kr.op == Keycap || res.eaten,
|
||||||
commit, ncommit, preedit, npreedit, want);
|
res.del, commit, ncommit, preedit, npreedit, want);
|
||||||
if(n < 0 || ipcsend(fd, out, n) < 0)
|
if(n < 0 || ipcsend(fd, out, n) < 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
17
str.c
17
str.c
@@ -1,7 +1,8 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
/* Fills s from n bytes of UTF-8; whole, valid, and at most Maxrunes. */
|
/* Fills s from n bytes of UTF-8; whole, valid, and at most Maxrunes.
|
||||||
|
* Anything else leaves s empty. */
|
||||||
int
|
int
|
||||||
sinit(Str *s, char *src, int n)
|
sinit(Str *s, char *src, int n)
|
||||||
{
|
{
|
||||||
@@ -24,6 +25,20 @@ sinit(Str *s, char *src, int n)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The last runes of n bytes of UTF-8, as many as a Str holds. */
|
||||||
|
void
|
||||||
|
stail(Str *s, char *src, int n)
|
||||||
|
{
|
||||||
|
Rune r;
|
||||||
|
char *p;
|
||||||
|
int nr;
|
||||||
|
|
||||||
|
nr = utfnlen(src, n);
|
||||||
|
for(p = src; nr > Maxrunes; nr--)
|
||||||
|
p += chartorune(&r, p);
|
||||||
|
sinit(s, p, n - (p - src));
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sclear(Str *s)
|
sclear(Str *s)
|
||||||
{
|
{
|
||||||
|
|||||||
138
strans.c
138
strans.c
@@ -7,17 +7,23 @@ static void *lostowner; /* the context the engine was taken from */
|
|||||||
static Str lost;
|
static Str lost;
|
||||||
static int clientpre; /* the owner draws its own preedit */
|
static int clientpre; /* the owner draws its own preedit */
|
||||||
static Caret caret;
|
static Caret caret;
|
||||||
|
static Str surround; /* the owner's own text just before the cursor */
|
||||||
|
static int takeback; /* runes of it a pick takes back before committing */
|
||||||
static Rune modemark; /* the mode the last key switched to */
|
static Rune modemark; /* the mode the last key switched to */
|
||||||
static int okuriat = -1; /* where Shift marked the okurigana, or -1 */
|
static int okuriat = -1; /* where Shift marked the okurigana, or -1 */
|
||||||
static Drawcmd lastdraw;
|
static Drawcmd lastdraw;
|
||||||
static Emit transjp(Im*, Rune);
|
static Emit transjp(Im*, Rune);
|
||||||
static void backjp(Im*);
|
static void backjp(Im*);
|
||||||
|
|
||||||
/* seed is what Ctrl+H found pending; raw the keys typed since; text both. */
|
/*
|
||||||
|
* back is the client's own text the reading reached into; seed what
|
||||||
|
* Ctrl+H found pending; raw the keys typed since; text all three.
|
||||||
|
*/
|
||||||
typedef struct Search Search;
|
typedef struct Search Search;
|
||||||
struct Search
|
struct Search
|
||||||
{
|
{
|
||||||
int lang;
|
int lang;
|
||||||
|
Str back;
|
||||||
Str seed;
|
Str seed;
|
||||||
Str raw;
|
Str raw;
|
||||||
Str text;
|
Str text;
|
||||||
@@ -91,11 +97,12 @@ movedelta(u32int ks)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Nothing chosen yet: Down takes the first candidate, PageDown pages. */
|
||||||
static void
|
static void
|
||||||
movekouho(int delta)
|
movekouho(int delta)
|
||||||
{
|
{
|
||||||
if(im.sel < 0)
|
if(im.sel < 0)
|
||||||
im.sel = 0;
|
im.sel = delta == Maxdisp ? Maxdisp : 0;
|
||||||
else
|
else
|
||||||
im.sel += delta;
|
im.sel += delta;
|
||||||
if(im.sel < 0)
|
if(im.sel < 0)
|
||||||
@@ -184,6 +191,17 @@ impre(Im *p, Str *s)
|
|||||||
*s = p->pre;
|
*s = p->pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The query as it is still ours to show: the client has the rest. */
|
||||||
|
static void
|
||||||
|
searchpre(Str *s)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
sclear(s);
|
||||||
|
for(i = search.back.n; i < search.text.n; i++)
|
||||||
|
sputr(s, search.text.r[i]);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
snapshot(Drawcmd *dc)
|
snapshot(Drawcmd *dc)
|
||||||
{
|
{
|
||||||
@@ -192,7 +210,7 @@ snapshot(Drawcmd *dc)
|
|||||||
|
|
||||||
memset(dc, 0, sizeof *dc);
|
memset(dc, 0, sizeof *dc);
|
||||||
if(search.lang)
|
if(search.lang)
|
||||||
pre = search.text;
|
searchpre(&pre);
|
||||||
else
|
else
|
||||||
impre(&im, &pre);
|
impre(&im, &pre);
|
||||||
if(!clientpre)
|
if(!clientpre)
|
||||||
@@ -265,7 +283,8 @@ okuriletter(Rune r)
|
|||||||
{
|
{
|
||||||
static char *rows[] = {
|
static char *rows[] = {
|
||||||
"aあ", "iい", "uう", "eえ", "oお",
|
"aあ", "iい", "uう", "eえ", "oお",
|
||||||
"kかきくけこ", "gがぎぐげご", "sさしすせそ", "zざじずぜぞ",
|
"kかきくけこ", "gがぎぐげご", "sさしすせそ", "zざずぜぞ",
|
||||||
|
"jじ",
|
||||||
"tたちつてとっ", "dだぢづでど", "nなにぬねのん", "hはひふへほ",
|
"tたちつてとっ", "dだぢづでど", "nなにぬねのん", "hはひふへほ",
|
||||||
"bばびぶべぼ", "pぱぴぷぺぽ", "mまみむめも", "yやゆよ",
|
"bばびぶべぼ", "pぱぴぷぺぽ", "mまみむめも", "yやゆよ",
|
||||||
"rらりるれろ", "wわ",
|
"rらりるれろ", "wわ",
|
||||||
@@ -625,14 +644,25 @@ emojiquery(void)
|
|||||||
selectfirst();
|
selectfirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The keys typed since Ctrl+H go on composing from the seed. */
|
/*
|
||||||
|
* The keys typed since Ctrl+H go on composing from the seed. A reading
|
||||||
|
* is also the start of the longer words it begins, so its own
|
||||||
|
* conversions come first and theirs follow: 대한 answers 大寒 and, past
|
||||||
|
* them, 大韓民國.
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
hanjaquery(void)
|
hanjaquery(void)
|
||||||
{
|
{
|
||||||
transstr(im.l, &search.seed, &search.raw, &search.text);
|
Str kouho[Maxkouho], tail;
|
||||||
|
int i, n;
|
||||||
|
|
||||||
|
transstr(im.l, &search.seed, &search.raw, &tail);
|
||||||
|
search.text = search.back;
|
||||||
|
sappend(&search.text, &tail);
|
||||||
clearkouho();
|
clearkouho();
|
||||||
im.nkouho = dictlookup(getlang(LangHANJA)->dict, &search.text,
|
n = dictprefix(getlang(LangHANJA)->dict, &search.text, kouho, Maxkouho);
|
||||||
im.kouho, Maxkouho);
|
for(i = 0; i < n; i++)
|
||||||
|
addkouho(&kouho[i]);
|
||||||
selectfirst();
|
selectfirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,6 +703,7 @@ reset(void)
|
|||||||
{
|
{
|
||||||
sclear(&im.pre);
|
sclear(&im.pre);
|
||||||
sclear(&im.raw);
|
sclear(&im.raw);
|
||||||
|
modemark = 0;
|
||||||
okuriat = -1;
|
okuriat = -1;
|
||||||
endsearch();
|
endsearch();
|
||||||
}
|
}
|
||||||
@@ -685,9 +716,12 @@ reset(void)
|
|||||||
static void
|
static void
|
||||||
flush(Str *com)
|
flush(Str *com)
|
||||||
{
|
{
|
||||||
if(search.lang)
|
Str pre;
|
||||||
sappend(com, &search.text);
|
|
||||||
else if(im.sel >= 0)
|
if(search.lang){
|
||||||
|
searchpre(&pre);
|
||||||
|
sappend(com, &pre);
|
||||||
|
}else if(im.sel >= 0)
|
||||||
sappend(com, &im.kouho[im.sel]);
|
sappend(com, &im.kouho[im.sel]);
|
||||||
else
|
else
|
||||||
commitim(&im, com);
|
commitim(&im, com);
|
||||||
@@ -708,6 +742,46 @@ takelost(void *owner, Str *com)
|
|||||||
lostowner = nil;
|
lostowner = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The syllables from i to the end of the client's text, then seed. */
|
||||||
|
static void
|
||||||
|
reachkey(int i, Str *seed, Str *key)
|
||||||
|
{
|
||||||
|
sclear(key);
|
||||||
|
for(; i < surround.n; i++)
|
||||||
|
sputr(key, surround.r[i]);
|
||||||
|
sappend(key, seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ctrl+H converts a word, and a word is committed one syllable at a
|
||||||
|
* time: only its last syllable is still pending. So the reading reaches
|
||||||
|
* back into the text the client already has, as far as the dictionary
|
||||||
|
* still knows the whole of it -- 한자 converts as a word where the 한 is
|
||||||
|
* the client's and only the 자 is ours. Nothing but the dictionary says
|
||||||
|
* where to stop: a reading is syllables, so a key holding a space or a
|
||||||
|
* Hanja leads nowhere and the reach ends there.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
reachback(Str *seed, Str *back)
|
||||||
|
{
|
||||||
|
Str key;
|
||||||
|
Trie *t;
|
||||||
|
int i, best;
|
||||||
|
|
||||||
|
t = getlang(LangHANJA)->dict;
|
||||||
|
best = surround.n;
|
||||||
|
for(i = surround.n - 1; i >= 0; i--){
|
||||||
|
if(surround.n - i + seed->n > Maxrunes)
|
||||||
|
break;
|
||||||
|
reachkey(i, seed, &key);
|
||||||
|
if(trienode(t, &key) >= 0)
|
||||||
|
best = i;
|
||||||
|
}
|
||||||
|
sclear(back);
|
||||||
|
for(i = best; i < surround.n; i++)
|
||||||
|
sputr(back, surround.r[i]);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A search takes over the keys. Hanja converts the pending syllable, so
|
* A search takes over the keys. Hanja converts the pending syllable, so
|
||||||
* it becomes the query; anything else pending is committed.
|
* it becomes the query; anything else pending is committed.
|
||||||
@@ -715,23 +789,30 @@ takelost(void *owner, Str *com)
|
|||||||
static void
|
static void
|
||||||
startsearch(int lang, Str *com)
|
startsearch(int lang, Str *com)
|
||||||
{
|
{
|
||||||
Str seed;
|
Str back, seed;
|
||||||
|
|
||||||
|
sclear(&back);
|
||||||
sclear(&seed);
|
sclear(&seed);
|
||||||
if(lang == LangHANJA && !search.lang && im.sel < 0)
|
if(lang == LangHANJA && !search.lang && im.sel < 0){
|
||||||
impre(&im, &seed);
|
impre(&im, &seed);
|
||||||
else
|
/* Nothing pending is no reading, and reaches into nothing. */
|
||||||
|
if(seed.n != 0)
|
||||||
|
reachback(&seed, &back);
|
||||||
|
}else
|
||||||
flush(com);
|
flush(com);
|
||||||
reset();
|
reset();
|
||||||
search.lang = lang;
|
search.lang = lang;
|
||||||
|
search.back = back;
|
||||||
search.seed = seed;
|
search.seed = seed;
|
||||||
if(lang == LangHANJA)
|
if(lang == LangHANJA)
|
||||||
searchquery();
|
searchquery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The client keeps what the reading reached back into; we take it back. */
|
||||||
static void
|
static void
|
||||||
picksearch(int n, Str *com)
|
picksearch(int n, Str *com)
|
||||||
{
|
{
|
||||||
|
takeback = search.back.n;
|
||||||
sappend(com, &im.kouho[n]);
|
sappend(com, &im.kouho[n]);
|
||||||
endsearch();
|
endsearch();
|
||||||
}
|
}
|
||||||
@@ -769,6 +850,8 @@ searchkey(u32int ks, u32int mod, Str *com)
|
|||||||
if(ks == Kback){
|
if(ks == Kback){
|
||||||
if(search.raw.n != 0)
|
if(search.raw.n != 0)
|
||||||
spopr(&search.raw);
|
spopr(&search.raw);
|
||||||
|
else if(search.back.n != 0)
|
||||||
|
sclear(&search.back); /* the reach undone, in its turn */
|
||||||
else if(search.seed.n != 0)
|
else if(search.seed.n != 0)
|
||||||
spopr(&search.seed);
|
spopr(&search.seed);
|
||||||
else{
|
else{
|
||||||
@@ -841,6 +924,11 @@ transition(u32int ks, u32int mod, Str *com)
|
|||||||
break;
|
break;
|
||||||
case Kmuhenkan: ks = 't'; mod = Mctrl; break;
|
case Kmuhenkan: ks = 't'; mod = Mctrl; break;
|
||||||
}
|
}
|
||||||
|
/* Ctrl+Backspace deletes a word, Ctrl+Tab changes tab: not ours. */
|
||||||
|
if(ks >= Kspec && (mod & ~Mshift)){
|
||||||
|
flush(com);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if(search.lang)
|
if(search.lang)
|
||||||
return searchkey(ks, mod, com);
|
return searchkey(ks, mod, com);
|
||||||
n = movedelta(ks);
|
n = movedelta(ks);
|
||||||
@@ -858,12 +946,15 @@ transition(u32int ks, u32int mod, Str *com)
|
|||||||
if((ks == ' ' || ks == Ktab) && isjp(&im) && haspre(&im) &&
|
if((ks == ' ' || ks == Ktab) && isjp(&im) && haspre(&im) &&
|
||||||
!(mod & ~Mshift)){
|
!(mod & ~Mshift)){
|
||||||
/* Space and Tab convert: they step through the candidates,
|
/* Space and Tab convert: they step through the candidates,
|
||||||
* Katakana last; a reading with none is committed. */
|
* Katakana last. Katakana mode converts nothing, so there
|
||||||
|
* the key only commits, and then it is the application's:
|
||||||
|
* the space between two Katakana words is a space. */
|
||||||
katakouho();
|
katakouho();
|
||||||
if(im.nkouho != 0)
|
if(im.nkouho == 0){
|
||||||
cyclekouho(mod & Mshift ? -1 : 1);
|
|
||||||
else
|
|
||||||
flush(com);
|
flush(com);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
cyclekouho(mod & Mshift ? -1 : 1);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(ks == Ktab || ks == Kret || (ks == Kesc && !isjp(&im))){
|
if(ks == Ktab || ks == Kret || (ks == Kesc && !isjp(&im))){
|
||||||
@@ -881,6 +972,7 @@ transition(u32int ks, u32int mod, Str *com)
|
|||||||
else if(ks == Kesc)
|
else if(ks == Kesc)
|
||||||
reset();
|
reset();
|
||||||
else{
|
else{
|
||||||
|
okuriat = -1; /* the mark goes with the kana it marked */
|
||||||
im.l->back(&im);
|
im.l->back(&im);
|
||||||
if(haspre(&im))
|
if(haspre(&im))
|
||||||
dictqjp();
|
dictqjp();
|
||||||
@@ -938,6 +1030,8 @@ init(void)
|
|||||||
activeowner = nil;
|
activeowner = nil;
|
||||||
lostowner = nil;
|
lostowner = nil;
|
||||||
clientpre = 0;
|
clientpre = 0;
|
||||||
|
sclear(&surround);
|
||||||
|
takeback = 0;
|
||||||
memset(&lastdraw, 0, sizeof lastdraw);
|
memset(&lastdraw, 0, sizeof lastdraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -956,6 +1050,7 @@ imhandlekey(Keyreq *kr)
|
|||||||
sclear(&res.commit);
|
sclear(&res.commit);
|
||||||
sclear(&res.preedit);
|
sclear(&res.preedit);
|
||||||
res.eaten = 1;
|
res.eaten = 1;
|
||||||
|
takeback = 0;
|
||||||
switch(kr->op){
|
switch(kr->op){
|
||||||
case Keyrelease:
|
case Keyrelease:
|
||||||
takelost(kr->owner, &res.commit);
|
takelost(kr->owner, &res.commit);
|
||||||
@@ -981,6 +1076,7 @@ imhandlekey(Keyreq *kr)
|
|||||||
break;
|
break;
|
||||||
case Keypress:
|
case Keypress:
|
||||||
res.eaten = 0;
|
res.eaten = 0;
|
||||||
|
takelost(kr->owner, &res.commit);
|
||||||
if(kr->owner == activeowner)
|
if(kr->owner == activeowner)
|
||||||
clientpre = kr->clientpre;
|
clientpre = kr->clientpre;
|
||||||
if(keymeaningful(kr->ks)){
|
if(keymeaningful(kr->ks)){
|
||||||
@@ -992,6 +1088,7 @@ imhandlekey(Keyreq *kr)
|
|||||||
clientpre = kr->clientpre;
|
clientpre = kr->clientpre;
|
||||||
}
|
}
|
||||||
caret = kr->caret;
|
caret = kr->caret;
|
||||||
|
surround = kr->surround;
|
||||||
res.eaten = transition(kr->ks, kr->mod, &res.commit);
|
res.eaten = transition(kr->ks, kr->mod, &res.commit);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -999,10 +1096,13 @@ imhandlekey(Keyreq *kr)
|
|||||||
redraw();
|
redraw();
|
||||||
if(kr->owner == activeowner){
|
if(kr->owner == activeowner){
|
||||||
if(search.lang)
|
if(search.lang)
|
||||||
res.preedit = search.text;
|
searchpre(&res.preedit);
|
||||||
|
else if(im.sel >= 0)
|
||||||
|
res.preedit = im.kouho[im.sel]; /* what Enter would commit */
|
||||||
else
|
else
|
||||||
impre(&im, &res.preedit);
|
impre(&im, &res.preedit);
|
||||||
}
|
}
|
||||||
|
res.del = takeback;
|
||||||
chansend(kr->reply, &res);
|
chansend(kr->reply, &res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ HOSTCC = cc
|
|||||||
PKG_CONFIG ?= pkg-config
|
PKG_CONFIG ?= pkg-config
|
||||||
CFLAGS ?= -O2 -g
|
CFLAGS ?= -O2 -g
|
||||||
UNIT_CPPFLAGS = -I..
|
UNIT_CPPFLAGS = -I..
|
||||||
UNIT_CFLAGS = -std=c99 -Wall -Wextra
|
UNIT_CFLAGS = -Wall -Wextra
|
||||||
HOST_CFLAGS = -std=c99 -Wall -Wextra
|
HOST_CFLAGS = -std=c99 -Wall -Wextra
|
||||||
TEXT_CFLAGS = $(shell $(PKG_CONFIG) --cflags pangocairo cairo fontconfig)
|
TEXT_CFLAGS = $(shell $(PKG_CONFIG) --cflags pangocairo cairo fontconfig)
|
||||||
TEXT_LIBS = $(shell $(PKG_CONFIG) --libs pangocairo cairo fontconfig)
|
TEXT_LIBS = $(shell $(PKG_CONFIG) --libs pangocairo cairo fontconfig)
|
||||||
@@ -20,7 +20,9 @@ X11_CFLAGS = $(shell $(PKG_CONFIG) --cflags x11)
|
|||||||
X11_LIBS = $(shell $(PKG_CONFIG) --libs x11)
|
X11_LIBS = $(shell $(PKG_CONFIG) --libs x11)
|
||||||
XIM_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
XIM_CFLAGS = $(shell $(PKG_CONFIG) --cflags xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
||||||
XIM_LIBS = $(shell $(PKG_CONFIG) --libs xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
XIM_LIBS = $(shell $(PKG_CONFIG) --libs xcb-imdkit xcb-aux xcb-xkb xkbcommon-x11)
|
||||||
UNIT_LDLIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS)
|
WL_CFLAGS = $(shell $(PKG_CONFIG) --cflags wayland-client xkbcommon)
|
||||||
|
WL_LIBS = $(shell $(PKG_CONFIG) --libs wayland-client xkbcommon)
|
||||||
|
UNIT_LDLIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS) $(WL_LIBS)
|
||||||
|
|
||||||
PROG = unit_test
|
PROG = unit_test
|
||||||
STRESS = stress_test
|
STRESS = stress_test
|
||||||
@@ -33,10 +35,11 @@ LIVE = $(SMOKE) $(FAULT)
|
|||||||
TESTSRC = test_util.c str_test.c trie_test.c \
|
TESTSRC = test_util.c str_test.c trie_test.c \
|
||||||
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
||||||
popup_test.c font_test.c ibus_test.c server_test.c compose_test.c \
|
popup_test.c font_test.c ibus_test.c server_test.c compose_test.c \
|
||||||
xim_adapter_test.c
|
xim_adapter_test.c wl_adapter_test.c
|
||||||
TESTOBJ = $(TESTSRC:.c=.o)
|
TESTOBJ = $(TESTSRC:.c=.o)
|
||||||
|
# imv2.c and vkv1.c are wayland-scanner's; the parent Makefile writes them.
|
||||||
PARENTSRC = str.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
|
PARENTSRC = str.c trie.c dict.c ko.c vi.c ipc.c popup_layout.c \
|
||||||
font.c compose.c
|
font.c compose.c imv2.c vkv1.c
|
||||||
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
PARENTOBJ = $(PARENTSRC:%.c=unit_%.o)
|
||||||
COMMONOBJ = $(TESTOBJ) $(PARENTOBJ)
|
COMMONOBJ = $(TESTOBJ) $(PARENTOBJ)
|
||||||
OBJS = unit_test.o stress_test.o $(COMMONOBJ)
|
OBJS = unit_test.o stress_test.o $(COMMONOBJ)
|
||||||
@@ -86,12 +89,16 @@ gtk_live_test: gtk_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
|||||||
$(LDFLAGS) -o $@ gtk_live_test.c live.c ../ipc.c $(GTK_LIBS) \
|
$(LDFLAGS) -o $@ gtk_live_test.c live.c ../ipc.c $(GTK_LIBS) \
|
||||||
-pthread $(LDLIBS)
|
-pthread $(LDLIBS)
|
||||||
|
|
||||||
../gtk/im-strans.so: ../gtk/main.c ../ipc.c ../ipc.h
|
# The parent make knows what these are built from; always ask it.
|
||||||
|
../gtk/im-strans.so:
|
||||||
$(MAKE) -C ../gtk
|
$(MAKE) -C ../gtk
|
||||||
|
|
||||||
../strans:
|
../strans:
|
||||||
$(MAKE) -C .. strans
|
$(MAKE) -C .. strans
|
||||||
|
|
||||||
|
../imv2.c ../imv2.h ../vkv1.c ../vkv1.h:
|
||||||
|
$(MAKE) -C .. $(@F)
|
||||||
|
|
||||||
xim_live_test: xim_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
xim_live_test: xim_live_test.c $(LIVESRC) ../ipc.c ../ipc.h
|
||||||
$(HOSTCC) $(CPPFLAGS) -I.. $(X11_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
$(HOSTCC) $(CPPFLAGS) -I.. $(X11_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \
|
||||||
$(LDFLAGS) -o $@ xim_live_test.c live.c ../ipc.c $(X11_LIBS) \
|
$(LDFLAGS) -o $@ xim_live_test.c live.c ../ipc.c $(X11_LIBS) \
|
||||||
@@ -124,6 +131,8 @@ ibus_test.o: UNIT_CPPFLAGS += $(IBUS_CFLAGS)
|
|||||||
ibus_test.o: ../ibus.c
|
ibus_test.o: ../ibus.c
|
||||||
xim_adapter_test.o: UNIT_CPPFLAGS += $(XIM_CFLAGS)
|
xim_adapter_test.o: UNIT_CPPFLAGS += $(XIM_CFLAGS)
|
||||||
xim_adapter_test.o: ../xim.c
|
xim_adapter_test.o: ../xim.c
|
||||||
|
wl_adapter_test.o unit_imv2.o unit_vkv1.o: UNIT_CPPFLAGS += $(WL_CFLAGS)
|
||||||
|
wl_adapter_test.o: ../wl.c ../imv2.h ../vkv1.h
|
||||||
server_test.o: ../srv.c
|
server_test.o: ../srv.c
|
||||||
unit_font.o: UNIT_CPPFLAGS += $(TEXT_CFLAGS)
|
unit_font.o: UNIT_CPPFLAGS += $(TEXT_CFLAGS)
|
||||||
unit_compose.o compose_test.o: UNIT_CPPFLAGS += $(IBUS_CFLAGS)
|
unit_compose.o compose_test.o: UNIT_CPPFLAGS += $(IBUS_CFLAGS)
|
||||||
@@ -137,4 +146,5 @@ unit_%.o: ../%.c ../dat.h ../fn.h ../ipc.h
|
|||||||
clean:
|
clean:
|
||||||
rm -f $(OBJS) $(PROG) $(STRESS) $(LIVE)
|
rm -f $(OBJS) $(PROG) $(STRESS) $(LIVE)
|
||||||
|
|
||||||
.PHONY: all check check-live check-stress test clean
|
.PHONY: all check check-live check-stress test clean \
|
||||||
|
../strans ../gtk/im-strans.so
|
||||||
|
|||||||
@@ -3,24 +3,37 @@
|
|||||||
#include "test.h"
|
#include "test.h"
|
||||||
#include <xkbcommon/xkbcommon-keysyms.h>
|
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||||
|
|
||||||
/* A sequence swallows its keys and hands its text back at the end. */
|
/*
|
||||||
|
* A sequence swallows its keys and hands its text back at the end, and
|
||||||
|
* belongs to the one context, in the one frontend, that started it.
|
||||||
|
*/
|
||||||
void
|
void
|
||||||
compose_sequences(struct ct *t)
|
compose_sequences(struct ct *t)
|
||||||
{
|
{
|
||||||
|
static int a, b; /* two input contexts, told apart by address */
|
||||||
static const struct {
|
static const struct {
|
||||||
|
int who;
|
||||||
|
void *owner;
|
||||||
u32int sym;
|
u32int sym;
|
||||||
int composing;
|
int composing;
|
||||||
char *text;
|
char *text;
|
||||||
} keys[] = {
|
} keys[] = {
|
||||||
{ XKB_KEY_a, 0, "" },
|
{ Composexim, &a, XKB_KEY_a, 0, "" },
|
||||||
{ XKB_KEY_dead_acute, 1, "" },
|
{ Composexim, &a, XKB_KEY_dead_acute, 1, "" },
|
||||||
{ XKB_KEY_e, 0, "é" },
|
{ Composexim, &a, XKB_KEY_e, 0, "é" },
|
||||||
{ XKB_KEY_Multi_key, 1, "" },
|
{ Composexim, &a, XKB_KEY_Multi_key, 1, "" },
|
||||||
{ XKB_KEY_o, 1, "" },
|
{ Composexim, &a, XKB_KEY_o, 1, "" },
|
||||||
{ XKB_KEY_c, 0, "©" },
|
{ Composexim, &a, XKB_KEY_c, 0, "©" },
|
||||||
{ XKB_KEY_dead_acute, 1, "" },
|
{ Composexim, &a, XKB_KEY_dead_acute, 1, "" },
|
||||||
{ XKB_KEY_Escape, 1, "" },
|
{ Composexim, &a, XKB_KEY_Escape, 1, "" },
|
||||||
{ XKB_KEY_a, 0, "" },
|
{ Composexim, &a, XKB_KEY_a, 0, "" },
|
||||||
|
/* another context of the same frontend starts over */
|
||||||
|
{ Composexim, &a, XKB_KEY_dead_acute, 1, "" },
|
||||||
|
{ Composexim, &b, XKB_KEY_e, 0, "" },
|
||||||
|
/* another frontend does not touch this one's sequence */
|
||||||
|
{ Composexim, &a, XKB_KEY_dead_acute, 1, "" },
|
||||||
|
{ Composewl, &a, XKB_KEY_o, 0, "" },
|
||||||
|
{ Composexim, &a, XKB_KEY_e, 0, "é" },
|
||||||
};
|
};
|
||||||
char buf[Maxutf];
|
char buf[Maxutf];
|
||||||
int i;
|
int i;
|
||||||
@@ -29,9 +42,15 @@ compose_sequences(struct ct *t)
|
|||||||
return;
|
return;
|
||||||
composeinit();
|
composeinit();
|
||||||
for(i = 0; i < nelem(keys); i++){
|
for(i = 0; i < nelem(keys); i++){
|
||||||
CT_EQ_INT(t, keys[i].composing,
|
CT_EQ_INT(t, keys[i].composing, composekey(keys[i].who,
|
||||||
composekey(keys[i].sym, buf, sizeof buf));
|
keys[i].owner, keys[i].sym, buf, sizeof buf));
|
||||||
CT_EQ_STR(t, keys[i].text, buf);
|
CT_EQ_STR(t, keys[i].text, buf);
|
||||||
}
|
}
|
||||||
|
/* a context that goes takes its half-typed sequence with it */
|
||||||
|
CT_EQ_INT(t, 1,
|
||||||
|
composekey(Composewl, &a, XKB_KEY_dead_acute, buf, sizeof buf));
|
||||||
|
composedrop(Composewl, &a);
|
||||||
|
CT_EQ_INT(t, 0, composekey(Composewl, &a, XKB_KEY_e, buf, sizeof buf));
|
||||||
|
CT_EQ_STR(t, "", buf);
|
||||||
unsetenv("XCOMPOSEFILE");
|
unsetenv("XCOMPOSEFILE");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,6 +140,38 @@ openpersistent(Test *t)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* plan9port ignores a broken pipe: the note handlers run and the daemon
|
||||||
|
* goes on serving, so the endpoints it announced must still be there.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
survivenote(Test *t)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
int fd, ok;
|
||||||
|
|
||||||
|
if(kill(t->firstpid, SIGPIPE) < 0)
|
||||||
|
return fail("send a broken pipe to the first daemon: %s",
|
||||||
|
strerror(errno));
|
||||||
|
pausems(200);
|
||||||
|
if(!daemonalive(&t->first))
|
||||||
|
return fail("first daemon died of a broken pipe");
|
||||||
|
if(lstat(t->l.socket, &st) < 0)
|
||||||
|
return fail("IPC socket taken by a note the daemon survived: %s",
|
||||||
|
strerror(errno));
|
||||||
|
if(lstat(t->l.addrfile, &st) < 0)
|
||||||
|
return fail("IBus address taken by a note the daemon survived: %s",
|
||||||
|
strerror(errno));
|
||||||
|
fd = connectsocket(t->l.socket, nowms() + Calltimeout);
|
||||||
|
if(fd < 0)
|
||||||
|
return fail("connect after a broken pipe: %s", strerror(errno));
|
||||||
|
ok = ipcprobe(fd, "after a broken pipe");
|
||||||
|
if(close(fd) < 0)
|
||||||
|
return fail("close the client opened after a broken pipe: %s",
|
||||||
|
strerror(errno));
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
hardcrash(Test *t)
|
hardcrash(Test *t)
|
||||||
{
|
{
|
||||||
@@ -265,8 +297,8 @@ rejectold(Test *t, char *where)
|
|||||||
static int
|
static int
|
||||||
openreplacement(Test *t)
|
openreplacement(Test *t)
|
||||||
{
|
{
|
||||||
unsigned char selectjp[] = {1, 0, 0, 0, 0};
|
unsigned char selectjp[] = {1, 0, 0, 0, 0, 0};
|
||||||
unsigned char keyk[] = {1, 0, 0, 1, 0, 'k'};
|
unsigned char keyk[] = {1, 0, 0, 0, 1, 0, 'k'};
|
||||||
char path[96];
|
char path[96];
|
||||||
|
|
||||||
t->ipcnew = connectsocket(t->l.socket, nowms() + Calltimeout);
|
t->ipcnew = connectsocket(t->l.socket, nowms() + Calltimeout);
|
||||||
@@ -329,6 +361,8 @@ runrestart(Test *t, char *program, char *mapdir)
|
|||||||
if(!waitready(&t->l, &t->first, t->firstaddress, sizeof t->firstaddress) ||
|
if(!waitready(&t->l, &t->first, t->firstaddress, sizeof t->firstaddress) ||
|
||||||
!privateaddress(t->firstaddress, t->firstpid) || !openpersistent(t))
|
!privateaddress(t->firstaddress, t->firstpid) || !openpersistent(t))
|
||||||
return 0;
|
return 0;
|
||||||
|
if(!survivenote(t))
|
||||||
|
return 0;
|
||||||
if(!hardcrash(t) || !waitipcclosed(t) || !waitbusclosed(t) ||
|
if(!hardcrash(t) || !waitipcclosed(t) || !waitbusclosed(t) ||
|
||||||
!checkstale(t) || !rejectold(t, "after hard crash"))
|
!checkstale(t) || !rejectold(t, "after hard crash"))
|
||||||
return 0;
|
return 0;
|
||||||
@@ -367,6 +401,6 @@ main(int argc, char **argv)
|
|||||||
showerrors(&test.second);
|
showerrors(&test.second);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
printf("daemon hard-crash endpoint recovery: ok\n");
|
printf("daemon endpoints across a note and a hard crash: ok\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ keystroke(u32int ks, u32int mod, Str *com)
|
|||||||
{
|
{
|
||||||
int eaten;
|
int eaten;
|
||||||
|
|
||||||
|
takeback = 0;
|
||||||
eaten = keymeaningful(ks) && transition(ks, mod, com);
|
eaten = keymeaningful(ks) && transition(ks, mod, com);
|
||||||
redraw();
|
redraw();
|
||||||
return eaten;
|
return eaten;
|
||||||
@@ -175,7 +176,7 @@ engine_candidate_shortcut_modifiers(struct ct *t)
|
|||||||
|
|
||||||
static Keyres
|
static Keyres
|
||||||
ownerrequestatcap(void *owner, int cap, int op, Rune key, u32int mod,
|
ownerrequestatcap(void *owner, int cap, int op, Rune key, u32int mod,
|
||||||
Caret *caret)
|
Caret *caret, char *before)
|
||||||
{
|
{
|
||||||
Channel *reply;
|
Channel *reply;
|
||||||
Keyreq req;
|
Keyreq req;
|
||||||
@@ -190,6 +191,8 @@ ownerrequestatcap(void *owner, int cap, int op, Rune key, u32int mod,
|
|||||||
req.mod = mod;
|
req.mod = mod;
|
||||||
if(caret != nil)
|
if(caret != nil)
|
||||||
req.caret = *caret;
|
req.caret = *caret;
|
||||||
|
if(before != nil)
|
||||||
|
req.surround = mkstr(before);
|
||||||
req.reply = reply;
|
req.reply = reply;
|
||||||
imhandlekey(&req);
|
imhandlekey(&req);
|
||||||
chanrecv(reply, &res);
|
chanrecv(reply, &res);
|
||||||
@@ -200,7 +203,7 @@ ownerrequestatcap(void *owner, int cap, int op, Rune key, u32int mod,
|
|||||||
static Keyres
|
static Keyres
|
||||||
ownerrequestat(void *owner, int op, Rune key, u32int mod, Caret *caret)
|
ownerrequestat(void *owner, int op, Rune key, u32int mod, Caret *caret)
|
||||||
{
|
{
|
||||||
return ownerrequestatcap(owner, 0, op, key, mod, caret);
|
return ownerrequestatcap(owner, 0, op, key, mod, caret, nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Keyres
|
static Keyres
|
||||||
@@ -212,7 +215,7 @@ ownerrequest(void *owner, int op, Rune key, u32int mod)
|
|||||||
static Keyres
|
static Keyres
|
||||||
ownerrequestcap(void *owner, int cap, int op, Rune key, u32int mod)
|
ownerrequestcap(void *owner, int cap, int op, Rune key, u32int mod)
|
||||||
{
|
{
|
||||||
return ownerrequestatcap(owner, cap, op, key, mod, nil);
|
return ownerrequestatcap(owner, cap, op, key, mod, nil, nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -258,6 +261,20 @@ engine_active_owner_lifecycle(struct ct *t)
|
|||||||
res = ownerrequest(&c, Keypress, 'k', 0);
|
res = ownerrequest(&c, Keypress, 'k', 0);
|
||||||
CT_EQ_PTR(t, &c, activeowner);
|
CT_EQ_PTR(t, &c, activeowner);
|
||||||
checkstr(t, "next owner first key", "k", &res.preedit);
|
checkstr(t, "next owner first key", "k", &res.preedit);
|
||||||
|
|
||||||
|
/* Typing is being heard from, as much as a reset or a release is. */
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJP);
|
||||||
|
ownerrequest(&a, Keypress, 'k', 0);
|
||||||
|
ownerrequest(&a, Keypress, 'a', 0);
|
||||||
|
ownerrequest(&b, Keypress, 'n', 0);
|
||||||
|
CT_EQ_PTR(t, &b, activeowner);
|
||||||
|
res = ownerrequest(&a, Keypress, 'k', 0);
|
||||||
|
checkstr(t, "the taken owner types and gets its reading back", "か",
|
||||||
|
&res.commit);
|
||||||
|
CT_EQ_PTR(t, &a, activeowner);
|
||||||
|
res = ownerrequest(&b, Keyreset, 0, 0);
|
||||||
|
checkstr(t, "the owner it took from keeps its own", "ん", &res.commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -394,6 +411,7 @@ engine_commit_contract(struct ct *t)
|
|||||||
{ 'd', "ㅇ", "ㅇㅇㅇ" },
|
{ 'd', "ㅇ", "ㅇㅇㅇ" },
|
||||||
{ 'k', "ㅏ", "ㅏㅏㅏ" },
|
{ 'k', "ㅏ", "ㅏㅏㅏ" },
|
||||||
};
|
};
|
||||||
|
Drawcmd dc;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
Str all, com;
|
Str all, com;
|
||||||
char owner;
|
char owner;
|
||||||
@@ -478,6 +496,26 @@ engine_commit_contract(struct ct *t)
|
|||||||
CT_CHECK(t, !keystroke(0xf008, 0, &com));
|
CT_CHECK(t, !keystroke(0xf008, 0, &com));
|
||||||
checkstr(t, "non-ASCII key commit", "か", &com);
|
checkstr(t, "non-ASCII key commit", "か", &com);
|
||||||
CT_EQ_INT(t, 0, im.pre.n);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
|
|
||||||
|
/* A chosen candidate is what the client shows and what Enter
|
||||||
|
* commits; the popup keeps the reading above the list. */
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJP);
|
||||||
|
draindraw(nil);
|
||||||
|
ownerrequestcap(&owner, 1, Keypress, 'k', 0);
|
||||||
|
res = ownerrequestcap(&owner, 1, Keypress, 'a', 0);
|
||||||
|
checkstr(t, "the reading before a candidate", "か", &res.preedit);
|
||||||
|
im.kouho[0] = mkstr("蚊");
|
||||||
|
im.nkouho = 1;
|
||||||
|
res = ownerrequestcap(&owner, 1, Keypress, Kdown, 0);
|
||||||
|
checkstr(t, "the candidate the client shows", "蚊", &res.preedit);
|
||||||
|
clientpre = 0;
|
||||||
|
redraw();
|
||||||
|
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||||||
|
checkstr(t, "the popup keeps the reading", "か", &dc.pre);
|
||||||
|
res = ownerrequestcap(&owner, 1, Keypress, Kret, 0);
|
||||||
|
checkstr(t, "Enter commits what was shown", "蚊", &res.commit);
|
||||||
|
CT_EQ_INT(t, 0, res.preedit.n);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -560,6 +598,15 @@ engine_korean_modifiers_and_backspace(struct ct *t)
|
|||||||
CT_EQ_INT(t, 0, im.pre.n);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
CT_CHECK(t, !keystroke(Kback, 0, &com));
|
CT_CHECK(t, !keystroke(Kback, 0, &com));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangKO);
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "rk", &com)){
|
||||||
|
CT_CHECK(t, !keystroke(Kback, Mctrl, &com));
|
||||||
|
checkstr(t, "Ctrl+Backspace commits and passes", "가", &com);
|
||||||
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -567,6 +614,7 @@ engine_direct_language_modes(struct ct *t)
|
|||||||
{
|
{
|
||||||
Drawcmd dc;
|
Drawcmd dc;
|
||||||
Str com;
|
Str com;
|
||||||
|
char owner;
|
||||||
|
|
||||||
init();
|
init();
|
||||||
draindraw(nil);
|
draindraw(nil);
|
||||||
@@ -607,6 +655,16 @@ engine_direct_language_modes(struct ct *t)
|
|||||||
checkstr(t, "Korean without map", "ㄱ", &im.pre);
|
checkstr(t, "Korean without map", "ㄱ", &im.pre);
|
||||||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
CT_EQ_INT(t, 0, im.pre.n);
|
CT_EQ_INT(t, 0, im.pre.n);
|
||||||
|
|
||||||
|
/* Leaving takes the mark away as surely as typing does. */
|
||||||
|
init();
|
||||||
|
draindraw(nil);
|
||||||
|
ownerrequest(&owner, Keypress, 's', Mctrl);
|
||||||
|
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||||||
|
checkstr(t, "mode mark", "한", &dc.pre);
|
||||||
|
ownerrequest(&owner, Keyrelease, 0, 0);
|
||||||
|
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||||||
|
CT_EQ_INT(t, 0, dc.pre.n);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -837,6 +895,10 @@ engine_candidate_page_movement(struct ct *t)
|
|||||||
{ 32, 22, Kpgdown, 31 },
|
{ 32, 22, Kpgdown, 31 },
|
||||||
{ 32, 31, Kpgup, 22 },
|
{ 32, 31, Kpgup, 22 },
|
||||||
{ 32, 31, Kpgdown, 31 },
|
{ 32, 31, Kpgdown, 31 },
|
||||||
|
{ 32, -1, Kdown, 0 },
|
||||||
|
{ 32, -1, Kup, 0 },
|
||||||
|
{ 32, -1, Kpgdown, 9 },
|
||||||
|
{ 32, -1, Kpgup, 0 },
|
||||||
};
|
};
|
||||||
int first, i, n;
|
int first, i, n;
|
||||||
Str com;
|
Str com;
|
||||||
@@ -1066,6 +1128,8 @@ hanjabegin(Searchfix *f, int lang)
|
|||||||
f->dictlang->dict = trienew();
|
f->dictlang->dict = trienew();
|
||||||
setdict(f->dictlang->dict, "한", "漢 韓");
|
setdict(f->dictlang->dict, "한", "漢 韓");
|
||||||
setdict(f->dictlang->dict, "가", "家");
|
setdict(f->dictlang->dict, "가", "家");
|
||||||
|
setdict(f->dictlang->dict, "민", "民");
|
||||||
|
setdict(f->dictlang->dict, "민국", "民國");
|
||||||
init();
|
init();
|
||||||
im.l = getlang(lang);
|
im.l = getlang(lang);
|
||||||
}
|
}
|
||||||
@@ -1234,6 +1298,13 @@ engine_japanese_candidates(struct ct *t)
|
|||||||
checkstr(t, "then the second", "描く", &im.kouho[1]);
|
checkstr(t, "then the second", "描く", &im.kouho[1]);
|
||||||
checkstr(t, "then the reading's own", "確", &im.kouho[2]);
|
checkstr(t, "then the reading's own", "確", &im.kouho[2]);
|
||||||
}
|
}
|
||||||
|
/* Backspace edits the reading, so the split it marked is gone. */
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
CT_CHECK(t, keystroke('k', 0, &com));
|
||||||
|
CT_CHECK(t, keystroke('u', 0, &com));
|
||||||
|
if(CT_EQ_INT(t, 3, im.nkouho))
|
||||||
|
checkstr(t, "the mark did not outlive its kana", "確",
|
||||||
|
&im.kouho[0]);
|
||||||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
setdict(jp->dict, "いt", "言 行");
|
setdict(jp->dict, "いt", "言 行");
|
||||||
setdict(jp->dict, "いっt", "言 行");
|
setdict(jp->dict, "いっt", "言 行");
|
||||||
@@ -1244,6 +1315,19 @@ engine_japanese_candidates(struct ct *t)
|
|||||||
checkstr(t, "sokuon written in kana", "行った", &im.kouho[1]);
|
checkstr(t, "sokuon written in kana", "行った", &im.kouho[1]);
|
||||||
}
|
}
|
||||||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
/* じ is keyed j, as SKK types it; ざずぜぞ keep z. */
|
||||||
|
setdict(jp->dict, "しんj", "信");
|
||||||
|
setdict(jp->dict, "まz", "混");
|
||||||
|
if(!typekeys(t, "shinjiru", &com))
|
||||||
|
goto cleanup;
|
||||||
|
if(CT_EQ_INT(t, 1, im.nkouho))
|
||||||
|
checkstr(t, "じ okurigana", "信じる", &im.kouho[0]);
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
if(!typekeys(t, "mazeru", &com))
|
||||||
|
goto cleanup;
|
||||||
|
if(CT_EQ_INT(t, 1, im.nkouho))
|
||||||
|
checkstr(t, "ぜ okurigana", "混ぜる", &im.kouho[0]);
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
/* A romaji typo stays in the reading for Backspace to mend. */
|
/* A romaji typo stays in the reading for Backspace to mend. */
|
||||||
if(!typekeys(t, "kanjr", &com))
|
if(!typekeys(t, "kanjr", &com))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
@@ -1388,6 +1472,16 @@ engine_katakana_sequences(struct ct *t)
|
|||||||
checkstr(t, "committed Katakana", cases[i].want, &com);
|
checkstr(t, "committed Katakana", cases[i].want, &com);
|
||||||
}
|
}
|
||||||
CT_EQ_PTR(t, nil, getlang(LangJPK)->dictname);
|
CT_EQ_PTR(t, nil, getlang(LangJPK)->dictname);
|
||||||
|
|
||||||
|
/* Nothing to convert to: the key commits and goes on to the client. */
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangJPK);
|
||||||
|
sclear(&com);
|
||||||
|
if(typekeys(t, "kaku", &com)){
|
||||||
|
CT_CHECK(t, !keystroke(' ', 0, &com));
|
||||||
|
checkstr(t, "Space commits the Katakana", "カク", &com);
|
||||||
|
CT_EQ_INT(t, 0, im.nkouho);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -1882,6 +1976,140 @@ cleanup:
|
|||||||
searchend(&f);
|
searchend(&f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A word is committed one syllable at a time, so Ctrl+H reaches back
|
||||||
|
* into the text the client already has, and a pick takes it back.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
engine_hanja_reaches_back(struct ct *t)
|
||||||
|
{
|
||||||
|
Searchfix f;
|
||||||
|
Keyres res;
|
||||||
|
Str com;
|
||||||
|
char owner;
|
||||||
|
|
||||||
|
hanjabegin(&f, LangKO);
|
||||||
|
surround = mkstr("민");
|
||||||
|
im.pre = mkstr("국");
|
||||||
|
sclear(&com);
|
||||||
|
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)))
|
||||||
|
goto cleanup;
|
||||||
|
checkstr(t, "the client's syllable joined the reading", "민국",
|
||||||
|
&search.text);
|
||||||
|
checkstr(t, "what the client already has", "민", &search.back);
|
||||||
|
CT_EQ_INT(t, 1, im.nkouho);
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "the word converted", "民國", &com);
|
||||||
|
CT_EQ_INT(t, 1, takeback);
|
||||||
|
|
||||||
|
/* Escape gives back what was pending, and nothing of the client's. */
|
||||||
|
surround = mkstr("민");
|
||||||
|
im.pre = mkstr("국");
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
checkstr(t, "only the pending syllable comes back", "국", &im.pre);
|
||||||
|
CT_EQ_INT(t, 0, takeback);
|
||||||
|
|
||||||
|
/* A reading nothing is keyed by stops the reach where it stands. */
|
||||||
|
surround = mkstr("가");
|
||||||
|
im.pre = mkstr("국");
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||||||
|
checkstr(t, "no reach without a reading", "국", &search.text);
|
||||||
|
CT_EQ_INT(t, 0, search.back.n);
|
||||||
|
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||||||
|
|
||||||
|
/* A query without candidates types only what the client lacks. */
|
||||||
|
surround = mkstr("민");
|
||||||
|
im.pre = mkstr("국");
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||||||
|
if(typekeys(t, "r", &com)){
|
||||||
|
CT_EQ_INT(t, 0, im.nkouho);
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "the client's syllable is not typed again", "국ㄱ",
|
||||||
|
&com);
|
||||||
|
CT_EQ_INT(t, 0, takeback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Backspace gives the reach back before it eats what was typed. */
|
||||||
|
surround = mkstr("민");
|
||||||
|
im.pre = mkstr("국");
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||||||
|
checkstr(t, "the reach joined the reading", "민국", &search.text);
|
||||||
|
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||||||
|
checkstr(t, "the client's syllable goes back first", "국",
|
||||||
|
&search.text);
|
||||||
|
CT_EQ_INT(t, 0, search.back.n);
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "only what was typed is committed", "국", &com);
|
||||||
|
CT_EQ_INT(t, 0, takeback);
|
||||||
|
|
||||||
|
/* Nothing pending is no reading, and reaches into nothing. */
|
||||||
|
surround = mkstr("민");
|
||||||
|
sclear(&im.pre);
|
||||||
|
sclear(&com);
|
||||||
|
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||||||
|
CT_EQ_INT(t, 0, search.back.n);
|
||||||
|
CT_EQ_INT(t, 0, im.nkouho);
|
||||||
|
if(typekeys(t, "als", &com)){
|
||||||
|
checkstr(t, "the reading typed is the whole query", "민",
|
||||||
|
&search.text);
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "the client's syllable is not converted", "民",
|
||||||
|
&com);
|
||||||
|
CT_EQ_INT(t, 0, takeback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The frontend's own request carries the text and is told to take it. */
|
||||||
|
init();
|
||||||
|
im.l = getlang(LangKO);
|
||||||
|
ownerrequestatcap(&owner, 1, Keypress, 'r', 0, nil, "민");
|
||||||
|
ownerrequestatcap(&owner, 1, Keypress, 'n', 0, nil, "민");
|
||||||
|
res = ownerrequestatcap(&owner, 1, Keypress, 'r', 0, nil, "민");
|
||||||
|
checkstr(t, "the syllable the request composed", "국", &res.preedit);
|
||||||
|
CT_EQ_INT(t, 0, res.del);
|
||||||
|
ownerrequestatcap(&owner, 1, Keypress, 'h', Mctrl, nil, "민");
|
||||||
|
checkstr(t, "the request's own text joined the reading", "민국",
|
||||||
|
&search.text);
|
||||||
|
res = ownerrequestatcap(&owner, 1, Keypress, Kret, 0, nil, "민");
|
||||||
|
checkstr(t, "the word the request converted", "民國", &res.commit);
|
||||||
|
CT_EQ_INT(t, 1, res.del);
|
||||||
|
cleanup:
|
||||||
|
searchend(&f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A reading is the start of the longer words it begins: its own
|
||||||
|
* conversions come first, and past them theirs.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
engine_hanja_word_prefix(struct ct *t)
|
||||||
|
{
|
||||||
|
Searchfix f;
|
||||||
|
Str com;
|
||||||
|
|
||||||
|
hanjabegin(&f, LangKO);
|
||||||
|
/* Two readings under one prefix may share a word; it shows once. */
|
||||||
|
setdict(f.dictlang->dict, "민민", "民");
|
||||||
|
sclear(&com);
|
||||||
|
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)) ||
|
||||||
|
!typekeys(t, "als", &com))
|
||||||
|
goto cleanup;
|
||||||
|
checkstr(t, "the reading typed so far", "민", &search.text);
|
||||||
|
CT_EQ_INT(t, 2, im.nkouho);
|
||||||
|
checkstr(t, "the reading's own conversion", "民", &im.kouho[0]);
|
||||||
|
checkstr(t, "the word it begins", "民國", &im.kouho[1]);
|
||||||
|
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||||||
|
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||||||
|
checkstr(t, "a word longer than the reading", "民國", &com);
|
||||||
|
cleanup:
|
||||||
|
searchend(&f);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
engine_hanja_korean_keys(struct ct *t)
|
engine_hanja_korean_keys(struct ct *t)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ enum
|
|||||||
Ecaret,
|
Ecaret,
|
||||||
Ekey,
|
Ekey,
|
||||||
Ereset,
|
Ereset,
|
||||||
|
Esurround,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum
|
enum
|
||||||
@@ -55,6 +56,7 @@ struct Event
|
|||||||
int32_t x;
|
int32_t x;
|
||||||
int32_t y;
|
int32_t y;
|
||||||
int32_t h;
|
int32_t h;
|
||||||
|
char text[Ipcfieldmax+1];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Server
|
struct Server
|
||||||
@@ -69,6 +71,7 @@ struct Server
|
|||||||
int eaten;
|
int eaten;
|
||||||
int stall;
|
int stall;
|
||||||
int response;
|
int response;
|
||||||
|
int del;
|
||||||
int nclose;
|
int nclose;
|
||||||
char pre[Ipcfieldmax+1];
|
char pre[Ipcfieldmax+1];
|
||||||
Event ev[Maxevent];
|
Event ev[Maxevent];
|
||||||
@@ -83,6 +86,10 @@ struct Siglog
|
|||||||
char shown[Ipcfieldmax+1];
|
char shown[Ipcfieldmax+1];
|
||||||
char commit[Ipcfieldmax+1];
|
char commit[Ipcfieldmax+1];
|
||||||
int prechanged;
|
int prechanged;
|
||||||
|
char around[64]; /* what the widget answers retrieve-surrounding */
|
||||||
|
int aroundcursor;
|
||||||
|
int deloffset;
|
||||||
|
int deln;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -110,13 +117,14 @@ sendresponse(Server *s, int fd, int eaten, int want, int key)
|
|||||||
unsigned char buf[Ipcmaxresp];
|
unsigned char buf[Ipcmaxresp];
|
||||||
const char *commit;
|
const char *commit;
|
||||||
char pre[Ipcfieldmax+1];
|
char pre[Ipcfieldmax+1];
|
||||||
int n, response, stall;
|
int del, n, response, stall;
|
||||||
size_t ncommit, npreedit;
|
size_t ncommit, npreedit;
|
||||||
|
|
||||||
pthread_mutex_lock(&s->lock);
|
pthread_mutex_lock(&s->lock);
|
||||||
memcpy(pre, s->pre, sizeof pre);
|
memcpy(pre, s->pre, sizeof pre);
|
||||||
response = s->response;
|
response = s->response;
|
||||||
stall = s->stall;
|
stall = s->stall;
|
||||||
|
del = key ? s->del : 0;
|
||||||
pthread_mutex_unlock(&s->lock);
|
pthread_mutex_unlock(&s->lock);
|
||||||
if(stall)
|
if(stall)
|
||||||
return 1;
|
return 1;
|
||||||
@@ -135,7 +143,7 @@ sendresponse(Server *s, int fd, int eaten, int want, int key)
|
|||||||
pre[2] = 'y';
|
pre[2] = 'y';
|
||||||
npreedit = 3;
|
npreedit = 3;
|
||||||
}
|
}
|
||||||
n = ipcpackresp(buf, sizeof buf, eaten, commit, ncommit,
|
n = ipcpackresp(buf, sizeof buf, eaten, del, commit, ncommit,
|
||||||
pre, npreedit, want);
|
pre, npreedit, want);
|
||||||
return n >= 0 && ipcsend(fd, buf, n) == 0;
|
return n >= 0 && ipcsend(fd, buf, n) == 0;
|
||||||
}
|
}
|
||||||
@@ -146,6 +154,7 @@ request(Server *s, int fd)
|
|||||||
unsigned char buf[Ipccaretsz];
|
unsigned char buf[Ipccaretsz];
|
||||||
uint32_t mod, key;
|
uint32_t mod, key;
|
||||||
Event e;
|
Event e;
|
||||||
|
size_t n;
|
||||||
int old, type, want;
|
int old, type, want;
|
||||||
|
|
||||||
if(ipcreadn(fd, buf, Ipcreqsz) < 0)
|
if(ipcreadn(fd, buf, Ipcreqsz) < 0)
|
||||||
@@ -168,6 +177,14 @@ request(Server *s, int fd)
|
|||||||
e.type = Ecaret;
|
e.type = Ecaret;
|
||||||
record(s, &e);
|
record(s, &e);
|
||||||
return 1;
|
return 1;
|
||||||
|
case Ipcsurround:
|
||||||
|
n = ipcsurroundlen(buf);
|
||||||
|
if(n > 0 && ipcreadn(fd, e.text, n) < 0)
|
||||||
|
return 0;
|
||||||
|
e.text[n] = '\0';
|
||||||
|
e.type = Esurround;
|
||||||
|
record(s, &e);
|
||||||
|
return 1;
|
||||||
case Ipckey:
|
case Ipckey:
|
||||||
ipcunpackreq(buf, &want, &mod, &key);
|
ipcunpackreq(buf, &want, &mod, &key);
|
||||||
e.want = want;
|
e.want = want;
|
||||||
@@ -513,6 +530,25 @@ clearlog(Siglog *l)
|
|||||||
memset(l, 0, sizeof *l);
|
memset(l, 0, sizeof *l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The widget's half of the surrounding-text contract. */
|
||||||
|
static gboolean
|
||||||
|
retrievearound(GtkIMContext *ctx, Siglog *l)
|
||||||
|
{
|
||||||
|
if(l->around[0] == '\0')
|
||||||
|
return FALSE;
|
||||||
|
gtk_im_context_set_surrounding(ctx, l->around, -1, l->aroundcursor);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
deletearound(GtkIMContext *ctx, gint offset, gint n, Siglog *l)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
l->deloffset = offset;
|
||||||
|
l->deln = n;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
static GtkIMContext*
|
static GtkIMContext*
|
||||||
newcontext(Siglog *l)
|
newcontext(Siglog *l)
|
||||||
{
|
{
|
||||||
@@ -520,6 +556,8 @@ newcontext(Siglog *l)
|
|||||||
|
|
||||||
ctx = gtk_im_multicontext_new();
|
ctx = gtk_im_multicontext_new();
|
||||||
gtk_im_multicontext_set_context_id(GTK_IM_MULTICONTEXT(ctx), "strans");
|
gtk_im_multicontext_set_context_id(GTK_IM_MULTICONTEXT(ctx), "strans");
|
||||||
|
g_signal_connect(ctx, "retrieve-surrounding", G_CALLBACK(retrievearound), l);
|
||||||
|
g_signal_connect(ctx, "delete-surrounding", G_CALLBACK(deletearound), l);
|
||||||
g_signal_connect(ctx, "preedit-start", G_CALLBACK(prestart), l);
|
g_signal_connect(ctx, "preedit-start", G_CALLBACK(prestart), l);
|
||||||
g_signal_connect(ctx, "preedit-changed", G_CALLBACK(prechange), l);
|
g_signal_connect(ctx, "preedit-changed", G_CALLBACK(prechange), l);
|
||||||
g_signal_connect(ctx, "preedit-end", G_CALLBACK(preend), l);
|
g_signal_connect(ctx, "preedit-end", G_CALLBACK(preend), l);
|
||||||
@@ -570,6 +608,25 @@ newwindow(int x, int y)
|
|||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A GtkEntry gives its context a window of its own, under the toplevel's. */
|
||||||
|
static GdkWindow*
|
||||||
|
entrywindow(GtkWidget *entry)
|
||||||
|
{
|
||||||
|
GdkWindow *w;
|
||||||
|
GList *kids, *l;
|
||||||
|
gpointer u;
|
||||||
|
|
||||||
|
w = NULL;
|
||||||
|
kids = gdk_window_get_children(gtk_widget_get_window(entry));
|
||||||
|
for(l = kids; l != NULL; l = l->next){
|
||||||
|
gdk_window_get_user_data(l->data, &u);
|
||||||
|
if(u == entry)
|
||||||
|
w = l->data;
|
||||||
|
}
|
||||||
|
g_list_free(kids);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
#define Check(c, ...) do{ if(!(c)){ fail(__VA_ARGS__); goto out; } }while(0)
|
#define Check(c, ...) do{ if(!(c)){ fail(__VA_ARGS__); goto out; } }while(0)
|
||||||
|
|
||||||
int
|
int
|
||||||
@@ -578,8 +635,8 @@ main(int argc, char **argv)
|
|||||||
Server srv;
|
Server srv;
|
||||||
Siglog log;
|
Siglog log;
|
||||||
GtkIMContext *ctx, *oldctx, *badctx;
|
GtkIMContext *ctx, *oldctx, *badctx;
|
||||||
GtkWidget *top;
|
GtkWidget *top, *entry;
|
||||||
GdkWindow *win;
|
GdkWindow *win, *entrywin;
|
||||||
GdkRectangle rect;
|
GdkRectangle rect;
|
||||||
Event e;
|
Event e;
|
||||||
static const GtkInputPurpose purposes[] = {
|
static const GtkInputPurpose purposes[] = {
|
||||||
@@ -648,7 +705,7 @@ main(int argc, char **argv)
|
|||||||
Check(eventcount(&srv) == 0, "cursor reporting opened the socket");
|
Check(eventcount(&srv) == 0, "cursor reporting opened the socket");
|
||||||
setpre(&srv, "");
|
setpre(&srv, "");
|
||||||
Check(key(ctx, win, GDK_KEY_a), "initial key was not eaten");
|
Check(key(ctx, win, GDK_KEY_a), "initial key was not eaten");
|
||||||
Check(waitcount(&srv, 3), "initial protocol frames timed out");
|
Check(waitcount(&srv, 4), "initial protocol frames timed out");
|
||||||
e = getevent(&srv, 0);
|
e = getevent(&srv, 0);
|
||||||
Check(e.type == Ecap && e.want == 1, "default preedit capability missing");
|
Check(e.type == Ecap && e.want == 1, "default preedit capability missing");
|
||||||
e = getevent(&srv, 1);
|
e = getevent(&srv, 1);
|
||||||
@@ -663,6 +720,9 @@ main(int argc, char **argv)
|
|||||||
e.x, e.y, e.h, (ox + rect.x) * scale,
|
e.x, e.y, e.h, (ox + rect.x) * scale,
|
||||||
(oy + rect.y) * scale, rect.height * scale);
|
(oy + rect.y) * scale, rect.height * scale);
|
||||||
e = getevent(&srv, 2);
|
e = getevent(&srv, 2);
|
||||||
|
Check(e.type == Esurround && e.text[0] == '\0',
|
||||||
|
"a widget that keeps no text sent some");
|
||||||
|
e = getevent(&srv, 3);
|
||||||
Check(e.type == Ekey && e.want == 1, "initial key framing changed");
|
Check(e.type == Ekey && e.want == 1, "initial key framing changed");
|
||||||
Check(log.n == 0, "initial empty preedit emitted %s", log.event);
|
Check(log.n == 0, "initial empty preedit emitted %s", log.event);
|
||||||
setpre(&srv, "ㅋ");
|
setpre(&srv, "ㅋ");
|
||||||
@@ -757,6 +817,66 @@ main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
g_object_set(ctx, "input-purpose", GTK_INPUT_PURPOSE_FREE_FORM, NULL);
|
g_object_set(ctx, "input-purpose", GTK_INPUT_PURPOSE_FREE_FORM, NULL);
|
||||||
|
|
||||||
|
/* An entry hidden the older way is private too, purpose or no purpose. */
|
||||||
|
entry = gtk_entry_new();
|
||||||
|
gtk_container_add(GTK_CONTAINER(top), entry);
|
||||||
|
gtk_widget_show(entry);
|
||||||
|
pump();
|
||||||
|
entrywin = entrywindow(entry);
|
||||||
|
Check(entrywin != NULL, "the test GtkEntry has no window of its own");
|
||||||
|
first = eventcount(&srv);
|
||||||
|
gtk_im_context_set_client_window(ctx, entrywin);
|
||||||
|
/* The caret the new window invalidates must land before keys are counted. */
|
||||||
|
Check(waitcount(&srv, first + 1) && getevent(&srv, first).type == Ecaret,
|
||||||
|
"the entry window did not renegotiate the caret");
|
||||||
|
setpre(&srv, "");
|
||||||
|
gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
|
||||||
|
clearlog(&log);
|
||||||
|
first = eventcount(&srv);
|
||||||
|
Check(key(ctx, entrywin, GDK_KEY_y), "hidden-entry fallback did not commit");
|
||||||
|
Check(eventcount(&srv) == first && strcmp(log.commit, "y") == 0,
|
||||||
|
"hidden-entry key reached daemon or did not commit");
|
||||||
|
gtk_entry_set_visibility(GTK_ENTRY(entry), TRUE);
|
||||||
|
clearlog(&log);
|
||||||
|
first = eventcount(&srv);
|
||||||
|
Check(key(ctx, entrywin, GDK_KEY_y), "visible-entry key was not eaten");
|
||||||
|
Check(waitcount(&srv, first + 1) && log.commit[0] == '\0',
|
||||||
|
"visible-entry key did not reach the daemon");
|
||||||
|
gtk_im_context_set_client_window(ctx, win);
|
||||||
|
|
||||||
|
/* The widget's own text goes over, and a take-back comes back. */
|
||||||
|
setpre(&srv, "");
|
||||||
|
clearlog(&log);
|
||||||
|
snprintf(log.around, sizeof log.around, "%s", "가나한");
|
||||||
|
log.aroundcursor = 9;
|
||||||
|
first = eventcount(&srv);
|
||||||
|
Check(key(ctx, win, GDK_KEY_j), "surrounding-text key was not eaten");
|
||||||
|
Check(waitcount(&srv, first + 2), "surrounding text did not reach the daemon");
|
||||||
|
e = getevent(&srv, first);
|
||||||
|
Check(e.type == Esurround && strcmp(e.text, "가나한") == 0,
|
||||||
|
"the daemon was sent %s, not the text before the cursor", e.text);
|
||||||
|
Check(getevent(&srv, first + 1).type == Ekey,
|
||||||
|
"the text did not ride ahead of the key");
|
||||||
|
/* Sent again only when it changes. */
|
||||||
|
first = eventcount(&srv);
|
||||||
|
Check(key(ctx, win, GDK_KEY_j), "second surrounding-text key was not eaten");
|
||||||
|
Check(waitcount(&srv, first + 1) && getevent(&srv, first).type == Ekey,
|
||||||
|
"unchanged surrounding text was sent again");
|
||||||
|
setreply(&srv, 1, Rnormal, 0);
|
||||||
|
pthread_mutex_lock(&srv.lock);
|
||||||
|
srv.del = 2;
|
||||||
|
pthread_mutex_unlock(&srv.lock);
|
||||||
|
log.deln = 0;
|
||||||
|
Check(key(ctx, win, GDK_KEY_j), "take-back key was not eaten");
|
||||||
|
Check(log.deloffset == -2 && log.deln == 2,
|
||||||
|
"the widget was told to take back %d runes at %d", log.deln,
|
||||||
|
log.deloffset);
|
||||||
|
pthread_mutex_lock(&srv.lock);
|
||||||
|
srv.del = 0;
|
||||||
|
pthread_mutex_unlock(&srv.lock);
|
||||||
|
log.around[0] = '\0';
|
||||||
|
|
||||||
setpre(&srv, "reset");
|
setpre(&srv, "reset");
|
||||||
clearlog(&log);
|
clearlog(&log);
|
||||||
Check(key(ctx, win, GDK_KEY_e), "reset setup key was not eaten");
|
Check(key(ctx, win, GDK_KEY_e), "reset setup key was not eaten");
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ struct Log
|
|||||||
int transfer;
|
int transfer;
|
||||||
int atext;
|
int atext;
|
||||||
int aclear;
|
int aclear;
|
||||||
|
int retake;
|
||||||
|
int retook;
|
||||||
char revent[16];
|
char revent[16];
|
||||||
int rn;
|
int rn;
|
||||||
int repeat;
|
int repeat;
|
||||||
@@ -28,6 +30,12 @@ struct Log
|
|||||||
int rcommit;
|
int rcommit;
|
||||||
int rdone;
|
int rdone;
|
||||||
int flushed;
|
int flushed;
|
||||||
|
int hanja;
|
||||||
|
int required;
|
||||||
|
int deleted;
|
||||||
|
int delbefore;
|
||||||
|
int delcount;
|
||||||
|
int converted;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -140,6 +148,17 @@ commit(IBusInputContext *ctx, IBusText *text, void *arg)
|
|||||||
log->commit++;
|
log->commit++;
|
||||||
s = ibus_text_get_text(text);
|
s = ibus_text_get_text(text);
|
||||||
attrs = ibus_text_get_attributes(text);
|
attrs = ibus_text_get_attributes(text);
|
||||||
|
if(log->hanja){
|
||||||
|
/* The word converted, with the client\'s own syllable taken back. */
|
||||||
|
if(strcmp(s, "漢字") == 0 && log->deleted && log->delbefore == -1 &&
|
||||||
|
log->delcount == 1)
|
||||||
|
log->converted = 1;
|
||||||
|
else
|
||||||
|
log->invalid = 1;
|
||||||
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(log->repeat == 3){
|
if(log->repeat == 3){
|
||||||
/* Focus loss hands the pending syllable back as a commit. */
|
/* Focus loss hands the pending syllable back as a commit. */
|
||||||
if(strcmp(s, "ㅋ") == 0)
|
if(strcmp(s, "ㅋ") == 0)
|
||||||
@@ -161,6 +180,17 @@ commit(IBusInputContext *ctx, IBusText *text, void *arg)
|
|||||||
g_main_loop_quit(log->loop);
|
g_main_loop_quit(log->loop);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(log->retake){
|
||||||
|
/* Typing again takes back the reading the other context took. */
|
||||||
|
if(strcmp(s, "k") == 0)
|
||||||
|
log->retook = 1;
|
||||||
|
else
|
||||||
|
log->invalid = 1;
|
||||||
|
log->retake = 0;
|
||||||
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(strcmp(s, "か") != 0 || attrs == NULL ||
|
if(strcmp(s, "か") != 0 || attrs == NULL ||
|
||||||
ibus_attr_list_get(attrs, 0) != NULL)
|
ibus_attr_list_get(attrs, 0) != NULL)
|
||||||
log->invalid = 1;
|
log->invalid = 1;
|
||||||
@@ -171,6 +201,32 @@ commit(IBusInputContext *ctx, IBusText *text, void *arg)
|
|||||||
g_main_loop_quit(log->loop);
|
g_main_loop_quit(log->loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
required(IBusInputContext *ctx, void *arg)
|
||||||
|
{
|
||||||
|
Log *log;
|
||||||
|
|
||||||
|
(void)ctx;
|
||||||
|
log = arg;
|
||||||
|
log->required = 1;
|
||||||
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
deleted(IBusInputContext *ctx, gint offset, guint n, void *arg)
|
||||||
|
{
|
||||||
|
Log *log;
|
||||||
|
|
||||||
|
(void)ctx;
|
||||||
|
log = arg;
|
||||||
|
log->deleted = 1;
|
||||||
|
log->delbefore = offset;
|
||||||
|
log->delcount = n;
|
||||||
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||||
|
g_main_loop_quit(log->loop);
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
timeout(void *arg)
|
timeout(void *arg)
|
||||||
{
|
{
|
||||||
@@ -201,7 +257,8 @@ int
|
|||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
IBusBus *bus;
|
IBusBus *bus;
|
||||||
IBusInputContext *a, *b;
|
IBusInputContext *a, *b, *c;
|
||||||
|
IBusText *around;
|
||||||
Log log;
|
Log log;
|
||||||
int ok;
|
int ok;
|
||||||
|
|
||||||
@@ -216,7 +273,7 @@ main(int argc, char **argv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
memset(&log, 0, sizeof log);
|
memset(&log, 0, sizeof log);
|
||||||
b = NULL;
|
b = c = NULL;
|
||||||
ibus_init();
|
ibus_init();
|
||||||
bus = ibus_bus_new();
|
bus = ibus_bus_new();
|
||||||
if(bus == NULL || !ibus_bus_is_connected(bus)){
|
if(bus == NULL || !ibus_bus_is_connected(bus)){
|
||||||
@@ -259,8 +316,9 @@ main(int argc, char **argv)
|
|||||||
ibus_input_context_focus_out(b);
|
ibus_input_context_focus_out(b);
|
||||||
}
|
}
|
||||||
ibus_input_context_focus_in(a);
|
ibus_input_context_focus_in(a);
|
||||||
|
log.retake = 1;
|
||||||
ok = ok && ibus_input_context_process_key_event(a, 's', 0,
|
ok = ok && ibus_input_context_process_key_event(a, 's', 0,
|
||||||
IBUS_CONTROL_MASK);
|
IBUS_CONTROL_MASK) && waitflag(&log, &log.retook);
|
||||||
log.repeat = 1;
|
log.repeat = 1;
|
||||||
ok = ok && ibus_input_context_process_key_event(a, 'z', 0, 0) &&
|
ok = ok && ibus_input_context_process_key_event(a, 'z', 0, 0) &&
|
||||||
waitflag(&log, &log.rfirst);
|
waitflag(&log, &log.rfirst);
|
||||||
@@ -272,22 +330,57 @@ main(int argc, char **argv)
|
|||||||
log.repeat = 3;
|
log.repeat = 3;
|
||||||
ibus_input_context_focus_out(a);
|
ibus_input_context_focus_out(a);
|
||||||
ok = ok && waitflag(&log, &log.flushed);
|
ok = ok && waitflag(&log, &log.flushed);
|
||||||
if(log.legacy == 0 || log.modern != 0 || log.commit != 3 ||
|
/*
|
||||||
log.invalid || !log.done || !log.atext || !log.aclear ||
|
* A word is committed a syllable at a time, so the Hanja key must
|
||||||
log.clearctx != a || !log.rdone)
|
* reach into what the client already holds and ask for it back.
|
||||||
|
*/
|
||||||
|
c = ibus_bus_create_input_context(bus, "strans-libibus-smoke-c");
|
||||||
|
if(c == NULL)
|
||||||
ok = 0;
|
ok = 0;
|
||||||
|
else{
|
||||||
|
g_signal_connect(c, "commit-text", G_CALLBACK(commit), &log);
|
||||||
|
g_signal_connect(c, "require-surrounding-text",
|
||||||
|
G_CALLBACK(required), &log);
|
||||||
|
g_signal_connect(c, "delete-surrounding-text",
|
||||||
|
G_CALLBACK(deleted), &log);
|
||||||
|
log.repeat = 0;
|
||||||
|
ibus_input_context_set_capabilities(c,
|
||||||
|
IBUS_CAP_PREEDIT_TEXT|IBUS_CAP_SURROUNDING_TEXT);
|
||||||
|
ibus_input_context_focus_in(c);
|
||||||
|
ok = ok && waitflag(&log, &log.required);
|
||||||
|
around = ibus_text_new_from_string("저는 한");
|
||||||
|
ibus_input_context_set_surrounding_text(c, around, 4, 4);
|
||||||
|
log.hanja = 1;
|
||||||
|
ok = ok && ibus_input_context_process_key_event(c, 's', 0,
|
||||||
|
IBUS_CONTROL_MASK) &&
|
||||||
|
ibus_input_context_process_key_event(c, 'w', 0, 0) &&
|
||||||
|
ibus_input_context_process_key_event(c, 'k', 0, 0) &&
|
||||||
|
ibus_input_context_process_key_event(c, 'h', 0,
|
||||||
|
IBUS_CONTROL_MASK) &&
|
||||||
|
ibus_input_context_process_key_event(c, IBUS_KEY_Return,
|
||||||
|
0, 0) && waitflag(&log, &log.converted);
|
||||||
|
log.hanja = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(log.legacy == 0 || log.modern != 0 || log.commit != 5 ||
|
||||||
|
log.invalid || !log.done || !log.atext || !log.aclear ||
|
||||||
|
!log.retook || log.clearctx != a || !log.rdone || !log.converted)
|
||||||
|
ok = 0;
|
||||||
|
if(c != NULL)
|
||||||
|
g_object_unref(c);
|
||||||
if(b != NULL)
|
if(b != NULL)
|
||||||
g_object_unref(b);
|
g_object_unref(b);
|
||||||
g_object_unref(a);
|
g_object_unref(a);
|
||||||
g_object_unref(bus);
|
g_object_unref(bus);
|
||||||
if(!ok){
|
if(!ok){
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"ibus_client_smoke: legacy=%d modern=%d commit=%d invalid=%d preedit=%d committed=%d transfer-text=%d a-clear=%d repeat=%s flushed=%d\n",
|
"ibus_client_smoke: legacy=%d modern=%d commit=%d invalid=%d preedit=%d committed=%d transfer-text=%d a-clear=%d retook=%d repeat=%s flushed=%d required=%d deleted=%d,%d converted=%d\n",
|
||||||
log.legacy, log.modern, log.commit, log.invalid,
|
log.legacy, log.modern, log.commit, log.invalid,
|
||||||
log.sawpreedit, log.sawcommit, log.atext, log.aclear,
|
log.sawpreedit, log.sawcommit, log.atext, log.aclear,
|
||||||
log.revent, log.flushed);
|
log.retook, log.revent, log.flushed, log.required,
|
||||||
|
log.delbefore, log.delcount, log.converted);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
printf("official libibus client preedit, commit, owner clear, and focus-out hand-back: ok\n");
|
printf("official libibus client preedit, commit, owner clear, take-back and focus-out hand-back: ok\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,6 +128,8 @@ runcontract(char *address)
|
|||||||
"SetEngine",
|
"SetEngine",
|
||||||
"SetCapabilities",
|
"SetCapabilities",
|
||||||
"SetCursorLocation",
|
"SetCursorLocation",
|
||||||
|
"SetCursorLocationRelative",
|
||||||
|
"SetSurroundingText",
|
||||||
};
|
};
|
||||||
DBusConnection *conn;
|
DBusConnection *conn;
|
||||||
char path[96];
|
char path[96];
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ struct Ibusfix
|
|||||||
{
|
{
|
||||||
Channel *oldreply;
|
Channel *oldreply;
|
||||||
Pump pump;
|
Pump pump;
|
||||||
|
/* A fake connection is nothing but an address to tell two apart. */
|
||||||
|
char fake[2];
|
||||||
DBusConnection *c1;
|
DBusConnection *c1;
|
||||||
DBusConnection *c2;
|
DBusConnection *c2;
|
||||||
};
|
};
|
||||||
@@ -81,8 +83,8 @@ cleanup:
|
|||||||
CT_CHECK(t, rmdir(root) == 0);
|
CT_CHECK(t, rmdir(root) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static void
|
||||||
ibusbegin(struct ct *t, Ibusfix *f)
|
ibusbegin(Ibusfix *f)
|
||||||
{
|
{
|
||||||
Drawcmd dc;
|
Drawcmd dc;
|
||||||
|
|
||||||
@@ -97,9 +99,8 @@ ibusbegin(struct ct *t, Ibusfix *f)
|
|||||||
f->oldreply = replyc;
|
f->oldreply = replyc;
|
||||||
replyc = chancreate(sizeof(Keyres), 0);
|
replyc = chancreate(sizeof(Keyres), 0);
|
||||||
pumpstart(&f->pump, Maxcontexts);
|
pumpstart(&f->pump, Maxcontexts);
|
||||||
f->c1 = malloc(1);
|
f->c1 = (DBusConnection*)&f->fake[0];
|
||||||
f->c2 = malloc(1);
|
f->c2 = (DBusConnection*)&f->fake[1];
|
||||||
return CT_CHECK(t, f->c1 != nil && f->c2 != nil);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -119,8 +120,6 @@ ibusend(Ibusfix *f)
|
|||||||
testengineinit(LangEN);
|
testengineinit(LangEN);
|
||||||
while(channbrecv(drawc, &dc) > 0)
|
while(channbrecv(drawc, &dc) > 0)
|
||||||
;
|
;
|
||||||
free(f->c1);
|
|
||||||
free(f->c2);
|
|
||||||
chanfree(replyc);
|
chanfree(replyc);
|
||||||
replyc = f->oldreply;
|
replyc = f->oldreply;
|
||||||
}
|
}
|
||||||
@@ -168,8 +167,7 @@ ibus_capability_policy(struct ct *t)
|
|||||||
Ictx *a, *b;
|
Ictx *a, *b;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
|
|
||||||
if(!ibusbegin(t, &f))
|
ibusbegin(&f);
|
||||||
goto cleanup;
|
|
||||||
a = newcontext(f.c1, "/context/cap-a");
|
a = newcontext(f.c1, "/context/cap-a");
|
||||||
b = newcontext(f.c2, "/context/cap-b");
|
b = newcontext(f.c2, "/context/cap-b");
|
||||||
if(!CT_CHECK(t, a != nil && b != nil))
|
if(!CT_CHECK(t, a != nil && b != nil))
|
||||||
@@ -213,8 +211,8 @@ ibus_private_input_policy(struct ct *t)
|
|||||||
{
|
{
|
||||||
static const struct { u32int purpose; int want; } cases[] = {
|
static const struct { u32int purpose; int want; } cases[] = {
|
||||||
{ 0, 0 },
|
{ 0, 0 },
|
||||||
{ Ibuspurposepassword, 1 },
|
{ Purposepassword, 1 },
|
||||||
{ Ibuspurposepin, 1 },
|
{ Purposepin, 1 },
|
||||||
{ 37, 0 },
|
{ 37, 0 },
|
||||||
};
|
};
|
||||||
Ibusfix f;
|
Ibusfix f;
|
||||||
@@ -223,8 +221,7 @@ ibus_private_input_policy(struct ct *t)
|
|||||||
char text[Maxutf];
|
char text[Maxutf];
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if(!ibusbegin(t, &f))
|
ibusbegin(&f);
|
||||||
goto cleanup;
|
|
||||||
ctx = newcontext(f.c1, "/context/private");
|
ctx = newcontext(f.c1, "/context/private");
|
||||||
if(!CT_CHECK(t, ctx != nil))
|
if(!CT_CHECK(t, ctx != nil))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
@@ -239,7 +236,7 @@ ibus_private_input_policy(struct ct *t)
|
|||||||
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
||||||
res = contextkey(t, &f, ctx, 'k', 0);
|
res = contextkey(t, &f, ctx, 'k', 0);
|
||||||
checkstr(t, "preedit", "k", &res.preedit);
|
checkstr(t, "preedit", "k", &res.preedit);
|
||||||
ctx->purpose = Ibuspurposepassword;
|
ctx->purpose = Purposepassword;
|
||||||
memset(&res, 0, sizeof res);
|
memset(&res, 0, sizeof res);
|
||||||
CT_CHECK(t, !processkey(ctx, 'x', 0, text, sizeof text, &res));
|
CT_CHECK(t, !processkey(ctx, 'x', 0, text, sizeof text, &res));
|
||||||
CT_CHECK(t, !res.eaten);
|
CT_CHECK(t, !res.eaten);
|
||||||
@@ -274,8 +271,7 @@ ibus_context_lifecycle(struct ct *t)
|
|||||||
Caret at;
|
Caret at;
|
||||||
char text[Maxutf];
|
char text[Maxutf];
|
||||||
|
|
||||||
if(!ibusbegin(t, &f))
|
ibusbegin(&f);
|
||||||
goto cleanup;
|
|
||||||
a = newcontext(f.c1, "/context/one");
|
a = newcontext(f.c1, "/context/one");
|
||||||
b = newcontext(f.c2, "/context/one");
|
b = newcontext(f.c2, "/context/one");
|
||||||
if(!CT_CHECK(t, a != nil && b != nil && a != b))
|
if(!CT_CHECK(t, a != nil && b != nil && a != b))
|
||||||
@@ -387,8 +383,7 @@ ibus_active_release_lifecycle(struct ct *t)
|
|||||||
Ictx *ctx, *reused;
|
Ictx *ctx, *reused;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
|
|
||||||
if(!ibusbegin(t, &f))
|
ibusbegin(&f);
|
||||||
goto cleanup;
|
|
||||||
ctx = newcontext(f.c1, "/context/reset");
|
ctx = newcontext(f.c1, "/context/reset");
|
||||||
if(!CT_CHECK(t, ctx != nil))
|
if(!CT_CHECK(t, ctx != nil))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ typedef struct Response Response;
|
|||||||
struct Response
|
struct Response
|
||||||
{
|
{
|
||||||
int eaten;
|
int eaten;
|
||||||
|
int del;
|
||||||
char commit[Ipcfieldmax+1];
|
char commit[Ipcfieldmax+1];
|
||||||
char preedit[Ipcfieldmax+1];
|
char preedit[Ipcfieldmax+1];
|
||||||
};
|
};
|
||||||
@@ -50,7 +51,8 @@ readresponseuntil(int fd, int want, Response *res, int64_t deadline,
|
|||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
res->eaten = hdr[0] != 0;
|
res->eaten = hdr[0] != 0;
|
||||||
n = getlen(hdr + 1);
|
res->del = hdr[1];
|
||||||
|
n = getlen(hdr + 2);
|
||||||
if(n > Ipcfieldmax){
|
if(n > Ipcfieldmax){
|
||||||
fail("invalid commit length %zu", n);
|
fail("invalid commit length %zu", n);
|
||||||
return -1;
|
return -1;
|
||||||
@@ -119,6 +121,18 @@ sendcap(int fd, int cap)
|
|||||||
return ipcsend(fd, req, sizeof req) == 0;
|
return ipcsend(fd, req, sizeof req) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sendsurround(int fd, char *text)
|
||||||
|
{
|
||||||
|
unsigned char req[Ipcreqsz];
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
n = strlen(text);
|
||||||
|
ipcpacksurround(req, n);
|
||||||
|
return ipcsend(fd, req, sizeof req) == 0 &&
|
||||||
|
(n == 0 || ipcsend(fd, text, n) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
||||||
char *where)
|
char *where)
|
||||||
@@ -152,6 +166,33 @@ requestkey(int fd, int want, uint32_t mod, uint32_t key, int eaten,
|
|||||||
return expectresponse(fd, want, eaten, commit, preedit, where);
|
return expectresponse(fd, want, eaten, commit, preedit, where);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The Korean habit: 한자 typed, then the Hanja key. The 한 is the
|
||||||
|
* client's by then, so the daemon must ask for it back and answer with
|
||||||
|
* the whole word.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
requesthanja(int fd)
|
||||||
|
{
|
||||||
|
Response res;
|
||||||
|
|
||||||
|
if(!sendsurround(fd, "저는 한"))
|
||||||
|
return fail("send surrounding text: %s", strerror(errno));
|
||||||
|
if(!requestkey(fd, 1, Mctrl, 's', 1, "", "", "select Korean") ||
|
||||||
|
!requestkey(fd, 1, 0, 'w', 1, "", "ㅈ", "jamo") ||
|
||||||
|
!requestkey(fd, 1, 0, 'k', 1, "", "자", "syllable") ||
|
||||||
|
!requestkey(fd, 1, Mctrl, 'h', 1, "", "자", "Hanja search"))
|
||||||
|
return 0;
|
||||||
|
if(!sendkey(fd, 1, 0, Kret))
|
||||||
|
return fail("send Hanja pick: %s", strerror(errno));
|
||||||
|
if(readresponseuntil(fd, 1, &res, nowms() + Calltimeout, 0) != 1)
|
||||||
|
return 0;
|
||||||
|
if(strcmp(res.commit, "漢字") != 0 || res.del != 1)
|
||||||
|
return fail("Hanja pick committed %s and took back %d",
|
||||||
|
res.commit, res.del);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
tryclient(char *path, int64_t deadline, int *client)
|
tryclient(char *path, int64_t deadline, int *client)
|
||||||
{
|
{
|
||||||
@@ -258,7 +299,8 @@ runsmoke(char *path)
|
|||||||
requestkey(client, 1, Mctrl, 'n', 1, "", "",
|
requestkey(client, 1, Mctrl, 'n', 1, "", "",
|
||||||
"select Japanese") &&
|
"select Japanese") &&
|
||||||
requestkey(client, 1, 0, 'k', 1, "", "k", "preedit") &&
|
requestkey(client, 1, 0, 'k', 1, "", "k", "preedit") &&
|
||||||
requestreset(client, 1, 1, "k", "", "reset");
|
requestreset(client, 1, 1, "k", "", "reset") &&
|
||||||
|
requesthanja(client);
|
||||||
close(client);
|
close(client);
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,34 +177,37 @@ ipc_runtime_path(struct ct *t)
|
|||||||
void
|
void
|
||||||
ipc_response_pack_boundaries(struct ct *t)
|
ipc_response_pack_boundaries(struct ct *t)
|
||||||
{
|
{
|
||||||
static const uchar withpre[] = { 1, 1, 0, 'A', 2, 0, 'x', 'y' };
|
static const uchar withpre[] = { 1, 3, 1, 0, 'A', 2, 0, 'x', 'y' };
|
||||||
static const uchar withoutpre[] = { 1, 1, 0, 'A' };
|
static const uchar withoutpre[] = { 1, 0, 1, 0, 'A' };
|
||||||
uchar out[Ipcmaxresp+1], field[Ipcfieldmax];
|
uchar out[Ipcmaxresp+1], field[Ipcfieldmax];
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
n = ipcpackresp(out, sizeof out, 7, "A", 1, "xy", 2, 1);
|
n = ipcpackresp(out, sizeof out, 7, 3, "A", 1, "xy", 2, 1);
|
||||||
CT_EQ_INT(t, sizeof withpre, n);
|
CT_EQ_INT(t, sizeof withpre, n);
|
||||||
CT_EQ_MEM(t, withpre, out, sizeof withpre);
|
CT_EQ_MEM(t, withpre, out, sizeof withpre);
|
||||||
n = ipcpackresp(out, sizeof out, 1, "A", 1, "xy", 2, 0);
|
n = ipcpackresp(out, sizeof out, 1, 0, "A", 1, "xy", 2, 0);
|
||||||
CT_EQ_INT(t, sizeof withoutpre, n);
|
CT_EQ_INT(t, sizeof withoutpre, n);
|
||||||
CT_EQ_MEM(t, withoutpre, out, sizeof withoutpre);
|
CT_EQ_MEM(t, withoutpre, out, sizeof withoutpre);
|
||||||
n = ipcpackresp(out, sizeof out, 0, nil, 0, nil, 0, 1);
|
n = ipcpackresp(out, sizeof out, 0, 0, nil, 0, nil, 0, 1);
|
||||||
CT_EQ_INT(t, Ipcresphdrsz + Ipclensz, n);
|
CT_EQ_INT(t, Ipcresphdrsz + Ipclensz, n);
|
||||||
|
|
||||||
memset(field, 'x', sizeof field);
|
memset(field, 'x', sizeof field);
|
||||||
memset(out, 0xa5, sizeof out);
|
memset(out, 0xa5, sizeof out);
|
||||||
n = ipcpackresp(out, Ipcmaxresp, 1,
|
n = ipcpackresp(out, Ipcmaxresp, 1, 0,
|
||||||
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
||||||
CT_EQ_INT(t, Ipcmaxresp, n);
|
CT_EQ_INT(t, Ipcmaxresp, n);
|
||||||
CT_EQ_INT(t, 0, out[1]);
|
CT_EQ_INT(t, 0, out[2]);
|
||||||
CT_EQ_INT(t, 1, out[2]);
|
CT_EQ_INT(t, 1, out[3]);
|
||||||
CT_EQ_INT(t, 0, out[3+Ipcfieldmax]);
|
CT_EQ_INT(t, 0, out[4+Ipcfieldmax]);
|
||||||
CT_EQ_INT(t, 1, out[4+Ipcfieldmax]);
|
CT_EQ_INT(t, 1, out[5+Ipcfieldmax]);
|
||||||
CT_EQ_INT(t, 0xa5, out[Ipcmaxresp]);
|
CT_EQ_INT(t, 0xa5, out[Ipcmaxresp]);
|
||||||
CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1,
|
CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1, 0,
|
||||||
(char*)field, sizeof field, (char*)field, sizeof field, 1));
|
(char*)field, sizeof field, (char*)field, sizeof field, 1));
|
||||||
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, 0,
|
||||||
(char*)field, Ipcfieldmax+1, "", 0, 0));
|
(char*)field, Ipcfieldmax+1, "", 0, 0));
|
||||||
|
/* A take-back is a rune count and never leaves its byte. */
|
||||||
|
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, -1, "", 0, "", 0, 0));
|
||||||
|
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, 256, "", 0, "", 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -215,8 +218,8 @@ ipc_response_empty_and_preedit(struct ct *t)
|
|||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
int fd[2], nfirst, nsecond;
|
int fd[2], nfirst, nsecond;
|
||||||
|
|
||||||
nfirst = ipcpackresp(first, sizeof first, 0, "", 0, "", 0, 0);
|
nfirst = ipcpackresp(first, sizeof first, 0, 0, "", 0, "", 0, 0);
|
||||||
nsecond = ipcpackresp(second, sizeof second, 1,
|
nsecond = ipcpackresp(second, sizeof second, 1, 0,
|
||||||
"go", 2, "kana", 4, 1);
|
"go", 2, "kana", 4, 1);
|
||||||
if(!CT_CHECK(t, nfirst > 0 && nsecond > 0))
|
if(!CT_CHECK(t, nfirst > 0 && nsecond > 0))
|
||||||
return;
|
return;
|
||||||
@@ -252,9 +255,9 @@ ipc_response_max_and_drain(struct ct *t)
|
|||||||
int fd[2], nfirst, nsecond;
|
int fd[2], nfirst, nsecond;
|
||||||
|
|
||||||
memset(field, 'x', sizeof field);
|
memset(field, 'x', sizeof field);
|
||||||
nfirst = ipcpackresp(first, sizeof first, 1,
|
nfirst = ipcpackresp(first, sizeof first, 1, 0,
|
||||||
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
||||||
nsecond = ipcpackresp(second, sizeof second, 0, "ok", 2, "", 0, 0);
|
nsecond = ipcpackresp(second, sizeof second, 0, 0, "ok", 2, "", 0, 0);
|
||||||
if(!CT_CHECK(t, nfirst == Ipcmaxresp && nsecond > 0))
|
if(!CT_CHECK(t, nfirst == Ipcmaxresp && nsecond > 0))
|
||||||
return;
|
return;
|
||||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
||||||
@@ -285,7 +288,7 @@ ipc_response_fragmented_and_truncated(struct ct *t)
|
|||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
int fd[2], i, n;
|
int fd[2], i, n;
|
||||||
|
|
||||||
n = ipcpackresp(frame, sizeof frame, 1, "abc", 3, "xy", 2, 1);
|
n = ipcpackresp(frame, sizeof frame, 1, 2, "abc", 3, "xy", 2, 1);
|
||||||
if(!CT_CHECK(t, n > 0))
|
if(!CT_CHECK(t, n > 0))
|
||||||
return;
|
return;
|
||||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
||||||
@@ -299,6 +302,7 @@ ipc_response_fragmented_and_truncated(struct ct *t)
|
|||||||
}
|
}
|
||||||
if(i == n && CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit,
|
if(i == n && CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit,
|
||||||
&resp))){
|
&resp))){
|
||||||
|
CT_EQ_INT(t, 2, resp.del);
|
||||||
CT_EQ_STR(t, "abc", commit);
|
CT_EQ_STR(t, "abc", commit);
|
||||||
CT_EQ_STR(t, "xy", preedit);
|
CT_EQ_STR(t, "xy", preedit);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -767,7 +767,8 @@ ipcrequest(int fd, uint32_t mod, uint32_t key, unsigned char *want,
|
|||||||
int
|
int
|
||||||
ipcprobe(int fd, char *where)
|
ipcprobe(int fd, char *where)
|
||||||
{
|
{
|
||||||
unsigned char empty[] = {0, 0, 0, 0, 0};
|
/* eaten, take-back, no commit, no preedit */
|
||||||
|
unsigned char empty[] = {0, 0, 0, 0, 0, 0};
|
||||||
|
|
||||||
return ipcrequest(fd, 0, 0, empty, sizeof empty, where);
|
return ipcrequest(fd, 0, 0, empty, sizeof empty, where);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,12 +35,11 @@ class MkemojiTest(unittest.TestCase):
|
|||||||
result = generate()
|
result = generate()
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
data = table(result.stdout)
|
data = table(result.stdout)
|
||||||
keys = list(data)
|
# The engine walks a prefix's children in rune order, so only the
|
||||||
self.assertEqual([k for k in keys if k[:1] == "^"][:9],
|
# prefix's own row decides which candidate a digit picks.
|
||||||
[f"^{d}" for d in range(1, 10)])
|
self.assertEqual(data["^"].split(), list("¹²³⁴⁵⁶⁷⁸⁹"))
|
||||||
self.assertEqual([k for k in keys if k[:1] == "_"][:9],
|
self.assertEqual(data["_"].split(), list("₁₂₃₄₅₆₇₈₉"))
|
||||||
[f"_{d}" for d in range(1, 10)])
|
self.assertEqual(data["<"].split()[2], "♥")
|
||||||
self.assertEqual([k for k in keys if k[:1] == "<"][2], "<3")
|
|
||||||
self.assertEqual(data["^1"], "¹")
|
self.assertEqual(data["^1"], "¹")
|
||||||
self.assertEqual(data["_2"], "₂")
|
self.assertEqual(data["_2"], "₂")
|
||||||
self.assertEqual(data["<3"].split()[0], "♥")
|
self.assertEqual(data["<3"].split()[0], "♥")
|
||||||
|
|||||||
@@ -64,6 +64,24 @@ class MkhanjaTest(unittest.TestCase):
|
|||||||
"\u91d1\t\uae40",
|
"\u91d1\t\uae40",
|
||||||
"\u6f22\u5b57\t\ud55c\uc790"])
|
"\u6f22\u5b57\t\ud55c\uc790"])
|
||||||
|
|
||||||
|
def test_imports_symbol_rows_under_a_jamo(self):
|
||||||
|
result = run(
|
||||||
|
IMPORT,
|
||||||
|
self.source(
|
||||||
|
"\u3141:\u203b:reference mark\n"
|
||||||
|
"\u3134:\u300c:bracket\n"
|
||||||
|
"\u3131:\u3000:ideographic space\n"
|
||||||
|
"\u3131:\u00ad:soft hyphen\n"
|
||||||
|
"\u3131:\u52a0:Hanja under a jamo\n"
|
||||||
|
"\u3141:\u203b\u203b:two runes\n"
|
||||||
|
"\u3141\u3134:\u203b:two jamo\n"
|
||||||
|
"\u314f:\u203b:a vowel\n"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertEqual(result.stdout.splitlines(),
|
||||||
|
["\u203b\t\u3141", "\u300c\t\u3134"])
|
||||||
|
|
||||||
def test_import_rejects_malformed_and_duplicate_rows(self):
|
def test_import_rejects_malformed_and_duplicate_rows(self):
|
||||||
bad = [
|
bad = [
|
||||||
"한 漢\n",
|
"한 漢\n",
|
||||||
@@ -110,6 +128,19 @@ class MkhanjaTest(unittest.TestCase):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_groups_symbols_under_their_jamo(self):
|
||||||
|
result = run(
|
||||||
|
GENERATE,
|
||||||
|
self.source(
|
||||||
|
"※\tㅁ\n"
|
||||||
|
"○\tㅁ\n"
|
||||||
|
"「\tㄴ\n"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertEqual(result.stdout.splitlines(),
|
||||||
|
["ㅁ\t※ ○", "ㄴ\t「"])
|
||||||
|
|
||||||
def test_generator_rejects_bad_rows(self):
|
def test_generator_rejects_bad_rows(self):
|
||||||
bad = [
|
bad = [
|
||||||
"漢a\t한\n",
|
"漢a\t한\n",
|
||||||
@@ -117,6 +148,12 @@ class MkhanjaTest(unittest.TestCase):
|
|||||||
"𠀀\t한\n",
|
"𠀀\t한\n",
|
||||||
"漢\t한\n漢\t한\n",
|
"漢\t한\n漢\t한\n",
|
||||||
"漢 한\n",
|
"漢 한\n",
|
||||||
|
"加\tㅁ\n",
|
||||||
|
"※\t한\n",
|
||||||
|
"※\tㅁㄴ\n",
|
||||||
|
"※※\tㅁ\n",
|
||||||
|
"\u3000\tㄱ\n",
|
||||||
|
"\u00ad\tㄱ\n",
|
||||||
]
|
]
|
||||||
for text in bad:
|
for text in bad:
|
||||||
with self.subTest(text=repr(text)):
|
with self.subTest(text=repr(text)):
|
||||||
@@ -131,6 +168,15 @@ class MkhanjaTest(unittest.TestCase):
|
|||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
self.assertIn("한\t", result.stdout)
|
self.assertIn("한\t", result.stdout)
|
||||||
|
|
||||||
|
def test_generator_reads_both_sources(self):
|
||||||
|
result = run(GENERATE)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
lines = result.stdout.splitlines()
|
||||||
|
self.assertEqual(lines.count(";; All rights reserved."), 2)
|
||||||
|
rows = dict(line.split("\t") for line in lines if "\t" in line)
|
||||||
|
self.assertIn("漢", rows["한"].split())
|
||||||
|
self.assertIn("※", rows["ㅁ"].split())
|
||||||
|
|
||||||
def test_generator_keeps_runtime_candidate_limit(self):
|
def test_generator_keeps_runtime_candidate_limit(self):
|
||||||
rows = "".join(f"{chr(0x4E00 + n)}\t한\n" for n in range(129))
|
rows = "".join(f"{chr(0x4E00 + n)}\t한\n" for n in range(129))
|
||||||
result = run(GENERATE, self.source(rows))
|
result = run(GENERATE, self.source(rows))
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ void engine_emoji_preedit_languages(struct ct*);
|
|||||||
void engine_emoji_start_and_unknown(struct ct*);
|
void engine_emoji_start_and_unknown(struct ct*);
|
||||||
void engine_hanja_search(struct ct*);
|
void engine_hanja_search(struct ct*);
|
||||||
void engine_hanja_unknown_and_cancel(struct ct*);
|
void engine_hanja_unknown_and_cancel(struct ct*);
|
||||||
|
void engine_hanja_reaches_back(struct ct*);
|
||||||
|
void engine_hanja_word_prefix(struct ct*);
|
||||||
void engine_hanja_korean_keys(struct ct*);
|
void engine_hanja_korean_keys(struct ct*);
|
||||||
void engine_hanja_backspace(struct ct*);
|
void engine_hanja_backspace(struct ct*);
|
||||||
void engine_hanja_input_languages(struct ct*);
|
void engine_hanja_input_languages(struct ct*);
|
||||||
@@ -125,3 +127,7 @@ void xim_adapter_callback_cleanup(struct ct*);
|
|||||||
void xim_adapter_callback_transfer(struct ct*);
|
void xim_adapter_callback_transfer(struct ct*);
|
||||||
void xim_adapter_callback_owner_loss(struct ct*);
|
void xim_adapter_callback_owner_loss(struct ct*);
|
||||||
void xim_adapter_commit_encoding(struct ct*);
|
void xim_adapter_commit_encoding(struct ct*);
|
||||||
|
void wl_modifier_mask(struct ct*);
|
||||||
|
void wl_forwarded_keys(struct ct*);
|
||||||
|
void wl_surrounding_text(struct ct*);
|
||||||
|
void wl_repeat_ends(struct ct*);
|
||||||
|
|||||||
@@ -107,6 +107,8 @@ static const struct ct_test tests[] = {
|
|||||||
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
|
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
|
||||||
{ "engine/hanja-search", engine_hanja_search },
|
{ "engine/hanja-search", engine_hanja_search },
|
||||||
{ "engine/hanja-unknown-cancel", engine_hanja_unknown_and_cancel },
|
{ "engine/hanja-unknown-cancel", engine_hanja_unknown_and_cancel },
|
||||||
|
{ "engine/hanja-reaches-back", engine_hanja_reaches_back },
|
||||||
|
{ "engine/hanja-word-prefix", engine_hanja_word_prefix },
|
||||||
{ "engine/hanja-korean-keys", engine_hanja_korean_keys },
|
{ "engine/hanja-korean-keys", engine_hanja_korean_keys },
|
||||||
{ "engine/hanja-backspace", engine_hanja_backspace },
|
{ "engine/hanja-backspace", engine_hanja_backspace },
|
||||||
{ "engine/hanja-input-languages", engine_hanja_input_languages },
|
{ "engine/hanja-input-languages", engine_hanja_input_languages },
|
||||||
@@ -145,6 +147,10 @@ static const struct ct_test tests[] = {
|
|||||||
{ "xim/callback-transfer", xim_adapter_callback_transfer },
|
{ "xim/callback-transfer", xim_adapter_callback_transfer },
|
||||||
{ "xim/callback-owner-loss", xim_adapter_callback_owner_loss },
|
{ "xim/callback-owner-loss", xim_adapter_callback_owner_loss },
|
||||||
{ "xim/commit-encoding", xim_adapter_commit_encoding },
|
{ "xim/commit-encoding", xim_adapter_commit_encoding },
|
||||||
|
{ "wl/modifier-mask", wl_modifier_mask },
|
||||||
|
{ "wl/forwarded-keys", wl_forwarded_keys },
|
||||||
|
{ "wl/surrounding-text", wl_surrounding_text },
|
||||||
|
{ "wl/repeat-ends", wl_repeat_ends },
|
||||||
};
|
};
|
||||||
#else
|
#else
|
||||||
static const struct ct_test tests[] = {
|
static const struct ct_test tests[] = {
|
||||||
|
|||||||
165
tests/wl_adapter_test.c
Normal file
165
tests/wl_adapter_test.c
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#include <wayland-client.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
#include "vkv1.h"
|
||||||
|
|
||||||
|
/* The virtual keyboard is the only wire call the tested code makes. */
|
||||||
|
static int nkeysent;
|
||||||
|
static void wirekey(struct zwp_virtual_keyboard_v1*, uint32_t, uint32_t,
|
||||||
|
uint32_t);
|
||||||
|
|
||||||
|
#define zwp_virtual_keyboard_v1_key wirekey
|
||||||
|
|
||||||
|
#include "wl.c"
|
||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
static void
|
||||||
|
wirekey(struct zwp_virtual_keyboard_v1 *vkb, uint32_t time, uint32_t code,
|
||||||
|
uint32_t state)
|
||||||
|
{
|
||||||
|
USED(vkb);
|
||||||
|
USED(time);
|
||||||
|
USED(code);
|
||||||
|
USED(state);
|
||||||
|
nkeysent++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only the modifiers are read from it, so nothing else need be here. */
|
||||||
|
static char keymaptext[] =
|
||||||
|
"xkb_keymap {\n"
|
||||||
|
"xkb_keycodes { <AD01> = 24; };\n"
|
||||||
|
"xkb_types { type \"ONE_LEVEL\" {\n"
|
||||||
|
" modifiers = none; map[none] = 1; level_name[1] = \"Any\"; }; };\n"
|
||||||
|
"xkb_compatibility { };\n"
|
||||||
|
"xkb_symbols { key <AD01> { [ q ] }; };\n"
|
||||||
|
"};\n";
|
||||||
|
|
||||||
|
void
|
||||||
|
wl_modifier_mask(struct ct *t)
|
||||||
|
{
|
||||||
|
struct xkb_context *ctx;
|
||||||
|
struct xkb_keymap *km;
|
||||||
|
xkb_mod_mask_t caps, ctrl, shift;
|
||||||
|
|
||||||
|
ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||||
|
km = xkb_keymap_new_from_string(ctx, keymaptext,
|
||||||
|
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||||
|
CT_CHECK(t, km != nil);
|
||||||
|
if(km != nil){
|
||||||
|
kstate = xkb_state_new(km);
|
||||||
|
shift = (xkb_mod_mask_t)1 <<
|
||||||
|
xkb_keymap_mod_get_index(km, XKB_MOD_NAME_SHIFT);
|
||||||
|
ctrl = (xkb_mod_mask_t)1 <<
|
||||||
|
xkb_keymap_mod_get_index(km, XKB_MOD_NAME_CTRL);
|
||||||
|
caps = (xkb_mod_mask_t)1 <<
|
||||||
|
xkb_keymap_mod_get_index(km, XKB_MOD_NAME_CAPS);
|
||||||
|
xkb_state_update_mask(kstate, shift, 0, 0, 0, 0, 0);
|
||||||
|
CT_EQ_UINT(t, Mshift, modmask());
|
||||||
|
xkb_state_update_mask(kstate, shift|ctrl, 0, 0, 0, 0, 0);
|
||||||
|
CT_EQ_UINT(t, Mshift|Mctrl, modmask());
|
||||||
|
/* Caps Lock is not Shift; a Korean key must not shift under it. */
|
||||||
|
xkb_state_update_mask(kstate, 0, 0, caps, 0, 0, 0);
|
||||||
|
CT_EQ_UINT(t, 0, modmask());
|
||||||
|
xkb_state_unref(kstate);
|
||||||
|
kstate = nil;
|
||||||
|
xkb_keymap_unref(km);
|
||||||
|
}
|
||||||
|
xkb_context_unref(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wl_forwarded_keys(struct ct *t)
|
||||||
|
{
|
||||||
|
haskeymap = 1;
|
||||||
|
memset(sent, 0, sizeof sent);
|
||||||
|
nkeysent = 0;
|
||||||
|
|
||||||
|
forward(30, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
CT_CHECK(t, issent(30));
|
||||||
|
forward(30, WL_KEYBOARD_KEY_STATE_RELEASED);
|
||||||
|
CT_CHECK(t, !issent(30));
|
||||||
|
CT_EQ_INT(t, 2, nkeysent);
|
||||||
|
|
||||||
|
/* A code the bitmap cannot hold is not passed on at all. */
|
||||||
|
nkeysent = 0;
|
||||||
|
forward(Maxcode, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
CT_EQ_INT(t, 0, nkeysent);
|
||||||
|
|
||||||
|
/* Whatever is still down is released, once each and no more. */
|
||||||
|
forward(1, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
forward(200, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
nkeysent = 0;
|
||||||
|
releasekeys();
|
||||||
|
CT_EQ_INT(t, 2, nkeysent);
|
||||||
|
CT_CHECK(t, !issent(1));
|
||||||
|
CT_CHECK(t, !issent(200));
|
||||||
|
releasekeys();
|
||||||
|
CT_EQ_INT(t, 2, nkeysent);
|
||||||
|
|
||||||
|
/* Before a keymap the virtual keyboard must not be touched. */
|
||||||
|
haskeymap = 0;
|
||||||
|
nkeysent = 0;
|
||||||
|
forward(30, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
CT_EQ_INT(t, 0, nkeysent);
|
||||||
|
CT_CHECK(t, !issent(30));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Arming a repeat needs the engine, which is not here; ending one does
|
||||||
|
* not, and a repeat that outlives its key is the worst this can do.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* The text before the cursor is what a reading may reach into, and a
|
||||||
|
* take-back is measured in the bytes those runes hold.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
wl_surrounding_text(struct ct *t)
|
||||||
|
{
|
||||||
|
sclear(&pendingsurround);
|
||||||
|
imsurrounding(nil, nil, "가나다라", 9, 9);
|
||||||
|
checkstr(t, "the text before the cursor", "가나다", &pendingsurround);
|
||||||
|
surround = pendingsurround;
|
||||||
|
CT_EQ_INT(t, 3, backbytes(1));
|
||||||
|
CT_EQ_INT(t, 6, backbytes(2));
|
||||||
|
CT_EQ_INT(t, 9, backbytes(9));
|
||||||
|
CT_EQ_INT(t, 0, backbytes(0));
|
||||||
|
/* A cursor past the end of the buffer is the whole of it. */
|
||||||
|
imsurrounding(nil, nil, "ab", 99, 99);
|
||||||
|
checkstr(t, "a cursor past the end", "ab", &pendingsurround);
|
||||||
|
/* An activation starts with none, whatever the last one had. */
|
||||||
|
imactivate(nil, nil);
|
||||||
|
CT_EQ_INT(t, 0, pendingsurround.n);
|
||||||
|
sclear(&surround);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wl_repeat_ends(struct ct *t)
|
||||||
|
{
|
||||||
|
haskeymap = 1;
|
||||||
|
memset(sent, 0, sizeof sent);
|
||||||
|
|
||||||
|
/* Not a rate or a delay any seat would pick: the body was empty
|
||||||
|
* once, and a stored constant would pass for the real thing. */
|
||||||
|
grabrepeat(nil, nil, 33, 410);
|
||||||
|
CT_EQ_INT(t, 33, reprate);
|
||||||
|
CT_EQ_INT(t, 410, repdelay);
|
||||||
|
|
||||||
|
/* The key's own release ends it; another key's release does not. */
|
||||||
|
repcode = 14;
|
||||||
|
repdue = 1;
|
||||||
|
grabkey(nil, nil, 0, 0, 15, WL_KEYBOARD_KEY_STATE_RELEASED);
|
||||||
|
CT_CHECK(t, repdue != 0);
|
||||||
|
grabkey(nil, nil, 0, 0, 14, WL_KEYBOARD_KEY_STATE_RELEASED);
|
||||||
|
CT_EQ_INT(t, 0, repdue);
|
||||||
|
|
||||||
|
/* So does a seat that has stopped repeating anything. */
|
||||||
|
repdue = 1;
|
||||||
|
grabrepeat(nil, nil, 0, 410);
|
||||||
|
CT_EQ_INT(t, 0, repdue);
|
||||||
|
|
||||||
|
/* A due repeat with no text input to serve is dropped, not sent. */
|
||||||
|
grabrepeat(nil, nil, 33, 410);
|
||||||
|
active = 0;
|
||||||
|
repdue = 1;
|
||||||
|
repeat();
|
||||||
|
CT_EQ_INT(t, 0, repdue);
|
||||||
|
}
|
||||||
@@ -398,8 +398,8 @@ struct Freejob
|
|||||||
Channel *done;
|
Channel *done;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int
|
static void
|
||||||
ximbegin(struct ct *t, Ximfix *f, int lang)
|
ximbegin(Ximfix *f, int lang)
|
||||||
{
|
{
|
||||||
Drawcmd dc;
|
Drawcmd dc;
|
||||||
|
|
||||||
@@ -410,8 +410,6 @@ ximbegin(struct ct *t, Ximfix *f, int lang)
|
|||||||
f->oldreply = replyc;
|
f->oldreply = replyc;
|
||||||
replyc = chancreate(sizeof(Keyres), 0);
|
replyc = chancreate(sizeof(Keyres), 0);
|
||||||
pumpstart(&f->pump, 16);
|
pumpstart(&f->pump, 16);
|
||||||
USED(t);
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -466,8 +464,7 @@ xim_adapter_key_contract(struct ct *t)
|
|||||||
u32int allmod;
|
u32int allmod;
|
||||||
|
|
||||||
memset(&state, 0, sizeof state);
|
memset(&state, 0, sizeof state);
|
||||||
if(!ximbegin(t, &f, LangJP))
|
ximbegin(&f, LangJP);
|
||||||
goto cleanup;
|
|
||||||
allmod = ~0;
|
allmod = ~0;
|
||||||
keypress(&state, 'x', allmod, &res);
|
keypress(&state, 'x', allmod, &res);
|
||||||
req = nexttrace(t, &f, Keypress, &state);
|
req = nexttrace(t, &f, Keypress, &state);
|
||||||
@@ -487,7 +484,6 @@ xim_adapter_key_contract(struct ct *t)
|
|||||||
CT_CHECK(t, !res.eaten);
|
CT_CHECK(t, !res.eaten);
|
||||||
checkstr(t, "preedit", "か", &res.commit);
|
checkstr(t, "preedit", "か", &res.commit);
|
||||||
checkstr(t, "preedit", "", &res.preedit);
|
checkstr(t, "preedit", "", &res.preedit);
|
||||||
cleanup:
|
|
||||||
ximend(&f);
|
ximend(&f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,8 +498,7 @@ xim_adapter_release_lifecycle(struct ct *t)
|
|||||||
memset(&a, 0, sizeof a);
|
memset(&a, 0, sizeof a);
|
||||||
memset(&b, 0, sizeof b);
|
memset(&b, 0, sizeof b);
|
||||||
dead = nil;
|
dead = nil;
|
||||||
if(!ximbegin(t, &f, LangJP))
|
ximbegin(&f, LangJP);
|
||||||
goto cleanup;
|
|
||||||
keypress(&a, 'k', 0, &res);
|
keypress(&a, 'k', 0, &res);
|
||||||
nexttrace(t, &f, Keypress, &a);
|
nexttrace(t, &f, Keypress, &a);
|
||||||
keypress(&a, 'a', 0, &res);
|
keypress(&a, 'a', 0, &res);
|
||||||
@@ -580,8 +575,7 @@ xim_adapter_free_waits_for_release(struct ct *t)
|
|||||||
state = nil;
|
state = nil;
|
||||||
freed = nil;
|
freed = nil;
|
||||||
workeractive = 0;
|
workeractive = 0;
|
||||||
if(!ximbegin(t, &f, LangJP))
|
ximbegin(&f, LangJP);
|
||||||
goto cleanup;
|
|
||||||
state = calloc(1, sizeof *state);
|
state = calloc(1, sizeof *state);
|
||||||
freed = chancreate(sizeof(uchar), 0);
|
freed = chancreate(sizeof(uchar), 0);
|
||||||
if(!CT_CHECK(t, state != nil && freed != nil))
|
if(!CT_CHECK(t, state != nil && freed != nil))
|
||||||
@@ -736,8 +730,7 @@ xim_adapter_placement_updates(struct ct *t)
|
|||||||
ic = (xcb_im_input_context_t*)(uintptr)71;
|
ic = (xcb_im_input_context_t*)(uintptr)71;
|
||||||
memset(&state, 0, sizeof state);
|
memset(&state, 0, sizeof state);
|
||||||
oldstate = kstate;
|
oldstate = kstate;
|
||||||
if(!ximbegin(t, &f, LangJP))
|
ximbegin(&f, LangJP);
|
||||||
goto cleanup;
|
|
||||||
kstate = testkeystate("us");
|
kstate = testkeystate("us");
|
||||||
if(!CT_CHECK(t, kstate != nil))
|
if(!CT_CHECK(t, kstate != nil))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
@@ -891,8 +884,7 @@ xim_adapter_callback_cleanup(struct ct *t)
|
|||||||
|
|
||||||
ic = (xcb_im_input_context_t*)(uintptr)29;
|
ic = (xcb_im_input_context_t*)(uintptr)29;
|
||||||
memset(&state, 0, sizeof state);
|
memset(&state, 0, sizeof state);
|
||||||
if(!ximbegin(t, &f, LangJP))
|
ximbegin(&f, LangJP);
|
||||||
return;
|
|
||||||
wireclear();
|
wireclear();
|
||||||
wirebind(0, ic, &state);
|
wirebind(0, ic, &state);
|
||||||
state.xic = ic;
|
state.xic = ic;
|
||||||
@@ -980,8 +972,7 @@ xim_adapter_callback_transfer(struct ct *t)
|
|||||||
aic = (xcb_im_input_context_t*)(uintptr)50;
|
aic = (xcb_im_input_context_t*)(uintptr)50;
|
||||||
memset(&a, 0, sizeof a);
|
memset(&a, 0, sizeof a);
|
||||||
memset(&b, 0, sizeof b);
|
memset(&b, 0, sizeof b);
|
||||||
if(!ximbegin(t, &f, LangJP))
|
ximbegin(&f, LangJP);
|
||||||
goto cleanup;
|
|
||||||
wireclear();
|
wireclear();
|
||||||
a.xic = aic;
|
a.xic = aic;
|
||||||
a.clientpre = 1;
|
a.clientpre = 1;
|
||||||
@@ -996,7 +987,6 @@ xim_adapter_callback_transfer(struct ct *t)
|
|||||||
CT_EQ_PTR(t, nil, preowner);
|
CT_EQ_PTR(t, nil, preowner);
|
||||||
release(&b);
|
release(&b);
|
||||||
nexttrace(t, &f, Keyrelease, &b);
|
nexttrace(t, &f, Keyrelease, &b);
|
||||||
cleanup:
|
|
||||||
preowner = nil;
|
preowner = nil;
|
||||||
ximend(&f);
|
ximend(&f);
|
||||||
}
|
}
|
||||||
@@ -1016,8 +1006,7 @@ xim_adapter_callback_owner_loss(struct ct *t)
|
|||||||
ic = (xcb_im_input_context_t*)(uintptr)51;
|
ic = (xcb_im_input_context_t*)(uintptr)51;
|
||||||
memset(&state, 0, sizeof state);
|
memset(&state, 0, sizeof state);
|
||||||
foreign = 0;
|
foreign = 0;
|
||||||
if(!ximbegin(t, &f, LangJP))
|
ximbegin(&f, LangJP);
|
||||||
goto cleanup;
|
|
||||||
wireclear();
|
wireclear();
|
||||||
wirebind(0, ic, &state);
|
wirebind(0, ic, &state);
|
||||||
state.xic = ic;
|
state.xic = ic;
|
||||||
@@ -1086,7 +1075,6 @@ xim_adapter_callback_owner_loss(struct ct *t)
|
|||||||
CT_CHECK(t, channbrecv(f.pump.trace, &trace) > 0);
|
CT_CHECK(t, channbrecv(f.pump.trace, &trace) > 0);
|
||||||
CT_EQ_INT(t, Keyrelease, trace.op);
|
CT_EQ_INT(t, Keyrelease, trace.op);
|
||||||
CT_EQ_PTR(t, &foreign, trace.owner);
|
CT_EQ_PTR(t, &foreign, trace.owner);
|
||||||
cleanup:
|
|
||||||
preowner = nil;
|
preowner = nil;
|
||||||
ximend(&f);
|
ximend(&f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,10 +129,10 @@ pumpinput(Display *dpy, XIC ic, char *commit, size_t cap, int timeout)
|
|||||||
continue;
|
continue;
|
||||||
n = Xutf8LookupString(ic, &ev.xkey, buf, sizeof buf - 1,
|
n = Xutf8LookupString(ic, &ev.xkey, buf, sizeof buf - 1,
|
||||||
&sym, &status);
|
&sym, &status);
|
||||||
if(n < 0 || ((status == XLookupChars || status == XLookupBoth) &&
|
if(n < 0 || (status == XLookupChars &&
|
||||||
((size_t)n >= cap || commit == NULL)))
|
((size_t)n >= cap || commit == NULL)))
|
||||||
return 0;
|
return 0;
|
||||||
if((status == XLookupChars || status == XLookupBoth) && n != 0){
|
if(status == XLookupChars && n != 0){
|
||||||
memcpy(commit, buf, n);
|
memcpy(commit, buf, n);
|
||||||
commit[n] = '\0';
|
commit[n] = '\0';
|
||||||
}
|
}
|
||||||
@@ -273,7 +273,7 @@ sendkey(Display *dpy, XIC ic, Window win, KeySym sym, unsigned int state,
|
|||||||
if(n < 0)
|
if(n < 0)
|
||||||
return fail("Xutf8LookupString failed for keysym %#lx",
|
return fail("Xutf8LookupString failed for keysym %#lx",
|
||||||
(unsigned long)sym);
|
(unsigned long)sym);
|
||||||
if((status == XLookupChars || status == XLookupBoth) && n != 0){
|
if(status == XLookupChars && n != 0){
|
||||||
if((size_t)n >= cap)
|
if((size_t)n >= cap)
|
||||||
return fail("commit buffer overflow");
|
return fail("commit buffer overflow");
|
||||||
memcpy(commit, buf, n);
|
memcpy(commit, buf, n);
|
||||||
@@ -405,6 +405,9 @@ testcallbacks(Display *dpy, XIM im, Window win)
|
|||||||
/*
|
/*
|
||||||
* The popup is the one viewable override-redirect window on the private
|
* The popup is the one viewable override-redirect window on the private
|
||||||
* display; created before the client window, it must still stack above it.
|
* display; created before the client window, it must still stack above it.
|
||||||
|
* The order and the map states have to be one observation: the daemon
|
||||||
|
* raises and maps between two ungrabbed round trips, and a stale order read
|
||||||
|
* against a fresh map state says the popup is below when it is not.
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
popupabove(Display *dpy, Window client)
|
popupabove(Display *dpy, Window client)
|
||||||
@@ -419,8 +422,11 @@ popupabove(Display *dpy, Window client)
|
|||||||
for(;;){
|
for(;;){
|
||||||
above = 0;
|
above = 0;
|
||||||
root = DefaultRootWindow(dpy);
|
root = DefaultRootWindow(dpy);
|
||||||
if(!XQueryTree(dpy, root, &root, &parent, &kids, &n))
|
XGrabServer(dpy);
|
||||||
|
if(!XQueryTree(dpy, root, &root, &parent, &kids, &n)){
|
||||||
|
XUngrabServer(dpy);
|
||||||
return fail("query the window tree");
|
return fail("query the window tree");
|
||||||
|
}
|
||||||
for(i = 0; i < n; i++){
|
for(i = 0; i < n; i++){
|
||||||
if(kids[i] == client)
|
if(kids[i] == client)
|
||||||
above = 1;
|
above = 1;
|
||||||
@@ -429,6 +435,7 @@ popupabove(Display *dpy, Window client)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
XFree(kids);
|
XFree(kids);
|
||||||
|
XUngrabServer(dpy);
|
||||||
if(i < n)
|
if(i < n)
|
||||||
return above || fail("popup is stacked below the client");
|
return above || fail("popup is stacked below the client");
|
||||||
if(leftms(deadline) == 0)
|
if(leftms(deadline) == 0)
|
||||||
|
|||||||
4
win.c
4
win.c
@@ -197,11 +197,7 @@ wininit(void)
|
|||||||
xcb_randr_query_version_cookie_t rc;
|
xcb_randr_query_version_cookie_t rc;
|
||||||
xcb_randr_query_version_reply_t *rr;
|
xcb_randr_query_version_reply_t *rr;
|
||||||
const xcb_query_extension_reply_t *rext;
|
const xcb_query_extension_reply_t *rext;
|
||||||
char *scale;
|
|
||||||
|
|
||||||
scale = getenv("GDK_SCALE");
|
|
||||||
if(scale != nil && atoi(scale) > 1)
|
|
||||||
popupscale = min(atoi(scale), 4);
|
|
||||||
conn = xcb_connect(nil, &n);
|
conn = xcb_connect(nil, &n);
|
||||||
if(xcb_connection_has_error(conn)){
|
if(xcb_connection_has_error(conn)){
|
||||||
fprint(2, "strans: popup disabled: cannot connect to X display\n");
|
fprint(2, "strans: popup disabled: cannot connect to X display\n");
|
||||||
|
|||||||
798
wl.c
Normal file
798
wl.c
Normal file
@@ -0,0 +1,798 @@
|
|||||||
|
#include "dat.h"
|
||||||
|
#include "fn.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
#include "imv2.h"
|
||||||
|
#include "vkv1.h"
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Maxcode = 0x300, /* KEY_MAX+1: what evdev can send */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* What popuplayout may use. The compositor puts the popup at the text
|
||||||
|
* cursor and constrains it there, so this need only be wide enough for
|
||||||
|
* the layout's own width and tall enough for every row it can show.
|
||||||
|
*/
|
||||||
|
#define Popupw PopupBasew
|
||||||
|
#define Popuph ((Maxdisp + 2)*Fontsz + 2*PopupPad + PopupSep)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One of two shm buffers, busy from attach until the compositor releases
|
||||||
|
* it. win.c's single grow-only image would be redrawn while the
|
||||||
|
* compositor was still reading it.
|
||||||
|
*/
|
||||||
|
typedef struct Buf Buf;
|
||||||
|
struct Buf
|
||||||
|
{
|
||||||
|
struct wl_buffer *b;
|
||||||
|
u32int *img;
|
||||||
|
int size;
|
||||||
|
int w;
|
||||||
|
int h;
|
||||||
|
int busy;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct wl_display *display;
|
||||||
|
static struct wl_seat *seat;
|
||||||
|
static struct wl_compositor *comp;
|
||||||
|
static struct wl_shm *shm;
|
||||||
|
static struct wl_surface *surface;
|
||||||
|
static struct zwp_input_popup_surface_v2 *popsurf;
|
||||||
|
static struct zwp_input_method_manager_v2 *immgr;
|
||||||
|
static struct zwp_virtual_keyboard_manager_v1 *vkmgr;
|
||||||
|
static struct zwp_input_method_v2 *im;
|
||||||
|
static struct zwp_virtual_keyboard_v1 *vk;
|
||||||
|
static struct zwp_input_method_keyboard_grab_v2 *grab;
|
||||||
|
static struct xkb_context *xkbctx;
|
||||||
|
static struct xkb_state *kstate;
|
||||||
|
static Channel *replyc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The protocol gives one input context per seat, so the engine knows us
|
||||||
|
* by one address; nil would mean no owner at all.
|
||||||
|
*/
|
||||||
|
static int context;
|
||||||
|
|
||||||
|
static u32int serial; /* done events counted; the serial commit takes */
|
||||||
|
static int active; /* a text input is being served */
|
||||||
|
static int pendingactive;
|
||||||
|
static int activated; /* an activate arrived since the last done */
|
||||||
|
static u32int purpose, pendingpurpose;
|
||||||
|
static Str surround, pendingsurround; /* the client's text before the cursor */
|
||||||
|
static u32int sent[Maxcode/32]; /* keycodes passed on and still down */
|
||||||
|
static u32int lasttime; /* the compositor's clock, for a release of our own */
|
||||||
|
static int reprate; /* repeats a second the seat asks for; 0 is none */
|
||||||
|
static int repdelay; /* ms a key is held before the first repeat */
|
||||||
|
static u32int repcode; /* the key repeating */
|
||||||
|
static vlong repdue; /* when its next repeat falls due; 0 if none */
|
||||||
|
static int engaged; /* the engine owes this context a release */
|
||||||
|
static int preshown; /* our preedit is on the client's screen */
|
||||||
|
static int haskeymap;
|
||||||
|
static int gone; /* unavailable: the seat is another input method's */
|
||||||
|
static Buf bufs[2];
|
||||||
|
static int shown; /* a buffer is attached and the popup is up */
|
||||||
|
static Drawcmd held; /* a picture that came while both buffers were busy */
|
||||||
|
static int helddraw;
|
||||||
|
|
||||||
|
static void grabkeyboard(void);
|
||||||
|
static void popup(Drawcmd*);
|
||||||
|
|
||||||
|
static void
|
||||||
|
wllog(char *msg)
|
||||||
|
{
|
||||||
|
fprint(2, "strans: wl: %s\n", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A repeat deadline needs a clock that cannot step; nsec() is not one. */
|
||||||
|
static vlong
|
||||||
|
nowms(void)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return (vlong)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
hidden(void)
|
||||||
|
{
|
||||||
|
return purpose == Purposepassword || purpose == Purposepin;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sendrequest(int op, u32int ks, u32int mod, Keyres *res)
|
||||||
|
{
|
||||||
|
Keyreq kr;
|
||||||
|
|
||||||
|
memset(&kr, 0, sizeof kr);
|
||||||
|
kr.owner = &context;
|
||||||
|
kr.clientpre = 1; /* set_preedit_string draws it; the popup does not */
|
||||||
|
kr.op = op;
|
||||||
|
kr.ks = ks;
|
||||||
|
kr.mod = mod;
|
||||||
|
kr.surround = surround;
|
||||||
|
kr.reply = replyc;
|
||||||
|
chansend(keyc, &kr);
|
||||||
|
chanrecv(replyc, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The bytes the last n runes of the client's text take. */
|
||||||
|
static int
|
||||||
|
backbytes(int n)
|
||||||
|
{
|
||||||
|
int i, nb;
|
||||||
|
|
||||||
|
nb = 0;
|
||||||
|
for(i = max(surround.n - n, 0); i < surround.n; i++)
|
||||||
|
nb += runelen(surround.r[i]);
|
||||||
|
return nb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The engine's modifier bits, read from the grab's own keyboard state. */
|
||||||
|
static u32int
|
||||||
|
modmask(void)
|
||||||
|
{
|
||||||
|
static char *name[] = {
|
||||||
|
XKB_MOD_NAME_SHIFT, XKB_MOD_NAME_CTRL,
|
||||||
|
XKB_MOD_NAME_ALT, XKB_MOD_NAME_LOGO,
|
||||||
|
};
|
||||||
|
static u32int bit[] = {Mshift, Mctrl, Malt, Msuper};
|
||||||
|
u32int m;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
m = 0;
|
||||||
|
for(i = 0; i < nelem(name); i++)
|
||||||
|
if(xkb_state_mod_name_is_active(kstate, name[i],
|
||||||
|
XKB_STATE_MODS_EFFECTIVE) > 0)
|
||||||
|
m |= bit[i];
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
issent(u32int code)
|
||||||
|
{
|
||||||
|
return code < Maxcode && (sent[code/32] & 1<<(code%32)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A key the engine did not take goes to the client as a key, not as
|
||||||
|
* text: eaten = 0 means it is not ours, and committing it would lie to a
|
||||||
|
* terminal and to anything with a keybinding.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
forward(u32int code, u32int state)
|
||||||
|
{
|
||||||
|
if(!haskeymap || code >= Maxcode)
|
||||||
|
return;
|
||||||
|
zwp_virtual_keyboard_v1_key(vk, lasttime, code, state);
|
||||||
|
if(state == WL_KEYBOARD_KEY_STATE_PRESSED)
|
||||||
|
sent[code/32] |= 1<<(code%32);
|
||||||
|
else
|
||||||
|
sent[code/32] &= ~(1<<(code%32));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Whatever we passed on is still down; the client must not keep it. */
|
||||||
|
static void
|
||||||
|
releasekeys(void)
|
||||||
|
{
|
||||||
|
u32int code;
|
||||||
|
|
||||||
|
for(code = 0; code < Maxcode; code++)
|
||||||
|
if(issent(code))
|
||||||
|
forward(code, WL_KEYBOARD_KEY_STATE_RELEASED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The engine's text, then the composed text, in one commit_string: the
|
||||||
|
* compositor keeps only the last one sent before a commit. A commit is
|
||||||
|
* an event and goes only when there is something, while a preedit is
|
||||||
|
* state and goes every time, empty to take the last one down.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
answer(Keyres *res, char *tail)
|
||||||
|
{
|
||||||
|
char utf[2*Maxutf];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
/* The compositor takes the text back before it inserts ours. */
|
||||||
|
if(res->del > 0)
|
||||||
|
zwp_input_method_v2_delete_surrounding_text(im,
|
||||||
|
backbytes(res->del), 0);
|
||||||
|
n = stoutf(&res->commit, utf, sizeof utf);
|
||||||
|
if(tail[0] != '\0')
|
||||||
|
n += snprint(utf+n, sizeof utf - n, "%s", tail);
|
||||||
|
if(n > 0)
|
||||||
|
zwp_input_method_v2_commit_string(im, utf);
|
||||||
|
n = stoutf(&res->preedit, utf, sizeof utf);
|
||||||
|
if(n > 0 || preshown) /* an empty one only to take the last down */
|
||||||
|
zwp_input_method_v2_set_preedit_string(im, utf, n, n);
|
||||||
|
zwp_input_method_v2_commit(im, serial);
|
||||||
|
preshown = n > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
bufrelease(void *p, struct wl_buffer*)
|
||||||
|
{
|
||||||
|
((Buf*)p)->busy = 0;
|
||||||
|
if(helddraw){
|
||||||
|
helddraw = 0;
|
||||||
|
popup(&held);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_buffer_listener buflisten = {bufrelease};
|
||||||
|
|
||||||
|
static void
|
||||||
|
bufclear(Buf *p)
|
||||||
|
{
|
||||||
|
if(p->b != nil)
|
||||||
|
wl_buffer_destroy(p->b);
|
||||||
|
if(p->img != nil)
|
||||||
|
munmap(p->img, p->size);
|
||||||
|
memset(p, 0, sizeof *p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* An anonymous file of pixels, shared with the compositor. */
|
||||||
|
static int
|
||||||
|
shmfd(int size)
|
||||||
|
{
|
||||||
|
char path[] = "/dev/shm/strans-XXXXXX";
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = mkstemp(path);
|
||||||
|
if(fd < 0)
|
||||||
|
return -1;
|
||||||
|
unlink(path);
|
||||||
|
if(ftruncate(fd, size) < 0){
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A free buffer of the wanted size. Sizes differ from draw to draw, so
|
||||||
|
* a free one of the wrong size is remade; both busy means the compositor
|
||||||
|
* is still reading them and the picture has to wait.
|
||||||
|
*/
|
||||||
|
static Buf*
|
||||||
|
getbuf(int w, int h)
|
||||||
|
{
|
||||||
|
struct wl_shm_pool *pool;
|
||||||
|
Buf *p;
|
||||||
|
int fd, i, size;
|
||||||
|
|
||||||
|
for(i = 0; i < nelem(bufs); i++)
|
||||||
|
if(!bufs[i].busy && bufs[i].w == w && bufs[i].h == h)
|
||||||
|
return &bufs[i];
|
||||||
|
for(i = 0; i < nelem(bufs); i++)
|
||||||
|
if(!bufs[i].busy)
|
||||||
|
break;
|
||||||
|
if(i == nelem(bufs))
|
||||||
|
return nil;
|
||||||
|
p = &bufs[i];
|
||||||
|
bufclear(p);
|
||||||
|
size = w * h * sizeof p->img[0];
|
||||||
|
fd = shmfd(size);
|
||||||
|
if(fd < 0)
|
||||||
|
return nil;
|
||||||
|
p->img = mmap(nil, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
|
||||||
|
if(p->img == MAP_FAILED){
|
||||||
|
p->img = nil;
|
||||||
|
close(fd);
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
pool = wl_shm_create_pool(shm, fd, size);
|
||||||
|
p->b = wl_shm_pool_create_buffer(pool, 0, w, h,
|
||||||
|
w * sizeof p->img[0], WL_SHM_FORMAT_XRGB8888);
|
||||||
|
wl_shm_pool_destroy(pool);
|
||||||
|
close(fd);
|
||||||
|
wl_buffer_add_listener(p->b, &buflisten, p);
|
||||||
|
p->size = size;
|
||||||
|
p->w = w;
|
||||||
|
p->h = h;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unmapping takes the popup down; the compositor puts it back at the
|
||||||
|
* next activate with whatever buffer is still attached. */
|
||||||
|
static void
|
||||||
|
hidepopup(void)
|
||||||
|
{
|
||||||
|
helddraw = 0; /* a picture still waiting for a buffer is stale now */
|
||||||
|
if(!shown)
|
||||||
|
return;
|
||||||
|
wl_surface_attach(surface, nil, 0, 0);
|
||||||
|
wl_surface_commit(surface);
|
||||||
|
shown = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
popup(Drawcmd *dc)
|
||||||
|
{
|
||||||
|
Popup p;
|
||||||
|
Buf *b;
|
||||||
|
|
||||||
|
popuplayout(dc, Popupw, Popuph, &p);
|
||||||
|
if(p.w <= 0 || p.h <= 0){
|
||||||
|
hidepopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* A buffer that is not a multiple of the scale is an invalid_size
|
||||||
|
* error at attach, which would kill the connection. */
|
||||||
|
p.w = (p.w + popupscale - 1) / popupscale * popupscale;
|
||||||
|
p.h = (p.h + popupscale - 1) / popupscale * popupscale;
|
||||||
|
b = getbuf(p.w, p.h);
|
||||||
|
if(b == nil){
|
||||||
|
held = *dc; /* the engine will not send it twice */
|
||||||
|
helddraw = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
popupdraw(b->img, dc, &p);
|
||||||
|
wl_surface_attach(surface, b->b, 0, 0);
|
||||||
|
wl_surface_set_buffer_scale(surface, popupscale);
|
||||||
|
wl_surface_damage_buffer(surface, 0, 0, p.w, p.h);
|
||||||
|
wl_surface_commit(surface);
|
||||||
|
b->busy = 1;
|
||||||
|
shown = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* drawc is empty or holds exactly what the engine last published, so one
|
||||||
|
* take is enough. lastdraw follows the engine and not our surface, so
|
||||||
|
* take it wherever the two can part -- but show it only while the engine
|
||||||
|
* is ours: what it drew for another frontend is not ours to put up.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
takedraw(void)
|
||||||
|
{
|
||||||
|
Drawcmd dc;
|
||||||
|
|
||||||
|
if(channbrecv(drawc, &dc) > 0 && engaged)
|
||||||
|
popup(&dc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The compositor places the popup; where the text is does not concern us. */
|
||||||
|
static void
|
||||||
|
poprect(void*, struct zwp_input_popup_surface_v2*, int, int, int, int)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zwp_input_popup_surface_v2_listener poplisten = {poprect};
|
||||||
|
|
||||||
|
static void
|
||||||
|
releasegrab(void)
|
||||||
|
{
|
||||||
|
if(grab == nil)
|
||||||
|
return;
|
||||||
|
zwp_input_method_keyboard_grab_v2_release(grab);
|
||||||
|
grab = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The context is going: give the client its keys back. The pending text
|
||||||
|
* goes nowhere either way -- the compositor drops a commit once the text
|
||||||
|
* input has its leave, and a client that disabled its own text input has
|
||||||
|
* stopped listening, so it throws a relayed commit away. XIM and IBus
|
||||||
|
* hand the text back here because their focus-out is a round trip the
|
||||||
|
* client still waits on; a deactivate is not.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
leave(void)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
|
||||||
|
repdue = 0;
|
||||||
|
releasekeys();
|
||||||
|
composedrop(Composewl, &context);
|
||||||
|
if(engaged){
|
||||||
|
sendrequest(Keyrelease, 0, 0, &res);
|
||||||
|
engaged = 0;
|
||||||
|
takedraw();
|
||||||
|
}
|
||||||
|
hidepopup();
|
||||||
|
preshown = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A purpose that turned secret with text pending gets the text first. */
|
||||||
|
static void
|
||||||
|
flushpending(void)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
|
||||||
|
if(!engaged)
|
||||||
|
return;
|
||||||
|
sendrequest(Keyreset, 0, 0, &res);
|
||||||
|
answer(&res, "");
|
||||||
|
takedraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A purpose belongs to the activation: the compositor sends a content type
|
||||||
|
* only for a client that asked for one, so a client that asks for none has
|
||||||
|
* none, whatever the last one wanted.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
imactivate(void*, struct zwp_input_method_v2*)
|
||||||
|
{
|
||||||
|
pendingactive = 1;
|
||||||
|
activated = 1;
|
||||||
|
pendingpurpose = 0;
|
||||||
|
sclear(&pendingsurround);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
imdeactivate(void*, struct zwp_input_method_v2*)
|
||||||
|
{
|
||||||
|
pendingactive = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The text around the cursor, of which a reading can use what comes
|
||||||
|
* before it. A text input that sends none leaves this empty, and then
|
||||||
|
* nothing is ever reached back into or taken away.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
imsurrounding(void*, struct zwp_input_method_v2*, const char *text,
|
||||||
|
u32int cursor, u32int)
|
||||||
|
{
|
||||||
|
u32int n;
|
||||||
|
|
||||||
|
n = strlen(text);
|
||||||
|
stail(&pendingsurround, (char*)text, cursor < n ? cursor : n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
imcause(void*, struct zwp_input_method_v2*, u32int)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
imcontent(void*, struct zwp_input_method_v2*, u32int, u32int what)
|
||||||
|
{
|
||||||
|
pendingpurpose = what;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* activate, deactivate and content_type are pending until done, and the
|
||||||
|
* count of done events is the serial a commit must carry: wlroots throws
|
||||||
|
* away a whole commit whose serial does not match, without a word.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
imdone(void*, struct zwp_input_method_v2*)
|
||||||
|
{
|
||||||
|
int washidden;
|
||||||
|
|
||||||
|
serial++;
|
||||||
|
washidden = hidden();
|
||||||
|
purpose = pendingpurpose;
|
||||||
|
surround = pendingsurround;
|
||||||
|
if(activated || (active && !pendingactive))
|
||||||
|
leave();
|
||||||
|
else if(active && !washidden && hidden())
|
||||||
|
flushpending();
|
||||||
|
if(activated && pendingactive)
|
||||||
|
grabkeyboard();
|
||||||
|
else if(!pendingactive)
|
||||||
|
releasegrab();
|
||||||
|
active = pendingactive;
|
||||||
|
activated = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
imgone(void*, struct zwp_input_method_v2*)
|
||||||
|
{
|
||||||
|
gone = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zwp_input_method_v2_listener imlisten = {
|
||||||
|
imactivate, imdeactivate, imsurrounding, imcause, imcontent, imdone,
|
||||||
|
imgone,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The same fd goes to the virtual keyboard, so a key we pass on means
|
||||||
|
* there what it meant here.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
grabkeymap(void*, struct zwp_input_method_keyboard_grab_v2*, u32int format,
|
||||||
|
int fd, u32int size)
|
||||||
|
{
|
||||||
|
struct xkb_keymap *keymap;
|
||||||
|
struct xkb_state *state;
|
||||||
|
char *s;
|
||||||
|
|
||||||
|
if(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1){
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s = mmap(nil, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||||
|
if(s == MAP_FAILED){
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
keymap = xkb_keymap_new_from_string(xkbctx, s,
|
||||||
|
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||||
|
munmap(s, size);
|
||||||
|
state = keymap != nil ? xkb_state_new(keymap) : nil;
|
||||||
|
xkb_keymap_unref(keymap);
|
||||||
|
if(state == nil){
|
||||||
|
wllog("cannot read the keyboard mapping");
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xkb_state_unref(kstate);
|
||||||
|
kstate = state;
|
||||||
|
zwp_virtual_keyboard_v1_keymap(vk, format, fd, size);
|
||||||
|
close(fd);
|
||||||
|
haskeymap = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A press, from the keyboard or from the repeat deadline. Returns whether
|
||||||
|
* the engine took it: what it did not take has gone on to the client as a
|
||||||
|
* key, and a key at the client is the client's own to repeat.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
presskey(u32int code)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
char text[Maxutf];
|
||||||
|
u32int key, sym;
|
||||||
|
|
||||||
|
sym = xkb_state_key_get_one_sym(kstate, code + 8);
|
||||||
|
if(composekey(Composewl, &context, sym, text, sizeof text))
|
||||||
|
return 0; /* a sequence in the making */
|
||||||
|
key = ipckeysym(sym, xkb_keysym_to_utf32(sym));
|
||||||
|
if(keymeaningful(key))
|
||||||
|
engaged = 1;
|
||||||
|
if(text[0] != '\0')
|
||||||
|
/* Composed text follows whatever was pending. */
|
||||||
|
sendrequest(Keyreset, 0, 0, &res);
|
||||||
|
else
|
||||||
|
sendrequest(Keypress, key, modmask(), &res);
|
||||||
|
answer(&res, text);
|
||||||
|
takedraw();
|
||||||
|
if(text[0] == '\0' && !res.eaten)
|
||||||
|
forward(code, WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
/* A finished sequence is one act; only a key is held down. */
|
||||||
|
return text[0] == '\0' && res.eaten;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
grabkey(void*, struct zwp_input_method_keyboard_grab_v2*, u32int,
|
||||||
|
u32int time, u32int code, u32int state)
|
||||||
|
{
|
||||||
|
lasttime = time;
|
||||||
|
if(state != WL_KEYBOARD_KEY_STATE_PRESSED){
|
||||||
|
if(code == repcode)
|
||||||
|
repdue = 0;
|
||||||
|
if(issent(code))
|
||||||
|
forward(code, state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
repdue = 0; /* one key repeats at a time, and this is the newest */
|
||||||
|
if(!active || hidden() || kstate == nil){
|
||||||
|
forward(code, state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!presskey(code) || reprate <= 0 ||
|
||||||
|
!xkb_keymap_key_repeats(xkb_state_get_keymap(kstate), code + 8))
|
||||||
|
return;
|
||||||
|
repcode = code;
|
||||||
|
repdue = nowms() + repdelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Our own lookup must see Shift, and so must the client: wlroots derives
|
||||||
|
* no state from a virtual keyboard's keys, so a modifier forwarded as a
|
||||||
|
* keycode arrives bare and Ctrl+C reaches the client as c.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
grabmodifiers(void*, struct zwp_input_method_keyboard_grab_v2*, u32int,
|
||||||
|
u32int depressed, u32int latched, u32int locked, u32int group)
|
||||||
|
{
|
||||||
|
if(kstate != nil)
|
||||||
|
xkb_state_update_mask(kstate, depressed, latched, locked,
|
||||||
|
0, 0, group);
|
||||||
|
if(haskeymap)
|
||||||
|
zwp_virtual_keyboard_v1_modifiers(vk, depressed, latched,
|
||||||
|
locked, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
grabrepeat(void*, struct zwp_input_method_keyboard_grab_v2*, int rate,
|
||||||
|
int delay)
|
||||||
|
{
|
||||||
|
reprate = rate;
|
||||||
|
repdelay = delay;
|
||||||
|
if(rate <= 0)
|
||||||
|
repdue = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zwp_input_method_keyboard_grab_v2_listener grablisten = {
|
||||||
|
grabkeymap, grabkey, grabmodifiers, grabrepeat,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sway hands keys to the grab holder whenever the grab object exists,
|
||||||
|
* without asking whether the input method is active, so a grab held
|
||||||
|
* while inactive takes every key in the session. Two activates can also
|
||||||
|
* arrive without a deactivate between them, and a second grab_keyboard
|
||||||
|
* then fails and leaves a dead object: release before grabbing again.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
grabkeyboard(void)
|
||||||
|
{
|
||||||
|
releasegrab();
|
||||||
|
grab = zwp_input_method_v2_grab_keyboard(im);
|
||||||
|
zwp_input_method_keyboard_grab_v2_add_listener(grab, &grablisten, nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
regglobal(void*, struct wl_registry *r, u32int name, const char *iface, u32int)
|
||||||
|
{
|
||||||
|
if(strcmp(iface, wl_seat_interface.name) == 0){
|
||||||
|
if(seat == nil) /* the first seat is ours */
|
||||||
|
seat = wl_registry_bind(r, name, &wl_seat_interface, 1);
|
||||||
|
}else if(strcmp(iface, wl_compositor_interface.name) == 0)
|
||||||
|
/* 4 for set_buffer_scale and damage_buffer */
|
||||||
|
comp = wl_registry_bind(r, name, &wl_compositor_interface, 4);
|
||||||
|
else if(strcmp(iface, wl_shm_interface.name) == 0)
|
||||||
|
shm = wl_registry_bind(r, name, &wl_shm_interface, 1);
|
||||||
|
else if(strcmp(iface, zwp_input_method_manager_v2_interface.name) == 0)
|
||||||
|
immgr = wl_registry_bind(r, name,
|
||||||
|
&zwp_input_method_manager_v2_interface, 1);
|
||||||
|
else if(strcmp(iface, zwp_virtual_keyboard_manager_v1_interface.name) == 0)
|
||||||
|
vkmgr = wl_registry_bind(r, name,
|
||||||
|
&zwp_virtual_keyboard_manager_v1_interface, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
regremove(void*, struct wl_registry*, u32int)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_registry_listener reglisten = {regglobal, regremove};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Another frontend may have taken the engine; then our preedit and our
|
||||||
|
* popup are both stale. Nothing later will say so: the engine's own
|
||||||
|
* pictures go to whoever took it, and it settles back at blank.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
checkowner(void)
|
||||||
|
{
|
||||||
|
Keyres res;
|
||||||
|
|
||||||
|
if(!preshown && !shown)
|
||||||
|
return;
|
||||||
|
sendrequest(Keycap, 0, 0, &res);
|
||||||
|
if(res.eaten)
|
||||||
|
return;
|
||||||
|
hidepopup();
|
||||||
|
if(preshown){
|
||||||
|
zwp_input_method_v2_set_preedit_string(im, "", 0, 0);
|
||||||
|
zwp_input_method_v2_commit(im, serial);
|
||||||
|
preshown = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One repeat, once the key has been held long enough. Under the grab the
|
||||||
|
* compositor feeds the client nothing while the key is down, so what it
|
||||||
|
* would have repeated there is ours to make -- but only while the engine
|
||||||
|
* wants the key. Backspace stops being ours once the preedit is empty,
|
||||||
|
* and by then presskey has passed it on, so the key is held down at the
|
||||||
|
* client and the client repeats it from there.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
repeat(void)
|
||||||
|
{
|
||||||
|
if(nowms() < repdue)
|
||||||
|
return;
|
||||||
|
if(!active || hidden() || kstate == nil || !presskey(repcode)){
|
||||||
|
repdue = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
repdue = nowms() + 1000/reprate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Whether this session has the protocol, not whether it is Wayland:
|
||||||
|
* GNOME and KWin set WAYLAND_DISPLAY and have no manager, and XIM is the
|
||||||
|
* only path left to them.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
wlinit(void)
|
||||||
|
{
|
||||||
|
struct wl_registry *reg;
|
||||||
|
|
||||||
|
display = wl_display_connect(nil);
|
||||||
|
if(display == nil)
|
||||||
|
return 0;
|
||||||
|
reg = wl_display_get_registry(display);
|
||||||
|
wl_registry_add_listener(reg, ®listen, nil);
|
||||||
|
wl_display_roundtrip(display);
|
||||||
|
/* Without the virtual keyboard every key we do not eat is lost. */
|
||||||
|
if(seat != nil && immgr != nil && vkmgr != nil && comp != nil &&
|
||||||
|
shm != nil)
|
||||||
|
return 1;
|
||||||
|
wl_display_disconnect(display);
|
||||||
|
display = nil;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One process touches libwayland: it reads events, dispatches them and
|
||||||
|
* answers the engine, in order. dispatch reads only when poll says
|
||||||
|
* there is something, so it never blocks and the owner check still runs.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
wlthread(void*)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
int ms, n;
|
||||||
|
|
||||||
|
threadsetname("wl");
|
||||||
|
replyc = chancreate(sizeof(Keyres), 0);
|
||||||
|
xkbctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||||
|
if(xkbctx == nil){
|
||||||
|
wllog("cannot make an xkb context");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
textinit();
|
||||||
|
im = zwp_input_method_manager_v2_get_input_method(immgr, seat);
|
||||||
|
zwp_input_method_v2_add_listener(im, &imlisten, nil);
|
||||||
|
vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(vkmgr, seat);
|
||||||
|
surface = wl_compositor_create_surface(comp);
|
||||||
|
popsurf = zwp_input_method_v2_get_input_popup_surface(im, surface);
|
||||||
|
zwp_input_popup_surface_v2_add_listener(popsurf, &poplisten, nil);
|
||||||
|
pfd.fd = wl_display_get_fd(display);
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
for(;;){
|
||||||
|
if(wl_display_flush(display) < 0 && errno != EAGAIN)
|
||||||
|
break;
|
||||||
|
pfd.revents = 0;
|
||||||
|
if(repdue != 0){
|
||||||
|
ms = repdue - nowms();
|
||||||
|
if(ms < 0)
|
||||||
|
ms = 0;
|
||||||
|
}else
|
||||||
|
ms = preshown || shown ? Ownerpoll : -1;
|
||||||
|
n = poll(&pfd, 1, ms);
|
||||||
|
if(n < 0 && errno != EINTR)
|
||||||
|
break;
|
||||||
|
if(pfd.revents & POLLIN){
|
||||||
|
if(wl_display_dispatch(display) < 0)
|
||||||
|
break;
|
||||||
|
}else if(wl_display_dispatch_pending(display) < 0)
|
||||||
|
break;
|
||||||
|
if(gone){
|
||||||
|
wllog("another input method already has the seat");
|
||||||
|
releasegrab();
|
||||||
|
leave();
|
||||||
|
wl_display_flush(display);
|
||||||
|
bufclear(&bufs[0]);
|
||||||
|
bufclear(&bufs[1]);
|
||||||
|
textclose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* A repeating key's own press keeps the engine ours, so
|
||||||
|
* while one repeats the owner poll has nothing to find. */
|
||||||
|
if(repdue != 0)
|
||||||
|
repeat();
|
||||||
|
else
|
||||||
|
checkowner();
|
||||||
|
}
|
||||||
|
if(wl_display_get_error(display) != 0)
|
||||||
|
die("wl: compositor disconnected");
|
||||||
|
die("wl: frontend stopped");
|
||||||
|
}
|
||||||
3
xim.c
3
xim.c
@@ -384,7 +384,7 @@ kpress(Ic *state, xcb_key_press_event_t *ev)
|
|||||||
int n;
|
int n;
|
||||||
|
|
||||||
sym = keymaplookup(kstate, ev->detail, ev->state);
|
sym = keymaplookup(kstate, ev->detail, ev->state);
|
||||||
if(composekey(sym, text, sizeof text))
|
if(composekey(Composexim, state, sym, text, sizeof text))
|
||||||
return;
|
return;
|
||||||
key = ipckeysym(sym, xkb_keysym_to_utf32(sym));
|
key = ipckeysym(sym, xkb_keysym_to_utf32(sym));
|
||||||
if(keymeaningful(key))
|
if(keymeaningful(key))
|
||||||
@@ -424,6 +424,7 @@ icfree(void *p)
|
|||||||
state = p;
|
state = p;
|
||||||
/* The owner remains valid until imthread acknowledges its release. */
|
/* The owner remains valid until imthread acknowledges its release. */
|
||||||
release(state);
|
release(state);
|
||||||
|
composedrop(Composexim, state);
|
||||||
icunlink(state);
|
icunlink(state);
|
||||||
free(state);
|
free(state);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user