├── .gitignore
├── README.org
├── assets
├── structure.drawio
├── structure.png
├── structure_small
├── structure_small.png
├── tmux-rime.png
└── tmux-rime_screenshot.png
├── build.sh
├── scripts
├── bindings.sh
├── config.sh
├── env.sh
├── setup.sh
├── tmux_rime.sh
└── utils.sh
├── slide
└── presentation.pdf
├── tmux_rime.tmux
├── tmux_rime
├── rime_wrapper
│ ├── Makefile
│ ├── __init__.py
│ ├── raw.py
│ ├── rime.c
│ ├── rime.h
│ └── structs.py
├── tmux_rime_client.py
└── tmux_rime_server.py
└── utils
└── debugging.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Packaging
10 | build/
11 |
12 | # SWIG intermediate files
13 | tmux_rime/rime.py
14 | lib/rime_wrap.c
15 |
16 |
--------------------------------------------------------------------------------
/README.org:
--------------------------------------------------------------------------------
1 | * Tmux-Rime
2 | #+html:
3 |
4 |
5 | ** Description
6 | ~tmux-rime~ is a tmux plugin that let you type CJK chars within terminal.
7 |
8 | You may refer to the introduction slide: [[file:slide/presentation.pdf]]
9 |
10 |
11 | ** Program Structure
12 | #+html:
13 |
14 | ** Screenshot
15 | #+html:
16 |
17 | ** Installation
18 | *** Prerequisite
19 | **** FreeBSD
20 |
21 | Install required packages:
22 | #+begin_src sh
23 | pkg install git gcc gmake bash tmux zh-librime zh-fcitx-rime
24 | #+end_src
25 |
26 | You may need to create a symobol link called ~python~ to link to ~python3.X~
27 | #+begin_src sh
28 | mkdir ~/bin
29 | ln -s /usr/local/bin/python3.8 ~/bin/python
30 | #+end_src
31 |
32 |
33 | Create a empty directory ~/usr/share/rime-data~ (I know it's a bad practice in FreeBSD), and put all your rime configs into ~/.config/tmux_rime/rime/~.
34 |
35 | You may also copy the default configs (under ~/usr/local/share/rime-data~):
36 | #+begin_src
37 | sudo touch /usr/share/rime-data
38 | mkdir -p ~/.config/tmux_rime
39 | cp -r /usr/local/share/rime-data ~/.config/tmux_rime/rime
40 | #+end_src
41 |
42 | *** Building
43 |
44 | Clone the repo and build it:
45 | #+begin_src sh
46 | mkdir -p ~/.tmux/plugins/
47 | git clone https://github.com/Cycatz/tmux-rime.git ~/.tmux/plugins/tmux-rime
48 | cd ~/.tmux/plugins/tmux-rime
49 | ./build.sh
50 | #+end_src
51 |
52 | Add the following line to your tmux config, e.g., =~/.tmux.conf=:
53 | #+begin_src sh
54 | run-shell '~/.tmux/plugins/tmux-rime/tmux_rime.tmux'
55 | #+end_src
56 |
57 |
58 | ** Usage
59 | + ~Prefix + t~ to enable the rime mode
60 | + ~Enter~ to commit raw characters
61 | + ~Esc~ to exit the rime mode
62 |
63 |
64 | ** Todo
65 | *** IME functions
66 | + +Preedit chars deleting+
67 | + +Raw input committing+
68 | + Candidate selection
69 | + Schema selection
70 |
71 | *** Customization
72 | + Custom IME status bar
73 | + Custom plugin options
74 | + Use tmux user options (prefixed with ~@~) to define variables
75 | + Custom key mappings
76 | + Define what keys need to be bound in tmux
77 |
78 | *** Misc
79 | + Write tests
80 | + Write documentation
81 | + Set up plugin installation process
82 |
--------------------------------------------------------------------------------
/assets/structure.drawio:
--------------------------------------------------------------------------------
1 | 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
--------------------------------------------------------------------------------
/assets/structure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cycatz/tmux-rime/501ce7754a271d3a3d2474cd6de46a261e54741f/assets/structure.png
--------------------------------------------------------------------------------
/assets/structure_small:
--------------------------------------------------------------------------------
1 | 7VtZb+M2EP41BnYfYuj08Wg7dlKgxQZ1gd19WtASLbGRRJWir/76khRl3fKtOE2CAKGG13C+4cznsdLRJ/72iYDQ/QPb0Otoir3t6I8dTdNMTevwX8XexRK1ryuxxCHIlrJUMEf/QilMhq2QDaPcQIqxR1GYF1o4CKBFczJACN7khy2xl981BA4sCeYW8MrS78imrpSqipJ2PEPkuHLrgSk7fJAMloLIBTbeZET6tKNPCMY0bvnbCfS49RK7xPNmNb17xQgM6DETxh7eKL2ZEX3/5v54+dV/Gi8WDxKeNfBW8sBSWbpLLAADe8QNyZ4sD0QRsjr62KW+xwQqa0aU4Ne9cdixxmXNpLLQzhlb6vkEsQ8p2bEBm9TEiR3djHETGYEeoGidhwhIpJ39cvsdXjBimmiKdEujN4ynSKd80BI0kzUivCIWlNOyNj20kl5ciQLiQFpaiTUyB09FArMT8NM/In5mEhxqrX4sfqWVSp5wY/yMCvx6HuWwhCDIAdn7Z8VDxXiJA/oQiUA5YgM0I9ymnazl8L8W9v1VgCxAoYCFocLWwtYrpMkGTN94j3hGyW8o3NK8pwAPOQF3I+YXkDDBGhLK9vBGssNHts2njwlkCoKFWEphzyG3n7CoOe6Yj3ytFcXxIcTS/FAz4COPA/EMvTXkC8uOuVRKk88T7GEilGTAm+Zs1uSzXEe4bfTaxBm0ojP0pSDj1tqgwq81pd6Fcx7T4B6mauv2egpm6+fpcjGaP4bmpsE9uB2OcQ91WOUef/krJlX+RD53jzkkzErJyguSusQxrriUsPHdWBfwQzFV1w32FwUsMUeYXXdQ6iur9aXWN3OHttlqD/E5iZVTyKWU84IRt7o240OiroOx40EQoqjLbgUTWxEbMkvUfvwtp+JeoxaOSxkKv4jAYBYJDLrhLmOC+LhvhMXXen9oRsfa3850SX25VC3VKu8yAeISSEcEgS2yQxQyReEBS1TqlR9YiGgErwIb2jImbVxE4TwEFu/dMPp6KC3WxqiaQFMRjmpjj24WiIShVsSeXkXsUYsZ62rBRzPLXIIRgCQaY0Jd7OAAeNNUOs7bOB3zO8ahtOzfkNKdZPo8DZxt9+ZcUCArlckh5gkNNuhXo3YhgekbhUyj6sataEf9sW6eVyYeggGtv69yryWw8lvVReRW4n4hE7WZcspZwRIWrM8KzUG6KmqeE6TnUMTlGNWZxJgzTBavow8dpdVBv2uW43S/Mk7vh149Ug9auc6RRVB4+Dq3w86ODRplYtlwK+4xEsVWZ9dstg8M3cj9evK1O+fiT9cifisieX+ge943i/m5+p5X1Thuec8TTthOuSAAPuRxP0Rhlov/74oGF7lKqWjQK/N23WizZqD1Pmm7WgPahbxdL8UFXe/2C7e9hrqPCAG7zDDp3fV76TWfEepqmYcmsEasw1U/SahJme38kNTIPRYosFHgiMwVLJETNSachrLEPeb261jqyKrZmaun/ENCETH6cVGR6tCOp1eeTlVDkiCuAnEWXzTTFPWyCc+Pg0zzK2/HdPMYxee8uK6sGFVSXuEuMdcB3S+z1Qmmegf0697o1s2+nYmrnpJnsXyx8qFIBp8kq5JkqcPDJGtPqNohWWoJlRZIFjMX2f3g87tGT08EP4VgONyPeNzKLeKnXfbpBfJPkMJJhPDOeJusZMQc6pYE7zL09TdG38xifwj3dwrx4C0RVtspqU05UGHM499jlTwtSfFWW9XoPcuCwRoRHPiZbxk+QnmqWIau+qqw7RI0n//Jle6BKxUKUsM3p0qfLPo+PKNn3JtnDN6CRl2BDp3LxK5Io7T+sUz5Uh5V8+brsJCDegUvufEbDNrBVxgqSYUifm5ViZPkLiTYglEk3uqKIoSDU8tXhUsRuSDkzWTdg3RkAaxXR1yUbyvqoQBez/WvQl+MvOsMKgJR1WuW57zqxB7TV+xj50v/U0Gf/gc=
--------------------------------------------------------------------------------
/assets/structure_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cycatz/tmux-rime/501ce7754a271d3a3d2474cd6de46a261e54741f/assets/structure_small.png
--------------------------------------------------------------------------------
/assets/tmux-rime.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cycatz/tmux-rime/501ce7754a271d3a3d2474cd6de46a261e54741f/assets/tmux-rime.png
--------------------------------------------------------------------------------
/assets/tmux-rime_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cycatz/tmux-rime/501ce7754a271d3a3d2474cd6de46a261e54741f/assets/tmux-rime_screenshot.png
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
4 |
5 | gmake -C "$CURRENT_DIR"/tmux_rime/rime_wrapper/
6 |
--------------------------------------------------------------------------------
/scripts/bindings.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 |
4 | CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
5 | TMUX_RIME_CLIENT="$CURRENT_DIR"/../tmux_rime/tmux_rime_client.py
6 |
7 | source "$CURRENT_DIR"/env.sh
8 | source "$CURRENT_DIR"/utils.sh
9 |
10 |
11 | tmux_rime_bind() {
12 | local key="$1"
13 | local command="$2"
14 |
15 | tmux bind-key -T tmux_rime "$key" run-shell -b "'${TMUX_RIME_CLIENT}' ${command}"
16 | }
17 |
18 | bind_reg_keys() {
19 | # for char in $TMUX_RIME_REG_KEYS; do
20 | for char in {0..9} {a..z} {A..Z} "." "," "/" ; do
21 | tmux_rime_bind "$char" "key -k '$char'"
22 | tmux_rime_bind "C-$char" "key -k '$char' -m ctrl"
23 | tmux_rime_bind "M-$char" "key -k '$char' -m alt"
24 | done
25 |
26 | # Special case for semicolon ';'
27 | tmux_rime_bind "\;" "key -k ';'"
28 | tmux_rime_bind "C-\;" "key -k ';' -m ctrl"
29 | tmux_rime_bind "M-\;" "key -k ';' -m alt"
30 |
31 | # Special case for Space
32 | tmux_rime_bind "Space" "key -k Space"
33 | }
34 |
35 | bind_special_keys() {
36 | tmux_rime_bind "DC" "delete-char"
37 | tmux_rime_bind "BSpace" "delete-char"
38 | tmux_rime_bind "Enter" "commit-raw"
39 | tmux_rime_bind "Escape" "exit"
40 | tmux_rime_bind "?" "toggle-help"
41 | tmux_rime_bind "Any" "noop"
42 | }
43 |
44 | main() {
45 | bind_reg_keys
46 | bind_special_keys
47 | }
48 |
49 | main "$@"
50 | exit 0
51 |
--------------------------------------------------------------------------------
/scripts/config.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
4 |
5 | source "$CURRENT_DIR"/env.sh
6 |
7 |
8 |
--------------------------------------------------------------------------------
/scripts/env.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | TMUX_RIME_PREFIX="T"
4 | # TMUX_RIME_REG_KEYS="{a..z} {A..Z} {0..9} Enter Space"
5 |
--------------------------------------------------------------------------------
/scripts/setup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -x
3 |
4 | CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
5 |
6 | source "$CURRENT_DIR"/utils.sh
7 |
8 |
9 | commands_pipe='/tmp/tmux-rime.client'
10 |
11 |
12 | create_rime_window() {
13 | local current_pane
14 | local rime_window_pane
15 | current_pane="$(tmux display-message -p -F ':#{window_id}.#{pane_id}')"
16 | rime_window_pane=$(tmux new-window -F ":#{window_id}.#{pane_id}" -P -d -n "[rime]" "bash --norc --noprofile")
17 | pane_exec "$rime_window_pane" "$CURRENT_DIR/tmux_rime.sh '$current_pane' '$rime_window_pane'"
18 | }
19 |
20 | setup_status_bar() {
21 | current_status="$(tmux show -gqv status-format[0])"
22 | tmux set-option -g status-format[0] "[ㄓ]"
23 | tmux set-option -g status-format[1] "$current_status"
24 | tmux set-option -g status 2
25 | }
26 |
27 | create_commands_pipe() {
28 | # TODO consider the condition that "$commands_pipe" is a regular file
29 | if [[ ! -p "$commands_pipe" ]]; then
30 | mkfifo -m 600 "$commands_pipe"
31 | fi
32 | }
33 |
34 | main() {
35 | create_rime_window
36 | create_commands_pipe
37 | setup_status_bar
38 | exit 0
39 | }
40 |
41 | main "$@"
42 |
--------------------------------------------------------------------------------
/scripts/tmux_rime.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -x
3 |
4 | CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
5 | source "$CURRENT_DIR"/utils.sh
6 | TMUX_RIME_SERVER="$CURRENT_DIR"/../tmux_rime/tmux_rime_server.py
7 |
8 | current_window_pane=""
9 | rime_window_pane=""
10 | tmux_prefix=""
11 | commands_pipe='/tmp/tmux-rime.client'
12 |
13 | enable_rime_mode() {
14 | tmux set-window-option key-table tmux_rime
15 | tmux switch-client -T tmux_rime
16 | tmux_prefix="$(tmux show -gqv prefix)" # Save tmux prefix
17 | tmux set-option -g prefix None
18 | }
19 |
20 | handle_exit() {
21 | # Restore status bar
22 | # TODO: Save status and status-format before start
23 | tmux set-option -g status on
24 | tmux set-option -g -u status-format
25 |
26 | # Restore prefix and key table
27 | tmux set-option -g prefix "$tmux_prefix"
28 | tmux set-window-option key-table root
29 | tmux switch-client -Troot
30 |
31 | local rime_window_id
32 | rime_window_id="$(echo $rime_window_pane | cut -d "." -f 2)"
33 | tmux kill-window -t "$rime_window_id"
34 | }
35 |
36 | init_rime_mode() {
37 | trap "handle_exit" EXIT
38 | "$TMUX_RIME_SERVER" &
39 | enable_rime_mode
40 | }
41 |
42 | insert_text() {
43 | local inserted_text
44 | inserted_text="${1#insert }"
45 | tmux send-key -l -t "$current_window_pane" "$inserted_text"
46 | }
47 |
48 | update_status() {
49 | local status_str
50 |
51 | if [[ "$1" == "status" ]]; then
52 | status_str="[ㄓ]"
53 | else
54 | status_str="${1#status }"
55 | fi
56 | tmux set -g status-format[0] "$status_str"
57 | }
58 |
59 |
60 | main() {
61 | current_window_pane="$1"
62 | rime_window_pane="$2"
63 | init_rime_mode
64 |
65 | while read -r -s statement; do
66 | echo "$current_window_pane" >> /tmp/pane
67 | echo "$statement" >> /tmp/statement
68 | case $statement in
69 | insert*)
70 | insert_text "$statement"
71 | ;;
72 | status*)
73 | update_status "$statement"
74 | ;;
75 | toggle-help)
76 | :
77 | ;;
78 | noop)
79 | :
80 | ;;
81 | exit)
82 | break
83 | ;;
84 | esac
85 | done < <(tail -f "$commands_pipe")
86 | exit 0
87 | }
88 |
89 | main "$@"
90 |
--------------------------------------------------------------------------------
/scripts/utils.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | create_pipe() {
4 | local pipe_path=$(mktemp -u "${TMPDIR:-/tmp}/tmux_rime.XXXXXXXX")
5 | mkfifo -m 600 "$pipe_path"
6 | echo "$pipe_path"
7 | }
8 |
9 | pane_exec() {
10 | local pane_id="$1"
11 | local pane_command="$2"
12 |
13 | tmux send-keys -t "$pane_id" "$pane_command"
14 | tmux send-keys -t "$pane_id" Enter
15 | }
16 |
--------------------------------------------------------------------------------
/slide/presentation.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cycatz/tmux-rime/501ce7754a271d3a3d2474cd6de46a261e54741f/slide/presentation.pdf
--------------------------------------------------------------------------------
/tmux_rime.tmux:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | PLUGIN_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
4 |
5 | source "$PLUGIN_DIR"/scripts/utils.sh
6 | source "$PLUGIN_DIR"/scripts/env.sh
7 |
8 |
9 | tmux run-shell -b "bash --norc --noprofile $PLUGIN_DIR/scripts/config.sh"
10 | tmux run-shell -b "$PLUGIN_DIR/scripts/bindings.sh"
11 |
12 | tmux bind-key "$TMUX_RIME_PREFIX" run-shell "$PLUGIN_DIR/scripts/setup.sh"
13 |
--------------------------------------------------------------------------------
/tmux_rime/rime_wrapper/Makefile:
--------------------------------------------------------------------------------
1 | CXX = gcc
2 | CXXFLAGS = -fPIC -shared -Wall -Wextra -Wstrict-prototypes -O2 -std=c11
3 | LDLIBS += -lrime
4 |
5 | SRC_EXT := c
6 | SOURCE := rime.$(SRC_EXT)
7 | TARGET := libwrime.so
8 |
9 | .PHONY = clean
10 |
11 | all: $(TARGET)
12 |
13 | $(TARGET): $(SOURCE)
14 | @echo "$(CXX) $(CXXFLAGS) $(LDLIBS) $< -o $@"; $(CXX) $(CXXFLAGS) $(LDLIBS) $< -o $@
15 | clean:
16 | @echo "$(RM) $(TARGET)"; $(RM) $(TARGET)
17 |
--------------------------------------------------------------------------------
/tmux_rime/rime_wrapper/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from ctypes import *
4 | from rime_wrapper.raw import *
5 |
6 | class RimeWrapper:
7 | def __init__(self):
8 | self.rime_wrapper_ptr = rime_wrapper_init()
9 |
10 | def start(self):
11 | rime_wrapper_start(self.rime_wrapper_ptr, 0)
12 |
13 | def has_composition(self, _context=None):
14 | new_created = False
15 | if _context is None:
16 | new_created = True
17 | _context = rime_wrapper_get_context(self.rime_wrapper_ptr)
18 | res = _context.contents.composition is not None and _context.contents.composition.length > 0
19 | if new_created:
20 | rime_wrapper_free_context(self.rime_wrapper_ptr, _context)
21 |
22 | return res
23 |
24 | def has_candidates(self, _context=None):
25 | new_created = False
26 | if _context is None:
27 | new_created = True
28 | _context = rime_wrapper_get_context(self.rime_wrapper_ptr)
29 |
30 | res = _context.contents.menu.page_size > 0
31 | if new_created:
32 | rime_wrapper_free_context(self.rime_wrapper_ptr, _context)
33 |
34 | return res
35 |
36 | def get_schema_list(self):
37 | _schema_list = rime_wrapper_get_schema_list(self.rime_wrapper_ptr)
38 |
39 | list_size = _schema_list.contents.size
40 | schema_list = _schema_list.contents.list
41 |
42 | items = []
43 | for i in range(list_size):
44 | schema = schema_list[i]
45 | items.append((schema.schema_id.decode('utf-8'),
46 | schema.name.decode('utf-8')))
47 | rime_wrapper_free_schema_list(self.rime_wrapper_ptr, _schema_list)
48 | return items
49 |
50 | def set_schema(self, schema_id):
51 | return rime_wrapper_set_schema(self.rime_wrapper_ptr, c_char_p(schema_id.encode('utf-8')))
52 |
53 | def get_commit_text_preview(self):
54 | _context = rime_wrapper_get_context(self.rime_wrapper_ptr)
55 | commit_text_preview = _context.contents.commit_text_preview.decode('utf-8')
56 | rime_wrapper_free_context(self.rime_wrapper_ptr, _context)
57 |
58 | return commit_text_preview
59 |
60 | def get_candidates(self):
61 | _context = rime_wrapper_get_context(self.rime_wrapper_ptr)
62 | menu = _context.contents.menu
63 |
64 | candidates = []
65 |
66 | if self.has_candidates():
67 | page_size = menu.page_size
68 | page_no = menu.page_no
69 | num_candidates = menu.num_candidates
70 | _candidates = menu.candidates
71 |
72 | for i in range(num_candidates):
73 | candidate = _candidates[i]
74 |
75 | text = candidate.text.decode('utf-8')
76 | comment = ''
77 | if candidate.comment is not None:
78 | comment = candidate.comment.decode('utf-8')
79 | candidates.append((text, comment))
80 |
81 | rime_wrapper_free_context(self.rime_wrapper_ptr, _context)
82 | return candidates
83 |
84 | def get_composition(self):
85 | _context = rime_wrapper_get_context(self.rime_wrapper_ptr)
86 |
87 | composition = RimeComposition()
88 | if self.has_composition(_context):
89 | # Deep copy
90 | composition = RimeComposition(_context.contents.composition.length,
91 | _context.contents.composition.cursor_pos,
92 | _context.contents.composition.sel_start,
93 | _context.contents.composition.sel_end,
94 | _context.contents.composition.preedit)
95 |
96 | rime_wrapper_free_context(self.rime_wrapper_ptr, _context)
97 |
98 | return composition
99 |
100 | def get_composition_preedit(self):
101 | _context = rime_wrapper_get_context(self.rime_wrapper_ptr)
102 |
103 | composition_preedit = ''
104 | if self.has_composition(_context):
105 | composition_preedit = _context.contents.composition.preedit.decode('utf-8')
106 |
107 | rime_wrapper_free_context(self.rime_wrapper_ptr, _context)
108 | return composition_preedit
109 |
110 |
111 | def get_commit_text_preview(self):
112 | _context = rime_wrapper_get_context(self.rime_wrapper_ptr)
113 |
114 | commit_text_preview = ''
115 | _commit_text_preview = _context.contents.commit_text_preview
116 | if _commit_text_preview is not None:
117 | commit_text_preview = _commit_text_preview.decode('utf-8')
118 | rime_wrapper_free_context(self.rime_wrapper_ptr, _context)
119 | return commit_text_preview
120 | # def get_context(self):
121 | # _context = rime_wrapper_get_context(self.rime_wrapper_ptr)
122 | # commit_text_preview = _context.contents.commit_text_preview.decode('utf-8')
123 | # select_labels = _context.contents.select_labels
124 | # rime_wrapper_free_context(self.rime_wrapper_ptr, _context)
125 |
126 | def get_commit(self):
127 | _commit = rime_wrapper_get_commit(self.rime_wrapper_ptr)
128 |
129 | text = ''
130 | if _commit is None:
131 | print('Error')
132 | else:
133 | try:
134 | data_size = _commit.contents.data_size
135 | text = _commit.contents.text.decode('utf-8')
136 | except ValueError: # null pointer
137 | print('Error!')
138 | pass
139 |
140 | rime_wrapper_free_commit(self.rime_wrapper_ptr, _commit)
141 | return text
142 |
143 | def process_key(self, keycode, mask):
144 | return rime_wrapper_process_key(self.rime_wrapper_ptr, keycode, mask)
145 |
146 |
147 | def commit_composition(self):
148 | return rime_wrapper_commit_composition(self.rime_wrapper_ptr)
149 |
150 | def clear_composition(self):
151 | rime_wrapper_clear_composition(self.rime_wrapper_ptr)
152 |
153 | def get_input_str(self):
154 | _input_str = rime_wrapper_get_input_str(self.rime_wrapper_ptr)
155 | if _input_str is None:
156 | print('Error!')
157 | return ''
158 | else:
159 | input_str = cast(_input_str, c_char_p).value.decode('utf-8')
160 | rime_wrapper_free_str(_input_str)
161 | return input_str
162 |
163 | def finish(self):
164 | rime_wrapper_finish(self.rime_wrapper_ptr)
165 |
166 |
167 | # rime_wrapper.start()
168 | # rime_wrapper.finish()
169 |
--------------------------------------------------------------------------------
/tmux_rime/rime_wrapper/raw.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import platform
4 | from ctypes import *
5 | import ctypes.util
6 |
7 | import rime_wrapper
8 | from rime_wrapper.structs import *
9 |
10 | system = platform.system()
11 |
12 | if system == 'Windows':
13 | library_name = 'libwrime.dll'
14 | elif system == 'Darwin':
15 | library_name = 'libwrime.dylib'
16 | else:
17 | library_name = 'libwrime.so'
18 |
19 | filename = os.path.join(os.path.dirname(rime_wrapper.__file__), library_name)
20 |
21 | try:
22 | _lib = ctypes.CDLL(filename)
23 | except (OSError, TypeError):
24 | _lib = None
25 | raise RuntimeError('Rime wrapper library not found')
26 |
27 | rime_wrapper_init = _lib.rime_wrapper_init
28 | rime_wrapper_init.argtypes = []
29 | rime_wrapper_init.restype = c_void_p
30 |
31 | rime_wrapper_start = _lib.rime_wrapper_start
32 | rime_wrapper_start.argtypes = [c_void_p, c_int]
33 | rime_wrapper_start.restype = c_int
34 |
35 | rime_wrapper_finish = _lib.rime_wrapper_finish
36 | rime_wrapper_finish.argtypes = [c_void_p]
37 | rime_wrapper_finish.restype = c_int
38 |
39 | rime_wrapper_get_input_str = _lib.rime_wrapper_get_input_str
40 | rime_wrapper_get_input_str.argtypes = [c_void_p]
41 | rime_wrapper_get_input_str.restype = c_void_p
42 |
43 | rime_wrapper_free_str = _lib.rime_wrapper_free_str
44 | rime_wrapper_free_str.argtypes = [c_void_p]
45 | rime_wrapper_free_str.restype = None
46 |
47 | rime_wrapper_set_cursor_pos = _lib.rime_wrapper_set_cursor_pos
48 | rime_wrapper_set_cursor_pos.argtypes = [c_void_p, c_int]
49 | rime_wrapper_set_cursor_pos.restype = None
50 |
51 | rime_wrapper_clear_composition = _lib.rime_wrapper_clear_composition
52 | rime_wrapper_clear_composition.argtypes = [c_void_p]
53 | rime_wrapper_clear_composition.restype = None
54 |
55 | rime_wrapper_get_commit = _lib.rime_wrapper_get_commit
56 | rime_wrapper_get_commit.argtypes = [c_void_p]
57 | rime_wrapper_get_commit.restype = POINTER(RimeCommit)
58 |
59 | rime_wrapper_free_commit = _lib.rime_wrapper_free_commit
60 | rime_wrapper_free_commit.argtypes = [c_void_p, POINTER(RimeCommit)]
61 | rime_wrapper_free_commit.restype = None
62 |
63 | rime_wrapper_get_context = _lib.rime_wrapper_get_context
64 | rime_wrapper_get_context.argtypes = [c_void_p]
65 | rime_wrapper_get_context.restype = POINTER(RimeContext)
66 |
67 | rime_wrapper_free_context = _lib.rime_wrapper_free_context
68 | rime_wrapper_free_context.argtypes = [c_void_p, POINTER(RimeContext)]
69 | rime_wrapper_free_context.restype = None
70 |
71 | rime_wrapper_get_schema_list = _lib.rime_wrapper_get_schema_list
72 | rime_wrapper_get_schema_list.argtypes = [c_void_p]
73 | rime_wrapper_get_schema_list.restype = POINTER(RimeSchemaList)
74 |
75 | rime_wrapper_free_schema_list = _lib.rime_wrapper_free_schema_list
76 | rime_wrapper_free_schema_list.argtypes = [c_void_p, POINTER(RimeSchemaList)]
77 | rime_wrapper_free_schema_list.restype = None
78 |
79 | rime_wrapper_set_schema = _lib.rime_wrapper_set_schema
80 | rime_wrapper_set_schema.argtypes = [c_void_p, c_char_p]
81 | rime_wrapper_set_schema.restype = c_int
82 |
83 |
84 | rime_wrapper_commit_composition = _lib.rime_wrapper_commit_composition
85 | rime_wrapper_commit_composition.argtypes = [c_void_p]
86 | rime_wrapper_commit_composition.restype = c_int
87 |
88 | rime_wrapper_process_key = _lib.rime_wrapper_process_key
89 | rime_wrapper_process_key.argtypes = [c_void_p, c_int, c_int]
90 | rime_wrapper_process_key.restype = c_int
91 |
92 |
--------------------------------------------------------------------------------
/tmux_rime/rime_wrapper/rime.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "rime.h"
4 |
5 | // Redefine related macros for the pointer type
6 | // #define RIME_STRUCT_INIT_P(Type, var) ((var)->data_size = sizeof(Type) - sizeof((var)->data_size))
7 | #define RIME_STRUCT_P(Type, var) *var = (Type){0}; RIME_STRUCT_INIT(Type, *var);
8 |
9 | static char *copy_string(const char *src)
10 | {
11 | char *dst;
12 | size_t len = strlen(src);
13 |
14 | rime_wrapper_malloc(dst, len + 1, NULL);
15 | strcpy(dst, src);
16 | dst[len] = '\0';
17 |
18 | return dst;
19 | }
20 |
21 | void rime_wrapper_notification_handler(void* context_object,
22 | RimeSessionId session_id,
23 | const char* message_type,
24 | const char* message_value)
25 |
26 | {
27 | }
28 |
29 | int rime_wrapper_process_key(RimeWrapper *rime_wrapper, int keycode, int mask)
30 | {
31 | if (!rime_wrapper->api->process_key(rime_wrapper->session_id, keycode, mask)) {
32 | return -1;
33 | }
34 | return 0;
35 | }
36 |
37 | int rime_wrapper_commit_composition(RimeWrapper *rime_wrapper)
38 | {
39 | if (!rime_wrapper->api->commit_composition(rime_wrapper->session_id)) {
40 | return -1;
41 | }
42 | return 0;
43 | }
44 |
45 | char *rime_wrapper_get_input_str(RimeWrapper *rime_wrapper)
46 | {
47 | const char* input = rime_wrapper->api->get_input(rime_wrapper->session_id);
48 | if (!input) {
49 | return NULL;
50 | }
51 | return copy_string(input);
52 | }
53 |
54 | void rime_wrapper_free_str(char *str)
55 | {
56 | free(str);
57 | }
58 |
59 | void rime_wrapper_set_cursor_pos(RimeWrapper *rime_wrapper, int pos)
60 | {
61 | rime_wrapper->api->set_caret_pos(rime_wrapper->session_id, pos);
62 | }
63 |
64 | void rime_wrapper_clear_composition(RimeWrapper *rime_wrapper)
65 | {
66 | rime_wrapper->api->clear_composition(rime_wrapper->session_id);
67 | }
68 |
69 | RimeCommit *rime_wrapper_get_commit(RimeWrapper *rime_wrapper)
70 | {
71 | RimeCommit *commit;
72 |
73 | rime_wrapper_malloc(commit, sizeof(RimeCommit), NULL);
74 |
75 | RIME_STRUCT_P(RimeCommit, commit);
76 |
77 | if (!rime_wrapper->api->get_commit(rime_wrapper->session_id, commit)) {
78 | return NULL;
79 | }
80 |
81 | return commit;
82 | }
83 |
84 | void rime_wrapper_free_commit(RimeWrapper *rime_wrapper, RimeCommit *commit)
85 | {
86 | rime_wrapper->api->free_commit(commit);
87 | free(commit);
88 | }
89 |
90 |
91 | RimeContext *rime_wrapper_get_context(RimeWrapper *rime_wrapper)
92 | {
93 | RimeContext *context;
94 |
95 | rime_wrapper_malloc(context, sizeof(RimeContext), NULL);
96 |
97 | RIME_STRUCT_P(RimeContext, context);
98 |
99 | if (!rime_wrapper->api->get_context(rime_wrapper->session_id, context)) {
100 | return NULL;
101 | }
102 |
103 | return context;
104 | }
105 |
106 | void rime_wrapper_free_context(RimeWrapper *rime_wrapper, RimeContext *context)
107 | {
108 | rime_wrapper->api->free_context(context);
109 | free(context);
110 | }
111 |
112 | RimeSchemaList *rime_wrapper_get_schema_list(RimeWrapper *rime_wrapper)
113 | {
114 | RimeSchemaList *schema_list;
115 |
116 | rime_wrapper_malloc(schema_list, sizeof(schema_list), NULL);
117 |
118 | if (!rime_wrapper->api->get_schema_list(schema_list)) {
119 | return NULL;
120 | }
121 | return schema_list;
122 | }
123 |
124 | void rime_wrapper_free_schema_list(RimeWrapper *rime_wrapper, RimeSchemaList *schema_list)
125 | {
126 | rime_wrapper->api->free_schema_list(schema_list);
127 | free(schema_list);
128 | }
129 |
130 | int rime_wrapper_set_schema(RimeWrapper *rime_wrapper, const char *schema_id)
131 | {
132 |
133 | if (!rime_wrapper->api->select_schema(rime_wrapper->session_id, schema_id)) {
134 | return -1;
135 | }
136 |
137 | return 0;
138 | }
139 | int rime_wrapper_start(RimeWrapper *rime_wrapper, int fullcheck)
140 | {
141 | const char *shared_data_dir = RIME_DATA_DIR;
142 | const char *user_data_dir = RIME_USER_DIR;
143 |
144 | RIME_STRUCT(RimeTraits, rime_wrapper_traits);
145 | rime_wrapper_traits.shared_data_dir = shared_data_dir;
146 | rime_wrapper_traits.user_data_dir = user_data_dir;
147 | rime_wrapper_traits.app_name = "rime.tmux-rime";
148 | rime_wrapper_traits.distribution_name = "Rime";
149 | rime_wrapper_traits.distribution_code_name = "tmux-rime";
150 | rime_wrapper_traits.distribution_version = RIME_WRAPPER_VERSION;
151 |
152 | if (rime_wrapper->first_run) {
153 | rime_wrapper->api->setup(&rime_wrapper_traits);
154 | rime_wrapper->first_run = 0;
155 | }
156 | rime_wrapper->api->initialize(&rime_wrapper_traits);
157 | rime_wrapper->api->set_notification_handler(rime_wrapper_notification_handler, rime_wrapper);
158 | rime_wrapper->api->start_maintenance(fullcheck);
159 |
160 | rime_wrapper->session_id = rime_wrapper->api->create_session();
161 |
162 | // wait for deploy
163 | rime_wrapper->api->join_maintenance_thread();
164 |
165 | return 0;
166 | }
167 |
168 | int rime_wrapper_finish(RimeWrapper *rime_wrapper)
169 | {
170 | if (rime_wrapper->session_id) {
171 | rime_wrapper->api->destroy_session(rime_wrapper->session_id);
172 | rime_wrapper->session_id = 0;
173 | }
174 | rime_wrapper->api->finalize();
175 | free(rime_wrapper);
176 | return 0;
177 | }
178 |
179 | RimeWrapper *rime_wrapper_init(void)
180 | {
181 | RimeWrapper *rime_wrapper;
182 | rime_wrapper_malloc(rime_wrapper, sizeof(RimeWrapper), NULL);
183 |
184 | rime_wrapper->first_run = 1;
185 | rime_wrapper->api = rime_get_api();
186 | if (!rime_wrapper->api) {
187 | free(rime_wrapper);
188 | return NULL;
189 | }
190 |
191 | return rime_wrapper;
192 | // rime_wrapper_start(rime_wrapper, 0);
193 | // rime_wrapper_get_schemas(rime_wrapper);
194 | // rime_wrapper_get_commit(rime_wrapper);
195 | // rime_wrapper_finish(rime_wrapper);
196 | // return 0;
197 | }
198 |
199 | /*
200 | int main()
201 | {
202 | rime_wrapper_init();
203 | }
204 | */
205 |
--------------------------------------------------------------------------------
/tmux_rime/rime_wrapper/rime.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include
4 | #include
5 |
6 | #define RIME_DATA_DIR "/usr/share/rime-data"
7 | #define RIME_USER_DIR "/home/cycatz/.config/tmux_rime/rime"
8 | #define RIME_WRAPPER_VERSION "0.0.1"
9 |
10 | #define rime_wrapper_malloc(x, size, ret) \
11 | do { \
12 | (x) = malloc((size)); \
13 | if ((x) == NULL) { \
14 | perror("malloc\n"); \
15 | return (ret); \
16 | } \
17 | } while (0)
18 |
19 | typedef struct _RimeWrapper {
20 | RimeSessionId session_id;
21 | RimeApi *api;
22 | int first_run;
23 | } RimeWrapper;
24 |
25 | void rime_wrapper_notification_handler(void*, RimeSessionId, const char*, const char*);
26 | int rime_wrapper_process_key(RimeWrapper *, int, int);
27 | int rime_wrapper_commit_composition(RimeWrapper *);
28 |
29 | char *rime_wrapper_get_input_str(RimeWrapper *);
30 | void rime_wrapper_free_str(char *);
31 |
32 | void rime_wrapper_set_cursor_pos(RimeWrapper *, int);
33 | void rime_wrapper_clear_composition(RimeWrapper *);
34 |
35 | RimeCommit *rime_wrapper_get_commit(RimeWrapper *);
36 | void rime_wrapper_free_commit(RimeWrapper *, RimeCommit *);
37 |
38 | RimeContext *rime_wrapper_get_context(RimeWrapper *);
39 | void rime_wrapper_free_context(RimeWrapper *, RimeContext *);
40 |
41 | RimeSchemaList *rime_wrapper_get_schema_list(RimeWrapper *);
42 | void rime_wrapper_free_schema_list(RimeWrapper *, RimeSchemaList *);
43 |
44 | int rime_wrapper_set_schema(RimeWrapper *, const char *);
45 | int rime_wrapper_start(RimeWrapper *, int);
46 | int rime_wrapper_finish(RimeWrapper *);
47 | RimeWrapper *rime_wrapper_init(void);
48 |
--------------------------------------------------------------------------------
/tmux_rime/rime_wrapper/structs.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from ctypes import *
4 |
5 | class RimeSchemaListItem(Structure):
6 | _fields_ = [('schema_id', c_char_p),
7 | ('name', c_char_p),
8 | ('reserved', c_void_p)]
9 |
10 |
11 | class RimeSchemaList(Structure):
12 | _fields_ = [('size', c_size_t),
13 | ('list', POINTER(RimeSchemaListItem))]
14 |
15 |
16 | class RimeCommit(Structure):
17 | _fields_ = [('data_size', c_int),
18 | ('text', c_char_p)]
19 |
20 | class RimeCandidate(Structure):
21 | _fields_ = [('text', c_char_p),
22 | ('comment', c_char_p),
23 | ('reserved', c_void_p)]
24 |
25 |
26 | class RimeMenu(Structure):
27 | _fields_ = [('page_size', c_int),
28 | ('page_no', c_int),
29 | ('is_last_page', c_bool),
30 | ('highlighted_candidate_index', c_int),
31 | ('num_candidates', c_int),
32 | ('candidates', POINTER(RimeCandidate)),
33 | ('select_keys', c_char_p)]
34 |
35 | class RimeComposition(Structure):
36 | _fields_ = [('length', c_int),
37 | ('cursor_pos', c_int),
38 | ('sel_start', c_int),
39 | ('sel_end', c_int),
40 | ('preedit', c_char_p)]
41 |
42 |
43 |
44 | class RimeContext(Structure):
45 | _fields_ = [('data_size', c_int),
46 | ('composition', RimeComposition),
47 | ('menu', RimeMenu),
48 | # v0.9.2
49 | ('commit_text_preview', c_char_p),
50 | ('select_labels', POINTER(c_char_p))]
51 |
--------------------------------------------------------------------------------
/tmux_rime/tmux_rime_client.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 | import logging
5 | import socket
6 | import argparse
7 |
8 |
9 | # Fix the FIFO variable for testing
10 | TMUX_RIME_FIFO = '/tmp/tmux-rime.client'
11 |
12 |
13 | def init_logging():
14 | logging_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
15 | logging_path = os.path.join(logging_dir, 'tmux_rime_client.log')
16 | logging.basicConfig(filename=logging_path,
17 | format='[%(asctime)s] %(levelname)-8s %(message)s',
18 | datefmt='%m-%d %H:%M:%S',
19 | level=logging.INFO)
20 |
21 |
22 | class TmuxRimeParser:
23 | def __init__(self, client):
24 | self.client = client
25 |
26 | def start_session(self, args):
27 | self.client.start_session()
28 |
29 | def exit_session(self, args):
30 | self.client.exit_session()
31 |
32 | def send_key(self, args):
33 | self.client.send_key(key=args.key,
34 | modifier=args.modifier)
35 |
36 | def delete_char(self, args):
37 | self.client.delete_char()
38 |
39 | def commit_raw(self, args):
40 | self.client.commit_raw()
41 |
42 | def output(self, args):
43 | self.client.output()
44 |
45 | def parse(self):
46 | parser = argparse.ArgumentParser()
47 | subparser = parser.add_subparsers()
48 |
49 | parser_start = subparser.add_parser('start', help='Start a rime session')
50 | parser_start.set_defaults(func=self.start_session)
51 |
52 | parser_key = subparser.add_parser('key', help='Send keys to a session')
53 | parser_key.add_argument('-k', '--key', type=str, required=True, help='Specify the key')
54 | parser_key.add_argument('-m', '--modifier', type=str, help='Specify the key modifier')
55 | parser_key.set_defaults(func=self.send_key)
56 |
57 | parser_delete_char = subparser.add_parser('delete-char',
58 | help='Delete a char')
59 | parser_delete_char.set_defaults(func=self.delete_char)
60 |
61 | parser_commit_raw = subparser.add_parser('commit-raw',
62 | help='Commit the raw input')
63 | parser_commit_raw.set_defaults(func=self.commit_raw)
64 |
65 | parser_output = subparser.add_parser('output', help='Get inserted text')
66 | parser_output.set_defaults(func=self.output)
67 |
68 | parser_exit = subparser.add_parser('exit', help='Exit a rime session')
69 | parser_exit.set_defaults(func=self.exit_session)
70 |
71 | args = parser.parse_args()
72 | args.func(args)
73 |
74 |
75 | class TmuxRimeClient:
76 | def __init__(self):
77 | self.server_address = '/tmp/tmux-rime-socket'
78 | # [test] start session when initializing
79 | # self.start(0)
80 |
81 | def send_data(self, data, recv=False):
82 | with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
83 | s.connect(self.server_address)
84 | try:
85 | s.sendall(data)
86 | if recv:
87 | res = s.recv(1024)
88 | return res
89 | finally:
90 | s.close()
91 |
92 | def write_pipe(self, data):
93 | with open(TMUX_RIME_FIFO, 'w') as f:
94 | f.write(data)
95 |
96 | def start_session(self):
97 | logging.info('Start session')
98 | self.send_data('start'.encode('utf-8'))
99 |
100 | def exit_session(self):
101 | logging.info('Exit session')
102 | self.send_data('exit'.encode('utf-8'))
103 | self.write_pipe('exit\n')
104 |
105 | def tmux_insert_text(self):
106 | inserted_text = self.send_data('output'.encode('utf-8'), True)
107 | if inserted_text is not None and len(inserted_text) > 0:
108 | message = 'insert {}'.format(inserted_text.decode('utf-8'))
109 | logging.info(message)
110 | self.write_pipe(message + '\n')
111 |
112 | def tmux_update_status(self, status_str):
113 | if status_str is not None:
114 | message = 'status {}'.format(status_str.decode('utf-8'))
115 | logging.info(message)
116 | self.write_pipe(message + '\n')
117 |
118 | def send_key(self, key, modifier):
119 | logging.info('Get key: {}, modifier: {}'.format(key, modifier))
120 |
121 | status_str = self.send_data('key {} {}'.format(key, modifier)
122 | .encode('utf-8'), True)
123 |
124 | self.tmux_update_status(status_str)
125 | self.tmux_insert_text()
126 |
127 | def delete_char(self):
128 | logging.info('Delete a char')
129 | status_str = self.send_data('delete'.encode('utf-8'), True)
130 | self.tmux_update_status(status_str)
131 |
132 | def commit_raw(self):
133 | logging.info('Commit the raw input')
134 | status_str = self.send_data('raw'.encode('utf-8'), True)
135 |
136 | self.tmux_update_status(status_str)
137 | self.tmux_insert_text()
138 |
139 | def output(self):
140 | res = self.send_data('output'.encode('utf-8'), True)
141 | if res is not None:
142 | logging.info(res)
143 |
144 |
145 | if __name__ == '__main__':
146 | init_logging()
147 | client = TmuxRimeClient()
148 | parser = TmuxRimeParser(client)
149 | parser.parse()
150 |
--------------------------------------------------------------------------------
/tmux_rime/tmux_rime_server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import re
4 | import sys
5 | import logging
6 | import socketserver
7 | from rime_wrapper import RimeWrapper
8 |
9 |
10 | def init_logging():
11 | logging_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
12 | logging_path = os.path.join(logging_dir, 'tmux_rime_server.log')
13 |
14 | file_handler = logging.FileHandler(filename=logging_path)
15 | stderr_handler = logging.StreamHandler(sys.stderr)
16 | handlers = [file_handler, stderr_handler]
17 |
18 | logging.basicConfig(format='[%(asctime)s] %(levelname)-8s %(message)s',
19 | datefmt='%m-%d %H:%M:%S',
20 | level=logging.INFO,
21 | handlers=handlers)
22 |
23 |
24 | class TmuxRimeSession:
25 | def start(self):
26 | self.rime = RimeWrapper()
27 | self.rime.start()
28 | self.rime.set_schema('bopomofo')
29 | self.output_text = ''
30 |
31 | def finish(self):
32 | self.rime.finish()
33 |
34 | def handle_key(self, key, modifier):
35 | # Special case for space
36 | self.output_text = ''
37 | if key == 'Space':
38 | key = ' '
39 | if key == ' ' and not self.rime.has_candidates():
40 | self.commit_text()
41 | else:
42 | self.rime.process_key(ord(key), 0)
43 |
44 | def delete_char(self):
45 | self.rime.process_key(65288, 0)
46 |
47 | def commit_text(self):
48 | """ Commit the text """
49 | commit_text_str = self.rime.get_commit_text_preview()
50 | self.output_text = commit_text_str
51 | self.clear_state()
52 |
53 | def commit_raw_str(self):
54 | """ Get the raw input """
55 | self.output_text = self.rime.get_input_str()
56 | self.clear_state()
57 |
58 | def clear_state(self):
59 | """ Clear the composition """
60 | self.rime.clear_composition()
61 |
62 | def get_output_text(self):
63 | return self.output_text
64 |
65 | def get_status_str(self):
66 | res = ''
67 | composition_preedit = self.rime.get_composition_preedit()
68 |
69 | if composition_preedit:
70 | res = '{} | '.format(composition_preedit)
71 | candidates = self.rime.get_candidates()
72 | for i, candidate in enumerate(candidates):
73 | text, comment = candidate
74 | res += '{}. {} '.format(i + 1 , text)
75 | return res
76 |
77 |
78 | class TmuxRimeRequestHandler(socketserver.BaseRequestHandler):
79 | def handle(self):
80 | data = self.request.recv(1024).strip().decode('utf-8')
81 | if data.startswith('key'):
82 | res = self.server.handle_key(data)
83 | if res is not None:
84 | self.request.sendall(res.encode('utf-8'))
85 | elif data.startswith('output'):
86 | logging.info('Start with output!')
87 | res = self.server.get_output_text()
88 | if res is not None:
89 | self.request.sendall(res.encode('utf-8'))
90 | elif data.startswith('raw'):
91 | res = self.server.commit_raw()
92 | if res is not None:
93 | self.request.sendall(res.encode('utf-8'))
94 | elif data.startswith('delete'):
95 | res = self.server.delete_char()
96 | if res is not None:
97 | self.request.sendall(res.encode('utf-8'))
98 | elif data.startswith('exit'):
99 | self.server.exit_session()
100 | else:
101 | command = data.split(' ')[0]
102 | logging.warning('Unknown command: {}'.format(command))
103 |
104 |
105 | class TmuxRimeServer(socketserver.UnixStreamServer):
106 | def __init__(self):
107 | # Currently not support multiple sessions
108 | # self.sessions = {}
109 | self.session = TmuxRimeSession()
110 | self.session.start()
111 | self.server_address = '/tmp/tmux-rime-socket'
112 | try:
113 | os.unlink(self.server_address)
114 | except OSError:
115 | if os.path.exists(self.server_address):
116 | raise
117 | socketserver.UnixStreamServer.__init__(self,
118 | self.server_address,
119 | TmuxRimeRequestHandler)
120 |
121 | def start_server(self):
122 | self.serve_forever()
123 |
124 | def close_server(self):
125 | self._BaseServer__shutdown_request = True
126 |
127 | def exit_session(self):
128 | logging.info('Exit session')
129 | self.session.finish()
130 | self.close_server()
131 |
132 | def handle_key(self, data):
133 | key, modifier = tuple(re.split('\s', data)[1:])
134 |
135 | logging.info('Received key: {}, modifier: {}'
136 | .format(key, modifier))
137 | self.session.handle_key(key, modifier)
138 | return self.session.get_status_str()
139 |
140 | def commit_raw(self):
141 | logging.info('Commit the raw input')
142 | self.session.commit_raw_str()
143 |
144 | def delete_char(self):
145 | logging.info('Delete a char')
146 | self.session.delete_char()
147 | return self.session.get_status_str()
148 |
149 | def get_output_text(self):
150 | text = self.session.get_output_text()
151 | logging.info('Get output text: {}'.format(text))
152 | return text
153 |
154 |
155 | if __name__ == '__main__':
156 | init_logging()
157 | with TmuxRimeServer() as server:
158 | server.start_server()
159 |
--------------------------------------------------------------------------------
/utils/debugging.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -x
4 | tmux_rime_directory="$1"
5 | tmux_rime_server_log="$tmux_rime_directory/tmux_rime/tmux_rime_server.log"
6 | tmux_rime_client_log="$tmux_rime_directory/tmux_rime/tmux_rime_client.log"
7 | tmux_session_name="tmux_rime_debug"
8 |
9 | tmux kill-session -t "$tmux_session_name"
10 | tmux new-session -d -s "$tmux_session_name" -c "$tmux_rime_directory"
11 | tmux split-window -t "$tmux_session_name" -h "tail -f $tmux_rime_server_log"
12 | tmux split-window -t "$tmux_session_name" -v "tail -f $tmux_rime_client_log"
13 | tmux attach-session -d -t tmux_rime_debug
14 |
--------------------------------------------------------------------------------