mirror of
https://github.com/mii443/vrclipboard-ime-gui.git
synced 2025-08-22 16:15:32 +00:00
add tracing
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "vrclipboard-ime-gui",
|
||||
"private": true,
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
10
release.json
10
release.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"url": "https://r2-vrime.mii.dev/releases/vrclipboard-ime-gui_1.6.0_x64_ja-JP.msi.zip",
|
||||
"version": "1.6.0",
|
||||
"notes": "ベータ機能 Text Services Framework を使用した再変換を追加",
|
||||
"pub_date": "2024-09-23T07:59:37+00:00",
|
||||
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVTTStkVlpUR0NpcVdnb0phVVBYd2dYWjlHbXdCRGloZzJaZ21aejR2UjgyYXlkWnpvTUI1aERjellPc2lYVHhqaWUrWmdQUXZTMkR4MkVPMWdROHM3RlN2cUFDV3ZBNFE0PQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzI3MDc4MzExCWZpbGU6dnJjbGlwYm9hcmQtaW1lLWd1aV8xLjYuMF94NjRfamEtSlAubXNpLnppcAo2WXhDUWlUNkNtamJmdkloaXRBUEEzdmhvTnJubDQ3Q3dNaXk0OGZISnU4WmM0eVp1NG9KMS9RUUgyY25pd01uWGlCVnpYUDlVc0tIYVlVRU10NzhBdz09Cg=="
|
||||
"url": "https://r2-vrime.mii.dev/releases/vrclipboard-ime-gui_1.7.0_x64_ja-JP.msi.zip",
|
||||
"version": "1.7.0",
|
||||
"notes": "Text Services Framework 再変換のバグ修正および仕様変更",
|
||||
"pub_date": "2024-09-25T12:10:54+00:00",
|
||||
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVTTStkVlpUR0NpcVlQZFdBNEJzYk1QbVl0WUx3YU03SG1KTlJHT05jbVJwSFJ5UFJOaWNxRUd2bnlTOG1uYlo4VGhPc0xieXJGcUk2cDNDdzU4dEVZeVZhRU5QL2tKS3dNPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzI3MjY2NDI3CWZpbGU6dnJjbGlwYm9hcmQtaW1lLWd1aV8xLjcuMF94NjRfamEtSlAubXNpLnppcApVSyt0VDVyRWl4K1IySTlmNEZWRDEvMVVpRzc2TEdhdyt0WmpvN3FCQm1wN1hpeXZDaUo5WmltM1dsV25raHRkRXNmbW9xazdRb25na29sL1d6MVhCdz09Cg=="
|
||||
}
|
||||
|
42
src-tauri/Cargo.lock
generated
42
src-tauri/Cargo.lock
generated
@ -1987,6 +1987,15 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc"
|
||||
version = "0.2.7"
|
||||
@ -3535,7 +3544,9 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa 1.0.11",
|
||||
"libc",
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
@ -3706,6 +3717,18 @@ dependencies = [
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-appender"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.27"
|
||||
@ -3738,6 +3761,16 @@ dependencies = [
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-serde"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.18"
|
||||
@ -3748,12 +3781,16 @@ dependencies = [
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"time",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3866,7 +3903,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "vrclipboard-ime-gui"
|
||||
version = "1.6.0"
|
||||
version = "1.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"calc",
|
||||
@ -3884,6 +3921,9 @@ dependencies = [
|
||||
"serde_yaml",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
"windows 0.58.0",
|
||||
"windows-core 0.58.0",
|
||||
]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "vrclipboard-ime-gui"
|
||||
version = "1.6.0"
|
||||
version = "1.8.0"
|
||||
description = "VRClipboard IME"
|
||||
authors = ["mii"]
|
||||
edition = "2021"
|
||||
@ -27,6 +27,12 @@ once_cell = "1.19.0"
|
||||
rosc = "~0.10"
|
||||
regex = "1"
|
||||
windows-core = "0.58.0"
|
||||
tracing = "0.1"
|
||||
tracing-appender = "0.2"
|
||||
|
||||
[dependencies.tracing-subscriber]
|
||||
version = "0.3.16"
|
||||
features = ["env-filter", "fmt", "json", "local-time", "time"]
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.58.0"
|
||||
|
@ -1,11 +1,16 @@
|
||||
use anyhow::Result;
|
||||
use tracing::debug;
|
||||
use windows::Win32::System::Com::{CoInitialize, CoUninitialize};
|
||||
|
||||
pub struct Com;
|
||||
|
||||
impl Drop for Com {
|
||||
fn drop(&mut self) {
|
||||
unsafe { CoUninitialize() };
|
||||
debug!("Dropping Com instance");
|
||||
unsafe {
|
||||
CoUninitialize();
|
||||
debug!("CoUninitialize called");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ use serde::Serialize;
|
||||
use serde_derive::Deserialize;
|
||||
use anyhow::Result;
|
||||
use tauri::State;
|
||||
use tracing::{info, error, debug, trace};
|
||||
|
||||
use crate::AppState;
|
||||
|
||||
@ -64,56 +65,71 @@ impl Default for OnCopyMode {
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Result<Config> {
|
||||
debug!("Loading config");
|
||||
std::fs::create_dir_all(Self::get_path()).unwrap();
|
||||
|
||||
if !Path::new(&Self::get_path().join("config.yaml")).exists() {
|
||||
let config_path = Self::get_path().join("config.yaml");
|
||||
if !Path::new(&config_path).exists() {
|
||||
info!("Config file not found, generating default");
|
||||
Self::generate_default_config()?;
|
||||
}
|
||||
let mut file = File::open(&Self::get_path().join("config.yaml"))?;
|
||||
let mut file = File::open(&config_path)?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
trace!("Raw config contents: {}", contents);
|
||||
let config: Config = serde_yaml::from_str(&contents)?;
|
||||
debug!("Config loaded successfully");
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn save(&self, state: State<AppState>) -> Result<(), String> {
|
||||
debug!("Saving config");
|
||||
std::fs::create_dir_all(Self::get_path()).unwrap();
|
||||
|
||||
let mut file = match File::create(&Self::get_path().join("config.yaml")) {
|
||||
let config_path = Self::get_path().join("config.yaml");
|
||||
let mut file = match File::create(&config_path) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
println!("Err: {:?}", e);
|
||||
error!("Failed to create config file: {}", e);
|
||||
return Err(format!("Failed to create config file: {}", e))
|
||||
},
|
||||
};
|
||||
|
||||
match serde_yaml::to_string(&self) {
|
||||
Ok(yaml) => {
|
||||
trace!("Config to be saved: {}", yaml);
|
||||
if let Err(e) = file.write_all(yaml.as_bytes()) {
|
||||
println!("Err: {:?}", e);
|
||||
error!("Failed to write config: {}", e);
|
||||
return Err(format!("Failed to write config: {}", e));
|
||||
}
|
||||
let mut app_config = state.config.lock().unwrap();
|
||||
*app_config = self.clone();
|
||||
info!("Config saved successfully");
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
println!("Err: {:?}", e);
|
||||
error!("Failed to serialize config: {}", e);
|
||||
Err(format!("Failed to serialize config: {}", e))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_default_config() -> Result<()> {
|
||||
let mut file = File::create(&Self::get_path().join("config.yaml"))?;
|
||||
file.write_all(serde_yaml::to_string(&Config::default()).unwrap().as_bytes())?;
|
||||
debug!("Generating default config");
|
||||
let config_path = Self::get_path().join("config.yaml");
|
||||
let mut file = File::create(&config_path)?;
|
||||
let default_config = Config::default();
|
||||
let yaml = serde_yaml::to_string(&default_config).unwrap();
|
||||
file.write_all(yaml.as_bytes())?;
|
||||
file.flush()?;
|
||||
info!("Default config generated successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_path() -> PathBuf {
|
||||
let app_dirs = AppDirs::new(Some("vrclipboard-ime"), false).unwrap();
|
||||
let app_data = app_dirs.config_dir;
|
||||
trace!("Config path: {:?}", app_data);
|
||||
app_data
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{config::Config, converter::converter::{get_custom_converter, Converter}, STATE};
|
||||
use anyhow::Result;
|
||||
use tracing::{info, debug, trace, warn};
|
||||
|
||||
pub struct ConversionBlock {
|
||||
pub text: String,
|
||||
@ -10,69 +11,96 @@ pub struct Conversion;
|
||||
|
||||
impl Conversion {
|
||||
pub fn new() -> Self {
|
||||
info!("Creating new Conversion instance");
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub fn convert_text(&self, text: &str) -> Result<String> {
|
||||
println!("Processing text: {}", text);
|
||||
info!("Converting text: {}", text);
|
||||
trace!("Text length: {}", text.len());
|
||||
let blocks = self.split_text(text)?;
|
||||
trace!("Number of blocks after splitting: {}", blocks.len());
|
||||
self.convert_blocks(blocks)
|
||||
}
|
||||
|
||||
pub fn convert_blocks(&self, blocks: Vec<ConversionBlock>) -> Result<String> {
|
||||
debug!("Converting blocks");
|
||||
let mut result = String::new();
|
||||
for block in blocks {
|
||||
for (index, block) in blocks.iter().enumerate() {
|
||||
trace!("Processing block {}/{}", index + 1, blocks.len());
|
||||
let converted = self.convert_block(&block)?;
|
||||
println!(" {}: {} -> {}", block.converter.name(), block.text, converted);
|
||||
debug!("Converted block - {}: {} -> {}", block.converter.name(), block.text, converted);
|
||||
result.push_str(&converted);
|
||||
trace!("Current result length: {}", result.len());
|
||||
}
|
||||
println!(" {}", result);
|
||||
trace!("Final conversion result: {}", result);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn convert_block(&self, block: &ConversionBlock) -> Result<String> {
|
||||
if block.text == "" {
|
||||
trace!("Converting block: {}", block.text);
|
||||
trace!("Using converter: {}", block.converter.name());
|
||||
if block.text.is_empty() {
|
||||
trace!("Empty block, returning default string");
|
||||
return Ok(String::default());
|
||||
}
|
||||
block.converter.convert(&block.text)
|
||||
let result = block.converter.convert(&block.text);
|
||||
trace!("Conversion result: {:?}", result);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn split_text(&self, text: &str) -> Result<Vec<ConversionBlock>> {
|
||||
debug!("Splitting text: {}", text);
|
||||
let mut text = text.to_string();
|
||||
let mut blocks = Vec::new();
|
||||
let mut current_converter = 'r';
|
||||
|
||||
let config = self.get_config();
|
||||
trace!("Config command: {}, split: {}", config.command, config.split);
|
||||
|
||||
if text.starts_with(&config.command) {
|
||||
trace!("Text starts with command");
|
||||
text = text.split_off(1);
|
||||
if text.len() != 0 {
|
||||
if !text.is_empty() {
|
||||
current_converter = text.chars().next().unwrap_or('n');
|
||||
text = text.split_off(1);
|
||||
trace!("Initial converter set to: {}", current_converter);
|
||||
}
|
||||
}
|
||||
|
||||
for (i, command_splitted) in text.split(&config.command).enumerate() {
|
||||
trace!("Processing split {}", i);
|
||||
let mut command_splitted = command_splitted.to_string();
|
||||
if i != 0 {
|
||||
if command_splitted.len() != 0 {
|
||||
if !command_splitted.is_empty() {
|
||||
current_converter = command_splitted.chars().next().unwrap_or('n');
|
||||
command_splitted = command_splitted.split_off(1);
|
||||
trace!("Converter changed to: {}", current_converter);
|
||||
}
|
||||
}
|
||||
|
||||
for splitted in command_splitted.split(&config.split) {
|
||||
trace!("Creating ConversionBlock - text: {}, converter: {}", splitted, current_converter);
|
||||
let converter = get_custom_converter(current_converter).unwrap_or_else(|| {
|
||||
warn!("Failed to get custom converter for '{}', using default", current_converter);
|
||||
get_custom_converter('n').unwrap()
|
||||
});
|
||||
blocks.push(ConversionBlock {
|
||||
text: splitted.to_string(),
|
||||
converter: get_custom_converter(current_converter).unwrap_or(get_custom_converter('n').unwrap())
|
||||
converter
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Split text into {} blocks", blocks.len());
|
||||
trace!("Blocks: {:?}", blocks.iter().map(|b| &b.text).collect::<Vec<_>>());
|
||||
Ok(blocks)
|
||||
}
|
||||
|
||||
pub fn get_config(&self) -> Config {
|
||||
STATE.lock().unwrap().clone()
|
||||
trace!("Getting config");
|
||||
let config = STATE.lock().unwrap().clone();
|
||||
trace!("Config retrieved: {:?}", config);
|
||||
config
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use calc::Context;
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
use super::converter::Converter;
|
||||
|
||||
@ -6,16 +7,25 @@ pub struct CalculatorConverter;
|
||||
|
||||
impl Converter for CalculatorConverter {
|
||||
fn convert(&self, text: &str) -> anyhow::Result<String> {
|
||||
debug!("Evaluating expression: {}", text);
|
||||
let mut ctx = Context::<f64>::default();
|
||||
let result = match ctx.evaluate(text) {
|
||||
Ok(result) => format!("{} = {}", text, result.to_string()),
|
||||
Err(e) => e.to_string(),
|
||||
Ok(result) => {
|
||||
let formatted = format!("{} = {}", text, result.to_string());
|
||||
info!("Evaluation successful: {}", formatted);
|
||||
formatted
|
||||
},
|
||||
Err(e) => {
|
||||
debug!("Evaluation failed: {}", e);
|
||||
e.to_string()
|
||||
},
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
trace!("Getting converter name");
|
||||
"calculator".to_string()
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
use super::{calculator::CalculatorConverter, hiragana::HiraganaConverter, katakana::KatakanaConverter, none_converter::NoneConverter, roman_to_kanji::RomanToKanjiConverter};
|
||||
|
||||
@ -8,12 +9,18 @@ pub trait Converter {
|
||||
}
|
||||
|
||||
pub fn get_custom_converter(prefix: char) -> Option<Box<dyn Converter>> {
|
||||
match prefix {
|
||||
debug!("Getting custom converter for prefix: {}", prefix);
|
||||
let converter = match prefix {
|
||||
'r' => Some(Box::new(RomanToKanjiConverter) as Box<dyn Converter>),
|
||||
'h' => Some(Box::new(HiraganaConverter) as Box<dyn Converter>),
|
||||
'c' => Some(Box::new(CalculatorConverter) as Box<dyn Converter>),
|
||||
'n' => Some(Box::new(NoneConverter) as Box<dyn Converter>),
|
||||
'k' => Some(Box::new(KatakanaConverter) as Box<dyn Converter>),
|
||||
_ => None,
|
||||
};
|
||||
match &converter {
|
||||
Some(c) => debug!("Custom converter found: {}", c.name()),
|
||||
None => trace!("No custom converter found for prefix: {}", prefix),
|
||||
}
|
||||
converter
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use windows::Win32::UI::Input::Ime::{FELANG_CMODE_HIRAGANAOUT, FELANG_CMODE_NOINVISIBLECHAR, FELANG_CMODE_PRECONV, FELANG_REQ_REV};
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
use crate::felanguage::FElanguage;
|
||||
|
||||
@ -9,11 +10,22 @@ pub struct HiraganaConverter;
|
||||
|
||||
impl Converter for HiraganaConverter {
|
||||
fn convert(&self, text: &str) -> anyhow::Result<String> {
|
||||
debug!("Converting to hiragana: {}", text);
|
||||
let felanguage = FElanguage::new()?;
|
||||
felanguage.j_morph_result(text, FELANG_REQ_REV, FELANG_CMODE_HIRAGANAOUT | FELANG_CMODE_PRECONV | FELANG_CMODE_NOINVISIBLECHAR)
|
||||
trace!("FElanguage instance created");
|
||||
|
||||
let result = felanguage.j_morph_result(text, FELANG_REQ_REV, FELANG_CMODE_HIRAGANAOUT | FELANG_CMODE_PRECONV | FELANG_CMODE_NOINVISIBLECHAR);
|
||||
|
||||
match &result {
|
||||
Ok(converted) => info!("Conversion successful: {} -> {}", text, converted),
|
||||
Err(e) => debug!("Conversion failed: {}", e),
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
trace!("Getting converter name");
|
||||
"hiragana".to_string()
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use windows::Win32::UI::Input::Ime::{FELANG_CMODE_KATAKANAOUT, FELANG_CMODE_NOINVISIBLECHAR, FELANG_CMODE_PRECONV, FELANG_REQ_REV};
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
use crate::felanguage::FElanguage;
|
||||
|
||||
@ -9,11 +10,22 @@ pub struct KatakanaConverter;
|
||||
|
||||
impl Converter for KatakanaConverter {
|
||||
fn convert(&self, text: &str) -> anyhow::Result<String> {
|
||||
debug!("Converting to katakana: {}", text);
|
||||
let felanguage = FElanguage::new()?;
|
||||
felanguage.j_morph_result(text, FELANG_REQ_REV, FELANG_CMODE_KATAKANAOUT | FELANG_CMODE_PRECONV | FELANG_CMODE_NOINVISIBLECHAR)
|
||||
trace!("FElanguage instance created");
|
||||
|
||||
let result = felanguage.j_morph_result(text, FELANG_REQ_REV, FELANG_CMODE_KATAKANAOUT | FELANG_CMODE_PRECONV | FELANG_CMODE_NOINVISIBLECHAR);
|
||||
|
||||
match &result {
|
||||
Ok(converted) => info!("Conversion successful: {} -> {}", text, converted),
|
||||
Err(e) => debug!("Conversion failed: {}", e),
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
trace!("Getting converter name");
|
||||
"katakana".to_string()
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
use tracing::{debug, trace};
|
||||
use super::converter::Converter;
|
||||
|
||||
pub struct NoneConverter;
|
||||
|
||||
impl Converter for NoneConverter {
|
||||
fn convert(&self, text: &str) -> anyhow::Result<String> {
|
||||
debug!("Converting with NoneConverter: {}", text);
|
||||
Ok(text.to_string())
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
trace!("Getting converter name");
|
||||
"none".to_string()
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use windows::Win32::UI::Input::Ime::{FELANG_CMODE_HIRAGANAOUT, FELANG_CMODE_NOINVISIBLECHAR, FELANG_CMODE_PRECONV, FELANG_CMODE_ROMAN, FELANG_REQ_CONV};
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
use crate::felanguage::FElanguage;
|
||||
|
||||
@ -8,14 +9,25 @@ pub struct RomanToKanjiConverter;
|
||||
|
||||
impl Converter for RomanToKanjiConverter {
|
||||
fn convert(&self, text: &str) -> anyhow::Result<String> {
|
||||
debug!("Converting roman to kanji: {}", text);
|
||||
let felanguage = FElanguage::new()?;
|
||||
felanguage.j_morph_result(text, FELANG_REQ_CONV, FELANG_CMODE_HIRAGANAOUT
|
||||
trace!("FElanguage instance created");
|
||||
|
||||
let result = felanguage.j_morph_result(text, FELANG_REQ_CONV, FELANG_CMODE_HIRAGANAOUT
|
||||
| FELANG_CMODE_ROMAN
|
||||
| FELANG_CMODE_NOINVISIBLECHAR
|
||||
| FELANG_CMODE_PRECONV)
|
||||
| FELANG_CMODE_PRECONV);
|
||||
|
||||
match &result {
|
||||
Ok(converted) => info!("Conversion successful: {} -> {}", text, converted),
|
||||
Err(e) => debug!("Conversion failed: {}", e),
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
trace!("Getting converter name");
|
||||
"roman_to_kanji".to_string()
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::ptr;
|
||||
|
||||
use anyhow::Result;
|
||||
use tracing::{debug, error, info, trace};
|
||||
use windows::{
|
||||
core::{w, PCWSTR},
|
||||
Win32::{
|
||||
@ -15,25 +16,41 @@ pub struct FElanguage {
|
||||
|
||||
impl Drop for FElanguage {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.ife.Close().ok() };
|
||||
debug!("Dropping FElanguage instance");
|
||||
if let Err(e) = unsafe { self.ife.Close() } {
|
||||
error!("Error closing IFELanguage: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FElanguage {
|
||||
pub fn new() -> Result<Self> {
|
||||
let clsid = unsafe { CLSIDFromProgID(w!("MSIME.Japan"))? };
|
||||
let ife: IFELanguage = unsafe { CoCreateInstance(&clsid, None, CLSCTX_ALL)? };
|
||||
unsafe { ife.Open()? };
|
||||
info!("Creating new FElanguage instance");
|
||||
let clsid = unsafe {
|
||||
trace!("Getting CLSID for MSIME.Japan");
|
||||
CLSIDFromProgID(w!("MSIME.Japan"))?
|
||||
};
|
||||
let ife: IFELanguage = unsafe {
|
||||
trace!("Creating IFELanguage instance");
|
||||
CoCreateInstance(&clsid, None, CLSCTX_ALL)?
|
||||
};
|
||||
unsafe {
|
||||
trace!("Opening IFELanguage");
|
||||
ife.Open()?
|
||||
};
|
||||
debug!("FElanguage instance created successfully");
|
||||
Ok(FElanguage { ife })
|
||||
}
|
||||
|
||||
pub fn j_morph_result(&self, input: &str, request: u32, mode: u32) -> Result<String> {
|
||||
debug!("Calling j_morph_result with input: {}, request: {}, mode: {}", input, request, mode);
|
||||
let input_utf16: Vec<u16> = input.encode_utf16().chain(Some(0)).collect();
|
||||
let input_len = input_utf16.len();
|
||||
let input_pcwstr = PCWSTR::from_raw(input_utf16.as_ptr());
|
||||
|
||||
let mut result_ptr = ptr::null_mut();
|
||||
unsafe {
|
||||
trace!("Calling GetJMorphResult");
|
||||
self.ife.GetJMorphResult(
|
||||
request,
|
||||
mode,
|
||||
@ -45,6 +62,7 @@ impl FElanguage {
|
||||
}
|
||||
|
||||
if result_ptr.is_null() {
|
||||
error!("GetJMorphResult returned null pointer");
|
||||
return Err(anyhow::anyhow!("GetJMorphResult returned null pointer"));
|
||||
}
|
||||
|
||||
@ -53,12 +71,14 @@ impl FElanguage {
|
||||
let output_len = result_struct.cchOutput as usize;
|
||||
|
||||
if output_bstr_ptr.is_null() {
|
||||
error!("Output BSTR pointer is null");
|
||||
return Err(anyhow::anyhow!("Output BSTR pointer is null"));
|
||||
}
|
||||
|
||||
let output_slice = unsafe { std::slice::from_raw_parts(output_bstr_ptr.as_ptr(), output_len) };
|
||||
let output_string = String::from_utf16_lossy(output_slice);
|
||||
|
||||
trace!("j_morph_result output: {}", output_string);
|
||||
Ok(output_string)
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use rosc::{encoder, OscMessage, OscPacket, OscType};
|
||||
use tauri::{AppHandle, Manager};
|
||||
use crate::{config::{Config, OnCopyMode}, conversion::Conversion, tsf_conversion::TsfConversion, Log, STATE};
|
||||
use anyhow::Result;
|
||||
use tracing::{info, warn, error};
|
||||
|
||||
pub struct ConversionHandler {
|
||||
app_handle: AppHandle,
|
||||
@ -23,6 +24,7 @@ impl ConversionHandler {
|
||||
let tsf_conversion = None;
|
||||
let clipboard_ctx = ClipboardProvider::new().unwrap();
|
||||
|
||||
info!("ConversionHandler created");
|
||||
Ok(Self { app_handle, conversion, tsf_conversion, clipboard_ctx, last_text: String::new() })
|
||||
}
|
||||
|
||||
@ -34,23 +36,24 @@ impl ConversionHandler {
|
||||
impl ConversionHandler {
|
||||
fn tsf_conversion(&mut self, contents: &str, config: &Config) -> Result<()> {
|
||||
if contents.chars().count() > 140 {
|
||||
info!("Content exceeds 140 characters, skipping TSF conversion");
|
||||
return Ok(());
|
||||
}
|
||||
if config.skip_url && Regex::new(r"(http://|https://){1}[\w\.\-/:\#\?=\&;%\~\+]+").unwrap().is_match(&contents) {
|
||||
info!("URL detected, skipping TSF conversion");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.tsf_conversion.is_none() {
|
||||
self.tsf_conversion = Some(TsfConversion::new());
|
||||
|
||||
println!("TSF conversion created.");
|
||||
info!("TSF conversion created");
|
||||
}
|
||||
|
||||
let tsf_conversion = self.tsf_conversion.as_mut().unwrap();
|
||||
|
||||
let converted = tsf_conversion.convert(contents)?;
|
||||
|
||||
println!("TSF conversion: {} -> {}", contents, converted);
|
||||
info!("TSF conversion: {} -> {}", contents, converted);
|
||||
|
||||
self.last_text = contents.to_string().clone();
|
||||
|
||||
@ -65,10 +68,12 @@ impl ConversionHandler {
|
||||
let mut count = 0;
|
||||
while self.clipboard_ctx.set_contents(converted.clone()).is_err() {
|
||||
if count > 4 {
|
||||
warn!("Failed to set clipboard contents after 5 attempts");
|
||||
break;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
info!("Conversion returned to clipboard");
|
||||
},
|
||||
OnCopyMode::ReturnToChatbox => {
|
||||
let sock = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
@ -81,7 +86,11 @@ impl ConversionHandler {
|
||||
]
|
||||
})).unwrap();
|
||||
|
||||
sock.send_to(&msg_buf, "127.0.0.1:9000").unwrap();
|
||||
if let Err(e) = sock.send_to(&msg_buf, "127.0.0.1:9000") {
|
||||
error!("Failed to send UDP packet: {}", e);
|
||||
} else {
|
||||
info!("Conversion returned to chatbox");
|
||||
}
|
||||
},
|
||||
OnCopyMode::SendDirectly => {
|
||||
let sock = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
@ -94,7 +103,11 @@ impl ConversionHandler {
|
||||
]
|
||||
})).unwrap();
|
||||
|
||||
sock.send_to(&msg_buf, "127.0.0.1:9000").unwrap();
|
||||
if let Err(e) = sock.send_to(&msg_buf, "127.0.0.1:9000") {
|
||||
error!("Failed to send UDP packet: {}", e);
|
||||
} else {
|
||||
info!("Conversion sent directly");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@ -105,7 +118,7 @@ impl ConversionHandler {
|
||||
original: parsed_contents,
|
||||
converted
|
||||
}).is_err() {
|
||||
println!("App handle add log failed.");
|
||||
error!("App handle add log failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,14 +128,16 @@ impl ClipboardHandler for ConversionHandler {
|
||||
let config = self.get_config();
|
||||
if let Ok(mut contents) = self.clipboard_ctx.get_contents() {
|
||||
if config.use_tsf_reconvert {
|
||||
self.tsf_conversion(&contents, &config).expect("TSF conversion failed.");
|
||||
if let Err(e) = self.tsf_conversion(&contents, &config) {
|
||||
error!("TSF conversion failed: {}", e);
|
||||
}
|
||||
return CallbackResult::Next;
|
||||
}
|
||||
|
||||
if contents != self.last_text {
|
||||
if contents.starts_with(&config.prefix) || config.ignore_prefix {
|
||||
|
||||
if config.skip_url && Regex::new(r"(http://|https://){1}[\w\.\-/:\#\?=\&;%\~\+]+").unwrap().is_match(&contents) {
|
||||
info!("URL detected, skipping conversion");
|
||||
return CallbackResult::Next;
|
||||
}
|
||||
|
||||
@ -130,7 +145,7 @@ impl ClipboardHandler for ConversionHandler {
|
||||
let converted = match self.conversion.convert_text(&parsed_contents) {
|
||||
Ok(converted) => converted,
|
||||
Err(err) => {
|
||||
println!("Error: {:?}", err);
|
||||
error!("Conversion error: {:?}", err);
|
||||
format!("Error: {:?}", err)
|
||||
}
|
||||
};
|
||||
|
@ -21,6 +21,7 @@ use clipboard_master::Master;
|
||||
use com::Com;
|
||||
use config::Config;
|
||||
use handler::ConversionHandler;
|
||||
use tracing::Level;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct Log {
|
||||
@ -55,6 +56,7 @@ fn save_settings(config: Config, state: State<AppState>) -> Result<(), String> {
|
||||
|
||||
fn main() {
|
||||
println!("VRClipboard-IME Logs\nバグがあった場合はこのログを送ってください。");
|
||||
tracing_subscriber::fmt().with_max_level(Level::TRACE).init();
|
||||
tauri::Builder::default()
|
||||
.manage(AppState {
|
||||
config: Mutex::new(Config::load().unwrap_or_else(|_| {
|
||||
@ -64,6 +66,7 @@ fn main() {
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![load_settings, save_settings])
|
||||
.setup(|app| {
|
||||
let _span = tracing::span!(tracing::Level::INFO, "main");
|
||||
app.manage(STATE.lock().unwrap().clone());
|
||||
let app_handle = app.app_handle();
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use tracing::{debug, info, error};
|
||||
use windows::{
|
||||
core::Interface,
|
||||
Win32::UI::TextServices::{ITfFnSearchCandidateProvider, ITfFunctionProvider},
|
||||
@ -12,12 +13,31 @@ pub struct FunctionProvider {
|
||||
|
||||
impl FunctionProvider {
|
||||
pub fn new(function_provider: ITfFunctionProvider) -> Self {
|
||||
debug!("Creating new FunctionProvider");
|
||||
Self { function_provider }
|
||||
}
|
||||
|
||||
pub fn get_search_candidate_provider(&self) -> Result<SearchCandidateProvider> {
|
||||
debug!("Getting search candidate provider");
|
||||
let zeroed_guid = windows_core::GUID::zeroed();
|
||||
let search_candidate_provider = unsafe { self.function_provider.GetFunction(&zeroed_guid, &ITfFnSearchCandidateProvider::IID)? };
|
||||
Ok(SearchCandidateProvider::new(search_candidate_provider.cast()?))
|
||||
match unsafe { self.function_provider.GetFunction(&zeroed_guid, &ITfFnSearchCandidateProvider::IID) } {
|
||||
Ok(search_candidate_provider) => {
|
||||
info!("Search candidate provider obtained successfully");
|
||||
match search_candidate_provider.cast() {
|
||||
Ok(provider) => {
|
||||
debug!("Successfully cast search candidate provider");
|
||||
Ok(SearchCandidateProvider::new(provider))
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to cast search candidate provider: {:?}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to get search candidate provider: {:?}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use tracing::{debug, info, error};
|
||||
use windows::Win32::{
|
||||
System::Com::{CoCreateInstance, CLSCTX_INPROC_SERVER},
|
||||
UI::{Input::KeyboardAndMouse::HKL, TextServices::{CLSID_TF_InputProcessorProfiles, ITfInputProcessorProfileMgr, GUID_TFCAT_TIP_KEYBOARD, TF_INPUTPROCESSORPROFILE, TF_IPPMF_DONTCARECURRENTINPUTLANGUAGE, TF_PROFILETYPE_INPUTPROCESSOR}},
|
||||
@ -10,21 +11,40 @@ pub struct InputProcessorProfileMgr {
|
||||
|
||||
impl InputProcessorProfileMgr {
|
||||
pub fn new() -> Result<Self> {
|
||||
debug!("Creating new InputProcessorProfileMgr");
|
||||
let input_processor_profile_mgr = unsafe { CoCreateInstance(&CLSID_TF_InputProcessorProfiles, None, CLSCTX_INPROC_SERVER)? };
|
||||
info!("InputProcessorProfileMgr created successfully");
|
||||
Ok(InputProcessorProfileMgr { input_processor_profile_mgr })
|
||||
}
|
||||
|
||||
pub fn get_active_profile(&self) -> Result<TF_INPUTPROCESSORPROFILE> {
|
||||
debug!("Getting active profile");
|
||||
let keyboard_guid = GUID_TFCAT_TIP_KEYBOARD;
|
||||
let mut profile = TF_INPUTPROCESSORPROFILE::default();
|
||||
|
||||
unsafe { self.input_processor_profile_mgr.GetActiveProfile(&keyboard_guid, &mut profile)? };
|
||||
|
||||
Ok(profile)
|
||||
match unsafe { self.input_processor_profile_mgr.GetActiveProfile(&keyboard_guid, &mut profile) } {
|
||||
Ok(_) => {
|
||||
info!("Active profile retrieved successfully");
|
||||
Ok(profile)
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to get active profile: {:?}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate_profile(&self, profile: &TF_INPUTPROCESSORPROFILE) -> Result<()> {
|
||||
unsafe { self.input_processor_profile_mgr.ActivateProfile(TF_PROFILETYPE_INPUTPROCESSOR, profile.langid, &profile.clsid, &profile.guidProfile, HKL::default(), TF_IPPMF_DONTCARECURRENTINPUTLANGUAGE)? };
|
||||
Ok(())
|
||||
debug!("Activating profile: {:?}", profile);
|
||||
match unsafe { self.input_processor_profile_mgr.ActivateProfile(TF_PROFILETYPE_INPUTPROCESSOR, profile.langid, &profile.clsid, &profile.guidProfile, HKL::default(), TF_IPPMF_DONTCARECURRENTINPUTLANGUAGE) } {
|
||||
Ok(_) => {
|
||||
info!("Profile activated successfully");
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to activate profile: {:?}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use tracing::{debug, error};
|
||||
use windows::Win32::UI::WindowsAndMessaging::{SystemParametersInfoW, SPI_SETTHREADLOCALINPUTSETTINGS, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS};
|
||||
|
||||
pub mod input_processor_profile_mgr;
|
||||
@ -7,8 +8,16 @@ pub mod search_candidate_provider;
|
||||
pub mod thread_mgr;
|
||||
|
||||
pub fn set_thread_local_input_settings(thread_local_input_settings: bool) -> Result<()> {
|
||||
debug!("Setting thread local input settings to: {}", thread_local_input_settings);
|
||||
let mut result = thread_local_input_settings;
|
||||
unsafe { SystemParametersInfoW(SPI_SETTHREADLOCALINPUTSETTINGS, 0, Some(&mut result as *mut _ as *const _ as *mut _), SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0))? };
|
||||
|
||||
Ok(())
|
||||
match unsafe { SystemParametersInfoW(SPI_SETTHREADLOCALINPUTSETTINGS, 0, Some(&mut result as *mut _ as *const _ as *mut _), SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0)) } {
|
||||
Ok(_) => {
|
||||
debug!("Successfully set thread local input settings");
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to set thread local input settings: {:?}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use tracing::{debug, info, error, trace};
|
||||
use windows::Win32::UI::TextServices::{ITfFnSearchCandidateProvider, TF_TMAE_NOACTIVATEKEYBOARDLAYOUT};
|
||||
|
||||
use super::{function_provider::FunctionProvider, input_processor_profile_mgr::InputProcessorProfileMgr, thread_mgr::ThreadMgr};
|
||||
@ -9,41 +10,60 @@ pub struct SearchCandidateProvider {
|
||||
|
||||
impl SearchCandidateProvider {
|
||||
pub fn new(search_candidate_provider: ITfFnSearchCandidateProvider) -> Self {
|
||||
debug!("Creating new SearchCandidateProvider");
|
||||
Self { search_candidate_provider }
|
||||
}
|
||||
|
||||
pub fn create() -> Result<Self> {
|
||||
info!("Creating SearchCandidateProvider");
|
||||
let profile_mgr = InputProcessorProfileMgr::new()?;
|
||||
let profile = profile_mgr.get_active_profile()?;
|
||||
debug!("Activating profile");
|
||||
profile_mgr.activate_profile(&profile)?;
|
||||
|
||||
debug!("Creating ThreadMgr");
|
||||
let thread_mgr = ThreadMgr::new()?;
|
||||
let _client_id = thread_mgr.activate_ex(TF_TMAE_NOACTIVATEKEYBOARDLAYOUT)?;
|
||||
|
||||
debug!("Getting function provider");
|
||||
let function_provider = thread_mgr.get_function_provider(&profile.clsid)?;
|
||||
|
||||
debug!("Getting search candidate provider");
|
||||
let search_candidate_provider = FunctionProvider::new(function_provider).get_search_candidate_provider()?;
|
||||
|
||||
info!("SearchCandidateProvider created successfully");
|
||||
Ok(search_candidate_provider)
|
||||
}
|
||||
|
||||
pub fn get_candidates(&self, input: &str, max: usize) -> Result<Vec<String>> {
|
||||
debug!("Getting candidates for input: {}, max: {}", input, max);
|
||||
let input_utf16: Vec<u16> = input.encode_utf16().chain(Some(0)).collect();
|
||||
let input_bstr = windows_core::BSTR::from_wide(&input_utf16)?;
|
||||
|
||||
let input_utf16: Vec<u16> = "".encode_utf16().chain(Some(0)).collect();
|
||||
let input_bstr_empty = windows_core::BSTR::from_wide(&input_utf16)?;
|
||||
|
||||
trace!("Calling GetSearchCandidates");
|
||||
let candidates = unsafe { self.search_candidate_provider.GetSearchCandidates(&input_bstr, &input_bstr_empty)? };
|
||||
let candidates_enum = unsafe { candidates.EnumCandidates()? };
|
||||
|
||||
let mut candidates = vec![None; max];
|
||||
let mut candidates_count = 0;
|
||||
trace!("Enumerating candidates");
|
||||
unsafe { candidates_enum.Next(&mut candidates, &mut candidates_count)? };
|
||||
|
||||
candidates.resize(candidates_count as usize, None);
|
||||
|
||||
let candidates: Vec<String> = candidates.iter().map(|candidate| unsafe { candidate.as_ref().unwrap().GetString().unwrap().to_string() }).collect();
|
||||
let candidates: Vec<String> = candidates.iter().map(|candidate| unsafe {
|
||||
match candidate.as_ref().unwrap().GetString() {
|
||||
Ok(s) => s.to_string(),
|
||||
Err(e) => {
|
||||
error!("Failed to get candidate string: {:?}", e);
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}).collect();
|
||||
info!("Retrieved {} candidates", candidates.len());
|
||||
Ok(candidates)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use tracing::{debug, error, info};
|
||||
use windows::Win32::{
|
||||
System::Com::{CoCreateInstance, CLSCTX_INPROC_SERVER},
|
||||
UI::TextServices::{CLSID_TF_ThreadMgr, ITfFunctionProvider, ITfThreadMgr2},
|
||||
@ -10,17 +11,31 @@ pub struct ThreadMgr {
|
||||
|
||||
impl ThreadMgr {
|
||||
pub fn new() -> Result<Self> {
|
||||
debug!("Creating new ThreadMgr");
|
||||
let thread_mgr = unsafe { CoCreateInstance(&CLSID_TF_ThreadMgr, None, CLSCTX_INPROC_SERVER)? };
|
||||
info!("ThreadMgr created successfully");
|
||||
Ok(ThreadMgr { thread_mgr })
|
||||
}
|
||||
|
||||
pub fn activate_ex(&self, flags: u32) -> Result<u32> {
|
||||
debug!("Activating ThreadMgr with flags: {}", flags);
|
||||
let mut client_id = 0;
|
||||
unsafe { self.thread_mgr.ActivateEx(&mut client_id as *mut _ as *const _ as *mut _, flags)? };
|
||||
info!("ThreadMgr activated with client_id: {}", client_id);
|
||||
Ok(client_id)
|
||||
}
|
||||
|
||||
pub fn get_function_provider(&self, clsid: &windows_core::GUID) -> Result<ITfFunctionProvider> {
|
||||
Ok(unsafe { self.thread_mgr.GetFunctionProvider(clsid)? })
|
||||
debug!("Getting function provider for CLSID: {:?}", clsid);
|
||||
match unsafe { self.thread_mgr.GetFunctionProvider(clsid) } {
|
||||
Ok(provider) => {
|
||||
info!("Function provider obtained successfully");
|
||||
Ok(provider)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to get function provider: {:?}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use tracing::{info, debug, error, trace};
|
||||
use crate::{converter::{converter::Converter, hiragana::HiraganaConverter, roman_to_kanji::RomanToKanjiConverter}, tsf::{search_candidate_provider::SearchCandidateProvider, set_thread_local_input_settings}};
|
||||
|
||||
pub struct TsfConversion {
|
||||
@ -14,9 +15,10 @@ pub struct TsfConversion {
|
||||
|
||||
impl TsfConversion {
|
||||
pub fn new() -> Self {
|
||||
info!("Creating new TsfConversion instance");
|
||||
set_thread_local_input_settings(true).unwrap();
|
||||
|
||||
Self {
|
||||
let instance = Self {
|
||||
conversion_history: Vec::new(),
|
||||
clipboard_history: Vec::new(),
|
||||
now_reconvertion: false,
|
||||
@ -25,55 +27,73 @@ impl TsfConversion {
|
||||
reconversion_candidates: None,
|
||||
reconversion_index: None,
|
||||
reconversion_prefix: None,
|
||||
}
|
||||
};
|
||||
instance
|
||||
}
|
||||
|
||||
fn reset_conversion_state(&mut self) {
|
||||
debug!("Resetting conversion state");
|
||||
trace!("Before reset - now_reconvertion: {}, reconversion_prefix: {:?}, reconversion_index: {:?}, reconversion_candidates: {:?}",
|
||||
self.now_reconvertion, self.reconversion_prefix, self.reconversion_index, self.reconversion_candidates);
|
||||
self.now_reconvertion = false;
|
||||
self.reconversion_prefix = None;
|
||||
self.reconversion_index = None;
|
||||
self.reconversion_candidates = None;
|
||||
trace!("After reset - now_reconvertion: {}, reconversion_prefix: {:?}, reconversion_index: {:?}, reconversion_candidates: {:?}",
|
||||
self.now_reconvertion, self.reconversion_prefix, self.reconversion_index, self.reconversion_candidates);
|
||||
}
|
||||
|
||||
fn convert_roman_to_kanji(&mut self, text: &str) -> Result<String> {
|
||||
debug!("Converting roman to kanji: {}", text);
|
||||
let o_minus_1 = self.conversion_history.get(if self.conversion_history.len() > 0 { self.conversion_history.len() - 1 } else { 0 }).unwrap_or(&("".to_string())).clone();
|
||||
trace!("Previous conversion (o_minus_1): {}", o_minus_1);
|
||||
let mut first_diff_position = o_minus_1.chars().zip(text.chars()).position(|(a, b)| a != b);
|
||||
|
||||
if o_minus_1 != text && first_diff_position.is_none() {
|
||||
first_diff_position = Some(o_minus_1.chars().count());
|
||||
}
|
||||
trace!("First difference position: {:?}", first_diff_position);
|
||||
let diff = text.chars().skip(first_diff_position.unwrap_or(0)).collect::<String>();
|
||||
debug!("Difference to convert: {}", diff);
|
||||
|
||||
let roman_to_kanji_converter = RomanToKanjiConverter;
|
||||
let converted = roman_to_kanji_converter.convert(&diff)?;
|
||||
trace!("Converted difference: {}", converted);
|
||||
self.conversion_history.push(o_minus_1.chars().zip(text.chars()).take_while(|(a, b)| a == b).map(|(a, _)| a).collect::<String>() + &converted);
|
||||
self.clipboard_history.push(text.to_string());
|
||||
return Ok(self.conversion_history.last().unwrap().clone());
|
||||
info!("Roman to kanji conversion result: {}", self.conversion_history.last().unwrap());
|
||||
trace!("Updated conversion history: {:?}", self.conversion_history);
|
||||
trace!("Updated clipboard history: {:?}", self.clipboard_history);
|
||||
Ok(self.conversion_history.last().unwrap().clone())
|
||||
}
|
||||
|
||||
fn convert_tsf(&mut self, text: &str) -> Result<String> {
|
||||
debug!("Converting using TSF: {}", text);
|
||||
self.now_reconvertion = true;
|
||||
let mut diff_hiragana = String::new();
|
||||
let mut diff = String::new();
|
||||
if self.reconversion_prefix.is_none() {
|
||||
let o_minus_2 = self.conversion_history.get(if self.conversion_history.len() > 1 { self.conversion_history.len() - 2 } else { 0 }).unwrap_or(&("".to_string())).clone();
|
||||
let i_minus_1 = self.clipboard_history.get(if self.clipboard_history.len() > 0 { self.clipboard_history.len() - 1 } else { 0 }).unwrap_or(&("".to_string())).clone();
|
||||
println!("o,i: {}, {}", o_minus_2, i_minus_1);
|
||||
trace!("o_minus_2: {}, i_minus_1: {}", o_minus_2, i_minus_1);
|
||||
let mut first_diff_position = i_minus_1.chars().zip(o_minus_2.chars()).position(|(a, b)| a != b);
|
||||
println!("diff_pos: {:?}", first_diff_position);
|
||||
trace!("First difference position: {:?}", first_diff_position);
|
||||
if o_minus_2 != i_minus_1 && first_diff_position.is_none() {
|
||||
first_diff_position = Some(o_minus_2.chars().count());
|
||||
}
|
||||
diff = i_minus_1.chars().skip(first_diff_position.unwrap_or(0)).collect::<String>();
|
||||
println!("diff: {}", diff);
|
||||
debug!("Difference to convert: {}", diff);
|
||||
diff_hiragana = HiraganaConverter.convert(&diff)?;
|
||||
trace!("Hiragana conversion: {}", diff_hiragana);
|
||||
let prefix = i_minus_1.chars().zip(o_minus_2.chars()).take_while(|(a, b)| a == b).map(|(a, _)| a).collect::<String>();
|
||||
self.reconversion_prefix = Some(prefix.clone());
|
||||
trace!("Set reconversion prefix: {:?}", self.reconversion_prefix);
|
||||
}
|
||||
println!("diff_hiragana: {}", diff_hiragana);
|
||||
|
||||
let candidates = self.reconversion_candidates.get_or_insert_with(|| {
|
||||
debug!("Generating new candidates");
|
||||
let mut candidates = self.search_candidate_provider.get_candidates(&diff_hiragana, 10).unwrap_or_default();
|
||||
trace!("Initial candidates: {:?}", candidates);
|
||||
if candidates.is_empty() {
|
||||
candidates.push(diff_hiragana.clone());
|
||||
let roman_to_kanji_converter = RomanToKanjiConverter;
|
||||
@ -81,23 +101,24 @@ impl TsfConversion {
|
||||
candidates.push(roman_to_kanji);
|
||||
}
|
||||
candidates.insert(0, diff.to_string());
|
||||
trace!("Final candidates: {:?}", candidates);
|
||||
candidates
|
||||
});
|
||||
|
||||
let index = self.reconversion_index.get_or_insert(-1);
|
||||
trace!("Current reconversion index: {}", index);
|
||||
|
||||
if *index + 1 < candidates.len() as i32 {
|
||||
*index += 1;
|
||||
} else {
|
||||
*index = 0;
|
||||
}
|
||||
|
||||
if self.reconversion_candidates.is_some() {
|
||||
println!("Candidates: {:?}", self.reconversion_candidates.as_ref().unwrap());
|
||||
}
|
||||
debug!("Updated reconversion index: {}", index);
|
||||
|
||||
self.conversion_history.push(self.reconversion_prefix.clone().unwrap() + &self.reconversion_candidates.as_ref().unwrap()[self.reconversion_index.unwrap() as usize].clone());
|
||||
self.clipboard_history.push(text.to_string());
|
||||
trace!("Updated conversion history: {:?}", self.conversion_history);
|
||||
trace!("Updated clipboard history: {:?}", self.clipboard_history);
|
||||
|
||||
while self.conversion_history.len() > 3 {
|
||||
self.conversion_history.remove(0);
|
||||
@ -105,32 +126,39 @@ impl TsfConversion {
|
||||
while self.clipboard_history.len() > 3 {
|
||||
self.clipboard_history.remove(0);
|
||||
}
|
||||
trace!("Trimmed conversion history: {:?}", self.conversion_history);
|
||||
trace!("Trimmed clipboard history: {:?}", self.clipboard_history);
|
||||
|
||||
return Ok(self.conversion_history.last().unwrap().clone());
|
||||
info!("TSF conversion result: {}", self.conversion_history.last().unwrap());
|
||||
Ok(self.conversion_history.last().unwrap().clone())
|
||||
}
|
||||
|
||||
pub fn convert(&mut self, text: &str) -> Result<String> {
|
||||
println!();
|
||||
println!("History: {:?}, {:?}", self.conversion_history, self.clipboard_history);
|
||||
println!("{} == {}", text, self.conversion_history.last().unwrap_or(&("".to_string())).clone());
|
||||
debug!("Starting conversion for: {}", text);
|
||||
trace!("Current conversion history: {:?}", self.conversion_history);
|
||||
trace!("Current clipboard history: {:?}", self.clipboard_history);
|
||||
let same_as_last_conversion = text.to_string() == self.conversion_history.last().unwrap_or(&("".to_string())).clone();
|
||||
trace!("Same as last conversion: {}", same_as_last_conversion);
|
||||
|
||||
self.target_text = text.to_string();
|
||||
trace!("Set target text: {}", self.target_text);
|
||||
|
||||
if !same_as_last_conversion && self.now_reconvertion {
|
||||
debug!("Resetting conversion state due to new input");
|
||||
self.reset_conversion_state();
|
||||
}
|
||||
|
||||
if !self.now_reconvertion && !same_as_last_conversion {
|
||||
println!("Convert using roman_to_kanji");
|
||||
info!("Converting using roman_to_kanji");
|
||||
return self.convert_roman_to_kanji(text);
|
||||
}
|
||||
|
||||
if same_as_last_conversion || self.now_reconvertion {
|
||||
println!("Convert using TSF");
|
||||
info!("Converting using TSF");
|
||||
return self.convert_tsf(text);
|
||||
}
|
||||
|
||||
error!("Failed to convert: {}", text);
|
||||
Err(anyhow::anyhow!("Failed to convert"))
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "vrclipboard-ime-gui",
|
||||
"version": "1.7.0"
|
||||
"version": "1.8.0"
|
||||
},
|
||||
"tauri": {
|
||||
"updater": {
|
||||
|
@ -192,7 +192,7 @@ const SettingsComponent = () => {
|
||||
/>
|
||||
<label htmlFor="use_tsf_reconvert" className="text-sm font-medium text-gray-700">
|
||||
ベータ機能: Text Services Framework 再変換を使用(区切り、モード変更、開始文字が無効化されます)<br />
|
||||
Windows11を使用している場合は、「以前のバージョンの Microsoft IME を使う」を有効化する必要があります。
|
||||
Windows10または11を使用している場合は、「以前のバージョンの Microsoft IME を使う」を有効化する必要があります。
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
|
@ -10,7 +10,7 @@ const TitleBar = () => {
|
||||
<div className="flex justify-between items-center bg-gray-800 text-white h-8 px-2" data-tauri-drag-region>
|
||||
<div className="flex items-center">
|
||||
<span className="text-sm font-semibold">VRClipboard-IME</span>
|
||||
<span className="text-xs font-semibold ml-2">v1.6.0</span>
|
||||
<span className="text-xs font-semibold ml-2">v1.7.0</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<button onClick={handleMinimize} className="p-1 hover:bg-gray-700 focus:outline-none">
|
||||
|
Reference in New Issue
Block a user