add tracing

This commit is contained in:
mii443
2024-09-25 23:48:02 +09:00
parent cc361b67a9
commit 19b7981428
25 changed files with 380 additions and 79 deletions

View File

@ -1,7 +1,7 @@
{
"name": "vrclipboard-ime-gui",
"private": true,
"version": "1.7.0",
"version": "1.8.0",
"type": "module",
"scripts": {
"dev": "vite",

View File

@ -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
View File

@ -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",
]

View File

@ -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"

View File

@ -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");
};
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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)
}
};

View File

@ -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();

View File

@ -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())
}
}
}
}

View File

@ -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)? };
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)? };
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())
}
}
}
}

View File

@ -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))? };
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())
}
}
}

View File

@ -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)
}
}

View File

@ -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())
}
}
}
}

View File

@ -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"))
}
}

View File

@ -7,7 +7,7 @@
},
"package": {
"productName": "vrclipboard-ime-gui",
"version": "1.7.0"
"version": "1.8.0"
},
"tauri": {
"updater": {

View File

@ -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

View File

@ -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">