|
|
相比直接开发TSF输入法,这个方案的难度大大降低。我来为您详细讲解实现方案。
PIME + 火山窗口整合方案
方案架构概述
text
火山窗口(候选词UI)
↑
↓ (进程间通信)
火山服务进程(转换引擎)
↑
↓ (COM接口)
PIME框架
↑
↓ (TSF API)
Windows系统
第一部分:环境准备
1. 安装必要组件
batch
# 需要先安装的软件
1. Python 3.6+ (PIME基于Python)
2. Node.js (用于编译PIME)
3. Visual Studio 2017+ (C++编译环境)
4. 火山视窗开发平台
2. 获取PIME框架
batch
# 克隆PIME项目
git clone https://github.com/EasyIME/PIME.git
# 目录结构说明
PIME/
├── client/ # 输入法客户端
├── server/ # 输入法服务端
├── imes/ # 各种输入法引擎
│ ├── cangjie5/ # 仓颉输入法
│ ├── chephon/ # 注音输入法
│ └── quick/ # 简易输入法示例
└── build/ # 编译目录
第二部分:创建自定义输入法引擎
1. 在PIME中创建新的输入法目录
python
# 在 PIME/imes/ 下创建新目录
# 例如: PIME/imes/my_volcano_ime/
2. 创建输入法配置文件
json
// ime.json
{
"name": "MyVolcanoIME",
"locale": "zh-CN",
"version": "1.0.0",
"author": "YourName",
"description": "火山开发的输入法",
"icon": "icon.ico",
"service": "my_volcano_service.exe",
"module": "MyVolcanoIME.dll"
}
3. 创建Python服务端(关键部分)
python
# my_volcano_service.py
import json
import sys
import struct
import threading
import socket
from ctypes import *
from win32pipe import *
from win32file import *
# 与火山窗口通信的IPC设置
VOLCANO_HOST = '127.0.0.1'
VOLCANO_PORT = 8888
PIPE_NAME = r'\\.\pipe\VolcanoIMEPipe'
class VolcanoIMEHandler:
def __init__(self):
self.socket_client = None
self.current_composing = ""
self.candidates = []
def process_key_event(self, key_event):
"""处理键盘事件"""
key_code = key_event['keyCode']
is_shift = key_event['shiftKey']
is_ctrl = key_event['ctrlKey']
# 字母键处理
if 65 <= key_code <= 90: # A-Z
char = chr(key_code)
if not is_shift:
char = char.lower()
self.current_composing += char
# 调用火山转换引擎
self.get_candidates_from_volcano()
return {
'success': True,
'showCandidates': True,
'composingText': self.current_composing,
'cursorIndex': len(self.current_composing)
}
# 空格键提交
elif key_code == 32: # Space
if self.candidates:
return self.commit_candidate(0)
# 数字键选择候选词
elif 49 <= key_code <= 57: # 1-9
index = key_code - 49
if index < len(self.candidates):
return self.commit_candidate(index)
# 退格键
elif key_code == 8: # Backspace
if self.current_composing:
self.current_composing = self.current_composing[:-1]
if self.current_composing:
self.get_candidates_from_volcano()
else:
self.candidates = []
return {'success': True, 'showCandidates': False}
def get_candidates_from_volcano(self):
"""通过IPC从火山进程获取候选词"""
try:
# 方式1:使用命名管道
pipe = CreateFile(
PIPE_NAME,
GENERIC_READ | GENERIC_WRITE,
0, None,
OPEN_EXISTING,
0, None
)
# 发送拼音字符串
request = json.dumps({
'action': 'getCandidates',
'pinyin': self.current_composing
})
WriteFile(pipe, request.encode('utf-8'))
# 读取响应
response = b''
while True:
chunk = ReadFile(pipe, 4096)[1]
if not chunk:
break
response += chunk
CloseHandle(pipe)
data = json.loads(response.decode('utf-8'))
self.candidates = data.get('candidates', [])
except Exception as e:
print(f"与火山通信失败: {e}")
# 回退到本地简单转换
self.local_pinyin_conversion()
def local_pinyin_conversion(self):
"""本地简单的拼音转换(备选)"""
# 这里可以放一个简单的拼音字典
pinyin_dict = {
'wo': ['我', '握', '窝'],
'ai': ['爱', '矮', '哎'],
'ni': ['你', '尼', '泥']
}
self.candidates = pinyin_dict.get(self.current_composing, [])
def commit_candidate(self, index):
"""提交候选词"""
if index < len(self.candidates):
text = self.candidates[index]
self.current_composing = ""
self.candidates = []
return {
'success': True,
'commitText': text,
'showCandidates': False
}
return {'success': False}
# PIME标准服务入口
def main():
handler = VolcanoIMEHandler()
# 标准输入输出用于与PIME框架通信
while True:
try:
# 读取消息长度(4字节)
length_bytes = sys.stdin.buffer.read(4)
if not length_bytes:
break
msg_length = struct.unpack('I', length_bytes)[0]
# 读取消息内容
msg_bytes = sys.stdin.buffer.read(msg_length)
message = json.loads(msg_bytes.decode('utf-8'))
# 处理消息
if message.get('type') == 'keyEvent':
result = handler.process_key_event(message['event'])
# 发送响应
response = json.dumps(result).encode('utf-8')
sys.stdout.buffer.write(struct.pack('I', len(response)))
sys.stdout.buffer.write(response)
sys.stdout.buffer.flush()
except Exception as e:
print(f"错误: {e}", file=sys.stderr)
if __name__ == '__main__':
main()
第三部分:火山服务进程
1. 创建火山Windows控制台程序
火山
// VolcanoIMEServer.v - 火山服务端
类 输入法服务端
{
变量 _服务器套接字 : 网络服务器套接字
变量 _客户端列表 : 网络客户端套接字[]
变量 _拼音转换器 : 拼音引擎
// 启动服务
方法 启动服务()
{
// 1. 启动命名管道服务器
启动命名管道服务()
// 2. 启动TCP服务器(备用)
_服务器套接字 = 创建 网络服务器套接字()
_服务器套接字.绑定("127.0.0.1", 8888)
_服务器套接字.开始监听(5)
// 异步接受连接
启动线程(&接受客户端连接)
输出日志("火山输入法服务已启动")
}
// 命名管道服务器
方法 启动命名管道服务()
{
启动线程(&命名管道服务线程)
}
方法 命名管道服务线程()
{
变量 管道句柄 : 变整数
变量 缓冲区 : 字节集 = 创建字节集(4096)
变量 读取字节数 : 变整数
变量 结果 : 逻辑型
// 创建命名管道
管道句柄 = CreateNamedPipe(
"\\.\pipe\VolcanoIMEPipe",
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
4096, 4096, 0, 空指针)
如果 (管道句柄 == INVALID_HANDLE_VALUE)
{
输出日志("创建命名管道失败")
返回
}
循环 (真)
{
// 等待客户端连接
ConnectNamedPipe(管道句柄, 空指针)
// 读取客户端请求
结果 = ReadFile(管道句柄, 缓冲区, 取字节集长度(缓冲区), 读取字节数, 空指针)
如果 (结果)
{
// 处理请求
变量 请求文本 : 文本 = 到文本(取字节集左边(缓冲区, 读取字节数))
变量 响应文本 : 文本 = 处理请求(请求文本)
变量 响应字节 : 字节集 = 到字节集(响应文本)
// 发送响应
WriteFile(管道句柄, 响应字节, 取字节集长度(响应字节), 空指针, 空指针)
}
DisconnectNamedPipe(管道句柄)
}
}
方法 处理请求(请求JSON:文本) : 文本
{
变量 json : Json对象 = 创建 Json对象()
json.解析(请求JSON)
变量 动作 : 文本 = json.取文本值("action")
如果 (动作 == "getCandidates")
{
变量 拼音 : 文本 = json.取文本值("pinyin")
变量 候选词 : 文本[] = _拼音转换器.转换(拼音)
变量 响应 : Json对象 = 创建 Json对象()
响应.置文本值("action", "candidatesResponse")
响应.置文本数组("candidates", 候选词)
返回 响应.到文本()
}
返回 "{}"
}
}
// 拼音转换引擎
类 拼音引擎
{
变量 _拼音字典 : 哈希表<文本, 文本[]> = 创建 哈希表<文本, 文本[]>()
方法 初始化()
{
// 加载拼音字典
// 可以从文件加载
_拼音字典["wo"] = ["我", "握", "窝", "沃", "卧"]
_拼音字典["ai"] = ["爱", "矮", "哎", "唉", "癌"]
_拼音字典["ni"] = ["你", "尼", "泥", "拟", "逆"]
_拼音字典["hao"] = ["好", "号", "豪", "耗", "浩"]
// ... 更多拼音
}
方法 转换(拼音:文本) : 文本[]
{
变量 结果 : 文本[] = []
// 简单直接匹配
如果 (_拼音字典.是否存在键(拼音))
{
结果 = _拼音字典[拼音]
}
否则
{
// 可以添加模糊匹配逻辑
结果 = ["未找到匹配"]
}
返回 结果
}
}
2. 火山候选词窗口程序
火山
// CandidateWindow.v - 候选词窗口
类 候选窗口 : 窗口
{
变量 _列表控件 : 列表框
变量 _当前拼音 : 文本 = ""
变量 _候选词 : 文本[] = []
变量 _服务连接 : 网络客户端套接字
// 窗口初始化
方法 初始化()
{
// 设置窗口样式
本对象.标题 = "火山输入法"
本对象.风格 = 窗口风格.无边框 | 窗口风格.置顶 | 窗口风格.分层窗口
本对象.宽度 = 300
本对象.高度 = 200
本对象.透明度 = 230 // 半透明
// 连接服务
连接服务()
// 创建控件
创建界面()
// 窗口位置跟随光标
启动线程(&窗口跟随线程)
}
方法 连接服务()
{
_服务连接 = 创建 网络客户端套接字()
变量 连接成功 : 逻辑型 = _服务连接.连接("127.0.0.1", 8888)
如果 (连接成功)
{
输出日志("已连接到输入法服务")
启动线程(&接收消息线程)
}
否则
{
输出日志("连接服务失败")
}
}
方法 创建界面()
{
// 拼音显示标签
变量 拼音标签 : 标签 = 创建 标签()
拼音标签.标题 = "拼音:"
拼音标签.左边 = 10
拼音标签.顶边 = 10
添加组件(拼音标签)
// 拼音输入框
变量 拼音框 : 编辑框 = 创建 编辑框()
拼音框.左边 = 60
拼音框.顶边 = 10
拼音框.宽度 = 200
拼音框.可视 = 假 // 隐藏,仅调试用
添加组件(拼音框)
// 候选词列表框
_列表控件 = 创建 列表框()
_列表控件.左边 = 10
_列表控件.顶边 = 40
_列表控件.宽度 = 280
_列表控件.高度 = 150
_列表控件.字体大小 = 14
_列表控件.绑定事件(&_列表控件_被双击, 事件类型.被双击)
添加组件(_列表控件)
// 样式美化
本对象.背景颜色 = 颜色.白色
_列表控件.背景颜色 = 颜色.浅青柠色
}
方法 更新候选词(拼音:文本, 候选列表:文本[])
{
_当前拼音 = 拼音
_候选词 = 候选列表
// 更新界面(必须在UI线程执行)
投递任务到界面(&实际更新界面)
}
方法 实际更新界面()
{
_列表控件.清空()
循环 (变量 i = 0; i < 取数组成员数(_候选词); i++)
{
_列表控件.加入项目(到文本(i+1) + ". " + _候选词)
}
// 调整窗口大小
本对象.高度 = 50 + 取数组成员数(_候选词) * 25
本对象.可视 = 真
本对象.置顶显示()
}
事件 _列表控件_被双击(行索引:整数)
{
如果 (行索引 >= 0 且 行索引 < 取数组成员数(_候选词))
{
提交文本(_候选词[行索引])
}
}
方法 提交文本(文本:文本)
{
// 发送提交消息给PIME服务
变量 消息 : Json对象 = 创建 Json对象()
消息.置文本值("action", "commitText")
消息.置文本值("text", 文本)
_服务连接.发送文本(消息.到文本())
// 隐藏窗口
本对象.可视 = 假
}
方法 窗口跟随线程()
{
循环 (真)
{
// 获取光标位置
变量 光标位置 : POINT
GetCursorPos(光标位置)
// 设置窗口位置
本对象.左边 = 光标位置.x + 10
本对象.顶边 = 光标位置.y + 20
延时(100)
}
}
}
第四部分:编译和部署
1. 编译PIME输入法
batch
# 进入PIME目录
cd PIME
# 安装依赖
npm install
# 编译
npm run build
# 将您的输入法添加到编译列表
# 修改 build\config.json,添加您的输入法
2. 创建火山项目文件结构
text
VolcanoIME/
├── VolcanoIMEServer/ # 火山服务端程序
│ ├── Main.v # 主文件
│ ├── PinyinEngine.v # 拼音引擎
│ └── CandidateWindow.v # 候选窗口
├── PIME_Integration/ # PIME集成文件
│ ├── ime.json # 输入法配置
│ ├── my_volcano_service.py # Python服务
│ └── icon.ico # 图标
└── Build/ # 编译输出
├── VolcanoIMEServer.exe
├── MyVolcanoIME.dll
└── install.bat
3. 安装脚本
batch
:: install.bat
@echo off
echo 正在安装火山输入法...
:: 1. 复制文件到PIME目录
xcopy /Y "%~dp0*.*" "C:\Program Files\PIME\imes\my_volcano_ime\"
:: 2. 注册输入法
cd "C:\Program Files\PIME"
python register.py my_volcano_ime
:: 3. 启动火山服务
start "Volcano IME Server" "%~dp0VolcanoIMEServer.exe"
echo 安装完成!
echo 请在语言栏中添加"火山输入法"
pause
第五部分:调试技巧
1. 调试PIME服务
python
# debug_server.py - 调试脚本
import subprocess
import sys
# 直接运行Python服务进行测试
proc = subprocess.Popen(
[sys.executable, "my_volcano_service.py"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# 模拟按键事件
test_event = {
"type": "keyEvent",
"event": {
"keyCode": 65, # 'A'
"shiftKey": False,
"ctrlKey": False
}
}
# 发送测试数据
import json
import struct
msg = json.dumps(test_event).encode('utf-8')
proc.stdin.write(struct.pack('I', len(msg)))
proc.stdin.write(msg)
proc.stdin.flush()
# 读取响应
response_length = struct.unpack('I', proc.stdout.read(4))[0]
response = json.loads(proc.stdout.read(response_length).decode('utf-8'))
print("响应:", response)
2. 火山端调试输出
火山
// 调试工具类
类 调试工具
{
方法 输出日志(信息:文本)
{
变量 时间 : 文本 = 取格式时间("hh:mm:ss", 取现行时间())
变量 完整信息 : 文本 = 时间 + " [火山IME] " + 信息
// 输出到调试器
OutputDebugStringA(完整信息)
// 同时输出到文件
变量 文件 : 文本文件 = 创建 文本文件()
文件.打开("volcano_ime.log", 打开方式.追加创建, 编码格式.UTF8)
文件.写文本行(完整信息)
文件.关闭()
}
}
第六部分:优化建议
1. 性能优化
火山
// 使用缓存提高响应速度
类 拼音缓存
{
变量 _缓存 : 哈希表<文本, 文本[]> = 创建 哈希表<文本, 文本[]>()
变量 _最大缓存数 : 整数 = 1000
方法 获取候选词(拼音:文本) : 文本[]
{
如果 (_缓存.是否存在键(拼音))
{
返回 _缓存[拼音]
}
// 计算并缓存
变量 结果 : 文本[] = 计算候选词(拼音)
如果 (_缓存.取数量() >= _最大缓存数)
{
// 简单LRU策略
_缓存.清空()
}
_缓存[拼音] = 结果
返回 结果
}
}
2. 添加云输入支持
火山
// 云输入模块
类 云输入服务
{
方法 获取云候选词(拼音:文本) : 文本[]
{
变量 http : HTTP客户端 = 创建 HTTP客户端()
变量 响应 : HTTP响应
尝试
{
// 调用云输入API(示例)
响应 = http.发送请求(
"POST",
"https://api.pinyin.com/search",
"{\"pinyin\":\"" + 拼音 + "\"}",
{"Content-Type": "application/json"}
)
如果 (响应.状态码 == 200)
{
变量 json : Json对象 = 创建 Json对象()
json.解析(响应.主体)
返回 json.取文本数组("candidates")
}
}
捕获 (异常)
{
// 降级到本地引擎
}
返回 []
}
}
开始步骤建议
第一步:先让PIME框架正常运行(测试自带输入法)
第二步:创建最简单的火山IPC服务(能收发消息即可)
第三步:实现拼音->汉字的本地转换
第四步:整合PIME和火山窗口
第五步:添加高级功能(云输入、皮肤等)
遇到的常见问题解决
PIME服务无法启动:检查Python路径和依赖包
火山窗口不显示:检查窗口样式和置顶属性
IPC通信失败:检查端口是否被占用
输入法不生效:以管理员身份运行注册脚本
这个方案的优势是分工明确:
PIME处理复杂的TSF系统集成
火山负责友好的用户界面和业务逻辑
Python作为粘合剂,灵活处理通信
|
|