[PATCH 3/3] text: add a composing input method demo

Philipp Brüschweiler blei42 at gmail.com
Sun Jul 22 15:07:40 PDT 2012


---
 clients/.gitignore     |   1 +
 clients/Makefile.am    |   7 +
 clients/hiragana-ime.c | 546 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 Dateien geändert, 554 Zeilen hinzugefügt(+)
 create mode 100644 clients/hiragana-ime.c

diff --git a/clients/.gitignore b/clients/.gitignore
index b43992b..2235a18 100644
--- a/clients/.gitignore
+++ b/clients/.gitignore
@@ -26,6 +26,7 @@ weston-terminal
 weston-screensaver
 wscreensaver
 editor
+hiragana-ime
 text-protocol.c
 text-client-protocol.h
 keyboard
diff --git a/clients/Makefile.am b/clients/Makefile.am
index 133eaca..ed2de14 100644
--- a/clients/Makefile.am
+++ b/clients/Makefile.am
@@ -53,6 +53,7 @@ clients_programs =				\
 	eventdemo				\
 	clickdot				\
 	editor					\
+	hiragana-ime				\
 	keyboard				\
 	$(full_gl_client_programs)
 
@@ -106,6 +107,12 @@ editor_SOURCES = 				\
 	text-client-protocol.h
 editor_LDADD = $(toolkit_libs)
 
+hiragana_ime_SOURCES = 				\
+	hiragana-ime.c				\
+	text-protocol.c				\
+	text-client-protocol.h
+hiragana_ime_LDADD = $(toolkit_libs)
+
 keyboard_SOURCES = 				\
 	keyboard.c				\
 	desktop-shell-client-protocol.h		\
diff --git a/clients/hiragana-ime.c b/clients/hiragana-ime.c
new file mode 100644
index 0000000..122b52b
--- /dev/null
+++ b/clients/hiragana-ime.c
@@ -0,0 +1,546 @@
+/*
+ * Copyright © 2012 Philipp Brüschweiler
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission.  The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "desktop-shell-client-protocol.h"
+#include "text-client-protocol.h"
+#include "window.h"
+
+typedef enum {
+	STATE_EMPTY = 0,
+	STATE_B,
+	STATE_BY,
+	STATE_C,
+	STATE_CH,
+	STATE_D,
+	STATE_DY,
+	STATE_F,
+	STATE_FY,
+	STATE_G,
+	STATE_GY,
+	STATE_H,
+	STATE_HY,
+	STATE_J,
+	STATE_JY,
+	STATE_K,
+	STATE_KY,
+	STATE_L,
+	STATE_LY,
+	STATE_M,
+	STATE_MY,
+	STATE_N,
+	STATE_NY,
+	STATE_P,
+	STATE_PY,
+	STATE_Q,
+	STATE_R,
+	STATE_RY,
+	STATE_S,
+	STATE_SH,
+	STATE_SY,
+	STATE_T,
+	STATE_TY,
+	STATE_V,
+	STATE_VY,
+	STATE_W,
+	STATE_X,
+	STATE_XY,
+	STATE_Y,
+	STATE_Z,
+	STATE_ZY
+} hiragana_state;
+
+struct hiragana_ime {
+	struct display *display;
+	struct input_method *input_method;
+	struct wl_keyboard *input_method_keyboard;
+
+	uint32_t modifiers;
+	struct {
+		struct xkb_context *context;
+		struct xkb_keymap *keymap;
+		struct xkb_state *state;
+		xkb_mod_mask_t control_mask;
+		xkb_mod_mask_t alt_mask;
+		xkb_mod_mask_t shift_mask;
+	} xkb;
+
+	bool on;
+	hiragana_state state;
+};
+
+static const char *
+state_to_preedit(hiragana_state state)
+{
+	switch (state) {
+	case STATE_EMPTY: return "";
+	case STATE_B:     return "b";
+	case STATE_BY:    return "by";
+	case STATE_C:     return "c";
+	case STATE_CH:    return "ch";
+	case STATE_D:     return "d";
+	case STATE_DY:    return "dy";
+	case STATE_F:     return "f";
+	case STATE_FY:    return "fy";
+	case STATE_G:     return "g";
+	case STATE_GY:    return "gy";
+	case STATE_H:     return "h";
+	case STATE_HY:    return "hy";
+	case STATE_J:     return "j";
+	case STATE_JY:    return "jy";
+	case STATE_K:     return "k";
+	case STATE_KY:    return "ky";
+	case STATE_L:     return "l";
+	case STATE_LY:    return "ly";
+	case STATE_M:     return "m";
+	case STATE_MY:    return "my";
+	case STATE_N:     return "n";
+	case STATE_NY:    return "ny";
+	case STATE_P:     return "p";
+	case STATE_PY:    return "py";
+	case STATE_Q:     return "q";
+	case STATE_R:     return "r";
+	case STATE_RY:    return "ry";
+	case STATE_S:     return "s";
+	case STATE_SH:    return "sh";
+	case STATE_SY:    return "sy";
+	case STATE_T:     return "t";
+	case STATE_TY:    return "ty";
+	case STATE_V:     return "v";
+	case STATE_VY:    return "vy";
+	case STATE_W:     return "w";
+	case STATE_X:     return "x";
+	case STATE_XY:    return "xy";
+	case STATE_Y:     return "y";
+	case STATE_Z:     return "z";
+	case STATE_ZY:    return "zy";
+	}
+}
+
+static const char *vowel_table[][5] = {
+	{"あ", "い", "う", "え", "お"}, // STATE_EMPTY
+	{"ば", "び", "ぶ", "べ", "ぼ"}, // STATE_B
+	{"びゃ", "びぃ", "びゅ", "びぇ", "びょ"}, // STATE_BY
+	{"か", "し", "く", "せ", "こ"}, // STATE_C
+	{"ちゃ", "ち", "ちゅ", "ちぇ", "ちょ"}, // STATE_CH
+	{"だ", "ぢ", "づ", "で", "ど"}, // STATE_D
+	{"ぢゃ", "ぢぃ", "ぢゅ", "ぢぇ", "ぢょ"}, // STATE_DY
+	{"ふぁ", "ふぃ", "ふ", "ふぇ", "ふぉ"}, // STATE_F
+	{"ふゃ", "fyい", "ふゅ", "fyえ", "ふょ"}, // STATE_FY
+	{"が", "ぎ", "ぐ", "げ", "ご"}, // STATE_G
+	{"ぎゃ", "ぎぃ", "ぎゅ", "ぎぇ", "ぎょ"}, // STATE_GY
+	{"は", "ひ", "ふ", "へ", "ほ"}, // STATE_H
+	{"ひゃ", "ひぃ", "ひゅ", "ひぇ", "ひょ"}, // STATE_HY
+	{"じゃ", "じ", "じゅ", "じぇ", "じょ"}, // STATE_J
+	{"じゃ", "じぃ", "じゅ", "じぇ", "じょ"}, // STATE_JY
+	{"か", "き", "く", "け", "こ"}, // STATE_K
+	{"きゃ", "きぃ", "きゅ", "きぇ", "きょ"}, // STATE_KY
+	{"ぁ", "ぃ", "ぅ", "ぇ", "ぉ"}, // STATE_L
+	{"ゃ", "ぃ", "ゅ", "ぇ", "ょ"}, // STATE_LY
+	{"ま", "み", "む", "め", "も"}, // STATE_M
+	{"みゃ", "みぃ", "みゅ", "みぇ", "みょ"}, // STATE_MY
+	{"な", "に", "ぬ", "ね", "の"}, // STATE_N
+	{"にゃ", "にぃ", "にゅ", "にぇ", "にょ"}, // STATE_NY
+	{"ぱ", "ぴ", "ぷ", "ぺ", "ぽ"}, // STATE_P
+	{"ぴゃ", "ぴぃ", "ぴゅ", "ぴぇ", "ぴょ"}, // STATE_PY
+	{"くぁ", "くぃ", "く", "くぇ", "くぉ"}, // STATE_Q
+	{"ら", "り", "る", "れ", "ろ"}, // STATE_R
+	{"りゃ", "りぃ", "りゅ", "りぇ", "りょ"}, // STATE_RY
+	{"さ", "し", "す", "せ", "そ"}, // STATE_S
+	{"しゃ", "し", "しゅ", "しぇ", "しょ"}, // STATE_SH
+	{"しゃ", "しぃ", "しゅ", "しぇ", "しょ"}, // STATE_SY
+	{"た", "ち", "つ", "て", "と"}, // STATE_T
+	{"ちゃ", "ちぃ", "ちゅ", "ちぇ", "ちょ"}, // STATE_TY
+	{"ヴぁ", "ヴぃ", "ヴ", "ヴぇ", "ヴぉ"}, // STATE_V
+	{"ヴゃ", "ヴぃ", "ヴゅ", "ヴぇ", "ヴょ"}, // STATE_VY
+	{"わ", "うぃ", "う", "うぇ", "を"}, // STATE_W
+	{"ぁ", "ぃ", "ぅ", "ぇ", "ぉ"}, // STATE_X
+	{"ゃ", "ぃ", "ゅ", "ぇ", "ょ"}, // STATE_XY
+	{"や", "yい", "ゆ", "いぇ", "よ"}, // STATE_Y
+	{"ざ", "じ", "ず", "ぜ", "ぞ"}, // STATE_Z
+	{"じゃ", "じぃ", "じゅ", "じぇ", "じょ"} // STATE_ZY
+};
+
+static inline int
+vowel_index(uint32_t sym)
+{
+	switch (sym) {
+	case 'a': return 0;
+	case 'i': return 1;
+	case 'u': return 2;
+	case 'e': return 3;
+	case 'o': return 4;
+	default: return -1;
+	}
+}
+
+static inline hiragana_state
+consonant_to_state(uint32_t sym)
+{
+	switch (sym) {
+	case 'b': return STATE_B;
+	case 'c': return STATE_C;
+	case 'd': return STATE_D;
+	case 'f': return STATE_F;
+	case 'g': return STATE_G;
+	case 'h': return STATE_H;
+	case 'j': return STATE_J;
+	case 'k': return STATE_K;
+	case 'l': return STATE_L;
+	case 'm': return STATE_M;
+	case 'n': return STATE_N;
+	case 'p': return STATE_P;
+	case 'q': return STATE_Q;
+	case 'r': return STATE_R;
+	case 's': return STATE_S;
+	case 't': return STATE_T;
+	case 'v': return STATE_V;
+	case 'w': return STATE_W;
+	case 'x': return STATE_X;
+	case 'y': return STATE_Y;
+	case 'z': return STATE_Z;
+	default: assert(false);
+	}
+}
+
+static void
+update_state(hiragana_state state,
+	     uint32_t sym,
+	     hiragana_state *new_state,
+	     const char **output)
+{
+	*output = NULL;
+	if (sym == 'y') {
+		switch (state) {
+		case STATE_B: *new_state = STATE_BY; return;
+		case STATE_D: *new_state = STATE_DY; return;
+		case STATE_F: *new_state = STATE_FY; return;
+		case STATE_G: *new_state = STATE_GY; return;
+		case STATE_H: *new_state = STATE_HY; return;
+		case STATE_J: *new_state = STATE_JY; return;
+		case STATE_K: *new_state = STATE_KY; return;
+		case STATE_L: *new_state = STATE_LY; return;
+		case STATE_M: *new_state = STATE_MY; return;
+		case STATE_N: *new_state = STATE_NY; return;
+		case STATE_P: *new_state = STATE_PY; return;
+		case STATE_R: *new_state = STATE_RY; return;
+		case STATE_S: *new_state = STATE_SY; return;
+		case STATE_T: *new_state = STATE_TY; return;
+		case STATE_V: *new_state = STATE_VY; return;
+		case STATE_X: *new_state = STATE_XY; return;
+		case STATE_Z: *new_state = STATE_ZY; return;
+		default: break; // continue normal handling
+		}
+	} else if (sym == 'h') {
+		switch (state) {
+		case STATE_C: *new_state = STATE_CH; return;
+		case STATE_S: *new_state = STATE_SH; return;
+		default: break; // continue normal handling
+		}
+	} else if (sym == XKB_KEY_BackSpace) {
+		switch (state) {
+		case STATE_CH: *new_state = STATE_C; break;
+		case STATE_SH: *new_state = STATE_S; break;
+		case STATE_BY: *new_state = STATE_B; break;
+		case STATE_DY: *new_state = STATE_D; break;
+		case STATE_FY: *new_state = STATE_F; break;
+		case STATE_GY: *new_state = STATE_G; break;
+		case STATE_HY: *new_state = STATE_H; break;
+		case STATE_JY: *new_state = STATE_J; break;
+		case STATE_KY: *new_state = STATE_K; break;
+		case STATE_LY: *new_state = STATE_L; break;
+		case STATE_MY: *new_state = STATE_M; break;
+		case STATE_NY: *new_state = STATE_N; break;
+		case STATE_PY: *new_state = STATE_P; break;
+		case STATE_RY: *new_state = STATE_R; break;
+		case STATE_SY: *new_state = STATE_S; break;
+		case STATE_TY: *new_state = STATE_T; break;
+		case STATE_VY: *new_state = STATE_V; break;
+		case STATE_XY: *new_state = STATE_X; break;
+		case STATE_ZY: *new_state = STATE_Z; break;
+		default:       *new_state = STATE_EMPTY; break;
+		}
+		return;
+	}
+
+	int vi = vowel_index(sym);
+
+	// N needs special handling
+	if (state == STATE_N) {
+		if (vi >= 0) {
+			*new_state = STATE_EMPTY;
+			*output = vowel_table[STATE_N][vi];
+		} else if (sym == 'n') {
+			*new_state = STATE_EMPTY;
+			*output = "ん";
+		} else if (sym == 'y') {
+			*new_state = STATE_NY;
+		} else {
+			*new_state = consonant_to_state(sym);
+			*output = "ん";
+		}
+		return;
+	}
+
+	if (vi >= 0) {
+		*output = vowel_table[state][vi];
+		*new_state = STATE_EMPTY;
+	} else {
+		*output = state_to_preedit(state);
+		*new_state = consonant_to_state(sym);
+	}
+}
+
+static void
+ime_handle_key(struct hiragana_ime *ime, uint32_t key, uint32_t sym,
+	       enum wl_keyboard_key_state state)
+{
+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
+		return;
+
+	if (sym == ' ' && (ime->modifiers & ime->xkb.control_mask)) {
+		ime->on = !ime->on;
+		fprintf(stderr, "turning ime %s\n", ime->on ? "on" : "off");
+		if (!ime->on) {
+			input_method_preedit_string(ime->input_method, "", -1);
+			ime->state = STATE_EMPTY;
+		}
+		return;
+	}
+
+	if (!ime->on)
+		return;
+
+	const char *output = NULL;
+	if (sym == XKB_KEY_BackSpace || (sym >= 'a' && sym <= 'z')) {
+		update_state(ime->state, sym, &ime->state, &output);
+	}
+
+	if (output && *output) {
+		fprintf(stderr, "committing '%s'\n", output);
+		input_method_commit_string(ime->input_method, output, -1);
+	}
+
+	if (ime->state != STATE_EMPTY || !(output && *output)) {
+		const char *preedit_string = state_to_preedit(ime->state);
+		fprintf(stderr, "preedit '%s'\n", preedit_string);
+		input_method_preedit_string(ime->input_method, preedit_string, -1);
+	}
+}
+
+static void reset_hiragana_ime(struct hiragana_ime *ime)
+{
+	ime->on = false;
+	ime->state = STATE_EMPTY;
+}
+
+static void
+input_method_reset(void *data,
+		   struct input_method *input_method)
+{
+	struct hiragana_ime *ime = data;
+	reset_hiragana_ime(ime);
+}
+
+static void
+input_method_keymap(void *data,
+		    struct wl_keyboard *wl_keyboard,
+		    uint32_t format,
+		    int32_t fd,
+		    uint32_t size)
+{
+	struct hiragana_ime *ime = data;
+
+	// XXX: this is copy-pasted from window.c
+	char *map_str;
+
+	if (!data) {
+		close(fd);
+		return;
+	}
+
+	if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
+		close(fd);
+		return;
+	}
+
+	map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+	if (map_str == MAP_FAILED) {
+		close(fd);
+		return;
+	}
+
+	ime->xkb.keymap = xkb_map_new_from_string(ime->xkb.context,
+						  map_str,
+						  XKB_KEYMAP_FORMAT_TEXT_V1,
+						  0);
+	munmap(map_str, size);
+	close(fd);
+
+	if (!ime->xkb.keymap) {
+		fprintf(stderr, "failed to compile keymap\n");
+		return;
+	}
+
+	ime->xkb.state = xkb_state_new(ime->xkb.keymap);
+	if (!ime->xkb.state) {
+		fprintf(stderr, "failed to create XKB state\n");
+		xkb_map_unref(ime->xkb.keymap);
+		ime->xkb.keymap = NULL;
+		return;
+	}
+
+	ime->xkb.control_mask =
+		1 << xkb_map_mod_get_index(ime->xkb.keymap, "Control");
+	ime->xkb.alt_mask =
+		1 << xkb_map_mod_get_index(ime->xkb.keymap, "Mod1");
+	ime->xkb.shift_mask =
+		1 << xkb_map_mod_get_index(ime->xkb.keymap, "Shift");
+}
+
+static void
+input_method_key(void *data,
+		 struct wl_keyboard *wl_keyboard,
+		 uint32_t serial,
+		 uint32_t time,
+		 uint32_t key,
+		 uint32_t state_w)
+{
+	struct hiragana_ime *ime = data;
+
+	// XXX: copied from window.c
+	uint32_t code, num_syms;
+	enum wl_keyboard_key_state state = state_w;
+	const xkb_keysym_t *syms;
+	xkb_keysym_t sym;
+	xkb_mod_mask_t mask;
+
+	code = key + 8;
+	if (!ime->xkb.state)
+		return;
+
+	num_syms = xkb_key_get_syms(ime->xkb.state, code, &syms);
+
+	mask = xkb_state_serialize_mods(ime->xkb.state,
+					XKB_STATE_DEPRESSED |
+					XKB_STATE_LATCHED);
+	ime->modifiers = 0;
+	if (mask & ime->xkb.control_mask)
+		ime->modifiers |= MOD_CONTROL_MASK;
+	if (mask & ime->xkb.alt_mask)
+		ime->modifiers |= MOD_ALT_MASK;
+	if (mask & ime->xkb.shift_mask)
+		ime->modifiers |= MOD_SHIFT_MASK;
+
+	sym = XKB_KEY_NoSymbol;
+	if (num_syms == 1)
+		sym = syms[0];
+
+	ime_handle_key(ime, key, sym, state);
+}
+
+static void
+input_method_modifiers(void *data,
+		       struct wl_keyboard *wl_keyboard,
+		       uint32_t serial,
+		       uint32_t mods_depressed,
+		       uint32_t mods_latched,
+		       uint32_t mods_locked,
+		       uint32_t group)
+{
+	struct hiragana_ime *ime = data;
+
+	// XXX: copied from window.c
+	xkb_state_update_mask(ime->xkb.state, mods_depressed, mods_latched,
+			      mods_locked, 0, 0, group);
+}
+
+static const struct wl_keyboard_listener input_method_keyboard_listener = {
+	input_method_keymap,
+	NULL, /* enter */
+	NULL, /* leave */
+	input_method_key,
+	input_method_modifiers
+};
+
+static const struct input_method_listener input_method_listener = {
+	input_method_reset
+};
+
+static void
+global_handler(struct wl_display *display, uint32_t id,
+	       const char *interface, uint32_t version, void *data)
+{
+	struct hiragana_ime *ime = data;
+
+	if (!strcmp(interface, "input_method")) {
+		ime->input_method = wl_display_bind(display, id, &input_method_interface);
+		input_method_add_listener(ime->input_method,
+					  &input_method_listener,
+					  ime);
+		ime->input_method_keyboard =
+			input_method_request_keyboard(ime->input_method);
+		wl_keyboard_add_listener(ime->input_method_keyboard,
+					 &input_method_keyboard_listener,
+					 ime);
+	}
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct hiragana_ime ime;
+	memset(&ime, 0, sizeof ime);
+
+	reset_hiragana_ime(&ime);
+
+	ime.xkb.context = xkb_context_new(0);
+	if (ime.xkb.context == NULL) {
+		fprintf(stderr, "Failed to create XKB context\n");
+		return 1;
+	}
+
+	ime.display = display_create(argc, argv);
+	if (ime.display == NULL) {
+		fprintf(stderr, "failed to create display: %m\n");
+		return 1;
+	}
+
+	wl_display_add_global_listener(display_get_display(ime.display),
+				       global_handler, &ime);
+
+	display_set_user_data(ime.display, &ime);
+
+	display_run(ime.display);
+
+	xkb_state_unref(ime.xkb.state);
+	xkb_map_unref(ime.xkb.keymap);
+
+	return 0;
+}
-- 
1.7.11.2



More information about the wayland-devel mailing list