init
7
src-tauri/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Generated by Tauri
|
||||
# will have schema files for capabilities auto-completion
|
||||
/gen/schemas
|
||||
4165
src-tauri/Cargo.lock
generated
Normal file
40
src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "vrclipboard-ime-gui"
|
||||
version = "1.4.0"
|
||||
description = "VRClipboard IME"
|
||||
authors = ["mii"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "1", features = [ "window-close", "window-start-dragging", "window-show", "window-unmaximize", "window-minimize", "window-maximize", "window-hide", "window-unminimize", "shell-open"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
chrono = "0.4.38"
|
||||
chrono-tz = "0.9.0"
|
||||
anyhow = "1.0.86"
|
||||
clipboard = "0.5.0"
|
||||
clipboard-master = "3.1.3"
|
||||
serde_derive = "1.0.203"
|
||||
serde_yaml = "0.9.34"
|
||||
calc = { version = "*", default-features = false }
|
||||
platform-dirs = "0.3.0"
|
||||
once_cell = "1.19.0"
|
||||
rosc = "~0.10"
|
||||
regex = "1"
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.52"
|
||||
features = [
|
||||
"Win32_System_Com",
|
||||
"Win32_UI_Input_Ime"
|
||||
]
|
||||
|
||||
|
||||
[features]
|
||||
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
3
src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
4
src-tauri/config.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
prefix: ";"
|
||||
command: ";"
|
||||
split: "/"
|
||||
ignore_prefix: false
|
||||
BIN
src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
src-tauri/icons/icon.icns
Normal file
BIN
src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
17
src-tauri/src/com.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use anyhow::Result;
|
||||
use windows::Win32::System::Com::{CoInitialize, CoUninitialize};
|
||||
|
||||
pub struct Com;
|
||||
|
||||
impl Drop for Com {
|
||||
fn drop(&mut self) {
|
||||
unsafe { CoUninitialize() };
|
||||
}
|
||||
}
|
||||
|
||||
impl Com {
|
||||
pub fn new() -> Result<Self> {
|
||||
unsafe { CoInitialize(None)? };
|
||||
Ok(Com)
|
||||
}
|
||||
}
|
||||
114
src-tauri/src/config.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use std::{fs::File, io::{Read, Write}, path::{Path, PathBuf}};
|
||||
|
||||
use platform_dirs::AppDirs;
|
||||
use serde::Serialize;
|
||||
use serde_derive::Deserialize;
|
||||
use anyhow::Result;
|
||||
use tauri::State;
|
||||
|
||||
use crate::AppState;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
#[serde(default = "semicolon" )]
|
||||
pub prefix: String,
|
||||
#[serde(default = "slash" )]
|
||||
pub split: String,
|
||||
#[serde(default = "semicolon" )]
|
||||
pub command: String,
|
||||
#[serde(default = "bool_true")]
|
||||
pub ignore_prefix: bool,
|
||||
#[serde(default)]
|
||||
pub on_copy_mode: OnCopyMode,
|
||||
#[serde(default = "bool_true")]
|
||||
pub skip_url: bool
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
prefix: ";".to_string(),
|
||||
split: "/".to_string(),
|
||||
command: ";".to_string(),
|
||||
ignore_prefix: true,
|
||||
on_copy_mode: OnCopyMode::ReturnToChatbox ,
|
||||
skip_url: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn slash() -> String { String::from("/") }
|
||||
#[inline]
|
||||
fn semicolon() -> String { String::from(";") }
|
||||
#[inline]
|
||||
fn bool_true() -> bool { true }
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub enum OnCopyMode {
|
||||
ReturnToClipboard,
|
||||
ReturnToChatbox,
|
||||
SendDirectly
|
||||
}
|
||||
|
||||
impl Default for OnCopyMode {
|
||||
fn default() -> Self {
|
||||
Self::ReturnToChatbox
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Result<Config> {
|
||||
std::fs::create_dir_all(Self::get_path()).unwrap();
|
||||
|
||||
if !Path::new(&Self::get_path().join("config.yaml")).exists() {
|
||||
Self::generate_default_config()?;
|
||||
}
|
||||
let mut file = File::open(&Self::get_path().join("config.yaml"))?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
let config: Config = serde_yaml::from_str(&contents)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn save(&self, state: State<AppState>) -> Result<(), String> {
|
||||
std::fs::create_dir_all(Self::get_path()).unwrap();
|
||||
|
||||
let mut file = match File::create(&Self::get_path().join("config.yaml")) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
println!("Err: {:?}", e);
|
||||
return Err(format!("Failed to create config file: {}", e))
|
||||
},
|
||||
};
|
||||
|
||||
match serde_yaml::to_string(&self) {
|
||||
Ok(yaml) => {
|
||||
if let Err(e) = file.write_all(yaml.as_bytes()) {
|
||||
println!("Err: {:?}", e);
|
||||
return Err(format!("Failed to write config: {}", e));
|
||||
}
|
||||
let mut app_config = state.config.lock().unwrap();
|
||||
*app_config = self.clone();
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
println!("Err: {:?}", 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())?;
|
||||
file.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_path() -> PathBuf {
|
||||
let app_dirs = AppDirs::new(Some("vrclipboard-ime"), false).unwrap();
|
||||
let app_data = app_dirs.config_dir;
|
||||
app_data
|
||||
}
|
||||
}
|
||||
79
src-tauri/src/conversion.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use crate::{config::Config, converter::converter::{get_custom_converter, Converter}, STATE};
|
||||
use anyhow::Result;
|
||||
use regex::Regex;
|
||||
|
||||
pub struct ConversionBlock {
|
||||
pub text: String,
|
||||
pub converter: Box<dyn Converter>,
|
||||
}
|
||||
|
||||
pub struct Conversion;
|
||||
|
||||
impl Conversion {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub fn convert_text(&self, text: &str) -> Result<String> {
|
||||
println!("Processing text: {}", text);
|
||||
let blocks = self.split_text(text)?;
|
||||
self.convert_blocks(blocks)
|
||||
}
|
||||
|
||||
pub fn convert_blocks(&self, blocks: Vec<ConversionBlock>) -> Result<String> {
|
||||
let mut result = String::new();
|
||||
for block in blocks {
|
||||
let converted = self.convert_block(&block)?;
|
||||
println!(" {}: {} -> {}", block.converter.name(), block.text, converted);
|
||||
result.push_str(&converted);
|
||||
}
|
||||
println!(" {}", result);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn convert_block(&self, block: &ConversionBlock) -> Result<String> {
|
||||
if block.text == "" {
|
||||
return Ok(String::default());
|
||||
}
|
||||
block.converter.convert(&block.text)
|
||||
}
|
||||
|
||||
pub fn split_text(&self, text: &str) -> Result<Vec<ConversionBlock>> {
|
||||
let mut text = text.to_string();
|
||||
let mut blocks = Vec::new();
|
||||
let mut current_converter = 'r';
|
||||
|
||||
let config = self.get_config();
|
||||
|
||||
if text.starts_with(&config.command) {
|
||||
text = text.split_off(1);
|
||||
if text.len() != 0 {
|
||||
current_converter = text.chars().next().unwrap_or('n');
|
||||
text = text.split_off(1);
|
||||
}
|
||||
}
|
||||
|
||||
for (i, command_splitted) in text.split(&config.command).enumerate() {
|
||||
let mut command_splitted = command_splitted.to_string();
|
||||
if i != 0 {
|
||||
if command_splitted.len() != 0 {
|
||||
current_converter = command_splitted.chars().next().unwrap_or('n');
|
||||
command_splitted = command_splitted.split_off(1);
|
||||
}
|
||||
}
|
||||
|
||||
for splitted in command_splitted.split(&config.split) {
|
||||
blocks.push(ConversionBlock {
|
||||
text: splitted.to_string(),
|
||||
converter: get_custom_converter(current_converter).unwrap_or(get_custom_converter('n').unwrap())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(blocks)
|
||||
}
|
||||
|
||||
pub fn get_config(&self) -> Config {
|
||||
STATE.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
21
src-tauri/src/converter/calculator.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use calc::Context;
|
||||
|
||||
use super::converter::Converter;
|
||||
|
||||
pub struct CalculatorConverter;
|
||||
|
||||
impl Converter for CalculatorConverter {
|
||||
fn convert(&self, text: &str) -> anyhow::Result<String> {
|
||||
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)
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
"calculator".to_string()
|
||||
}
|
||||
}
|
||||
19
src-tauri/src/converter/converter.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use super::{calculator::CalculatorConverter, hiragana::HiraganaConverter, katakana::KatakanaConverter, none_converter::NoneConverter, roman_to_kanji::RomanToKanjiConverter};
|
||||
|
||||
pub trait Converter {
|
||||
fn convert(&self, text: &str) -> Result<String>;
|
||||
fn name(&self) -> String;
|
||||
}
|
||||
|
||||
pub fn get_custom_converter(prefix: char) -> Option<Box<dyn 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,
|
||||
}
|
||||
}
|
||||
19
src-tauri/src/converter/hiragana.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use windows::Win32::UI::Input::Ime::{FELANG_CMODE_HIRAGANAOUT, FELANG_CMODE_NOINVISIBLECHAR, FELANG_CMODE_PRECONV, FELANG_REQ_REV};
|
||||
|
||||
use crate::felanguage::FElanguage;
|
||||
|
||||
use super::converter::Converter;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HiraganaConverter;
|
||||
|
||||
impl Converter for HiraganaConverter {
|
||||
fn convert(&self, text: &str) -> anyhow::Result<String> {
|
||||
let felanguage = FElanguage::new()?;
|
||||
felanguage.j_morph_result(text, FELANG_REQ_REV, FELANG_CMODE_HIRAGANAOUT | FELANG_CMODE_PRECONV | FELANG_CMODE_NOINVISIBLECHAR)
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
"hiragana".to_string()
|
||||
}
|
||||
}
|
||||
19
src-tauri/src/converter/katakana.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use windows::Win32::UI::Input::Ime::{FELANG_CMODE_KATAKANAOUT, FELANG_CMODE_NOINVISIBLECHAR, FELANG_CMODE_PRECONV, FELANG_REQ_REV};
|
||||
|
||||
use crate::felanguage::FElanguage;
|
||||
|
||||
use super::converter::Converter;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct KatakanaConverter;
|
||||
|
||||
impl Converter for KatakanaConverter {
|
||||
fn convert(&self, text: &str) -> anyhow::Result<String> {
|
||||
let felanguage = FElanguage::new()?;
|
||||
felanguage.j_morph_result(text, FELANG_REQ_REV, FELANG_CMODE_KATAKANAOUT | FELANG_CMODE_PRECONV | FELANG_CMODE_NOINVISIBLECHAR)
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
"katakana".to_string()
|
||||
}
|
||||
}
|
||||
6
src-tauri/src/converter/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod converter;
|
||||
mod hiragana;
|
||||
mod katakana;
|
||||
mod roman_to_kanji;
|
||||
mod calculator;
|
||||
mod none_converter;
|
||||
13
src-tauri/src/converter/none_converter.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use super::converter::Converter;
|
||||
|
||||
pub struct NoneConverter;
|
||||
|
||||
impl Converter for NoneConverter {
|
||||
fn convert(&self, text: &str) -> anyhow::Result<String> {
|
||||
Ok(text.to_string())
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
"none".to_string()
|
||||
}
|
||||
}
|
||||
21
src-tauri/src/converter/roman_to_kanji.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use windows::Win32::UI::Input::Ime::{FELANG_CMODE_HIRAGANAOUT, FELANG_CMODE_NOINVISIBLECHAR, FELANG_CMODE_PRECONV, FELANG_CMODE_ROMAN, FELANG_REQ_CONV};
|
||||
|
||||
use crate::felanguage::FElanguage;
|
||||
|
||||
use super::converter::Converter;
|
||||
|
||||
pub struct RomanToKanjiConverter;
|
||||
|
||||
impl Converter for RomanToKanjiConverter {
|
||||
fn convert(&self, text: &str) -> anyhow::Result<String> {
|
||||
let felanguage = FElanguage::new()?;
|
||||
felanguage.j_morph_result(text, FELANG_REQ_CONV, FELANG_CMODE_HIRAGANAOUT
|
||||
| FELANG_CMODE_ROMAN
|
||||
| FELANG_CMODE_NOINVISIBLECHAR
|
||||
| FELANG_CMODE_PRECONV)
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
"roman_to_kanji".to_string()
|
||||
}
|
||||
}
|
||||
54
src-tauri/src/felanguage.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use std::ptr;
|
||||
|
||||
use anyhow::Result;
|
||||
use windows::{
|
||||
core::{w, PCWSTR},
|
||||
Win32::{
|
||||
System::Com::{CLSIDFromProgID, CoCreateInstance, CLSCTX_ALL},
|
||||
UI::Input::Ime::IFELanguage,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct FElanguage {
|
||||
ife: IFELanguage,
|
||||
}
|
||||
|
||||
impl Drop for FElanguage {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.ife.Close().ok() };
|
||||
}
|
||||
}
|
||||
|
||||
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()? };
|
||||
Ok(FElanguage { ife })
|
||||
}
|
||||
|
||||
pub fn j_morph_result(&self, input: &str, request: u32, mode: u32) -> Result<String> {
|
||||
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 {
|
||||
self.ife.GetJMorphResult(
|
||||
request,
|
||||
mode,
|
||||
input_len as _,
|
||||
input_pcwstr,
|
||||
ptr::null_mut(),
|
||||
&mut result_ptr,
|
||||
)?;
|
||||
}
|
||||
|
||||
let result_struct = unsafe { ptr::read_unaligned(result_ptr) };
|
||||
let output_bstr_ptr = result_struct.pwchOutput;
|
||||
let output_bstr = unsafe { output_bstr_ptr.to_string()? };
|
||||
let output_string: String = output_bstr.chars().take(result_struct.cchOutput as usize).collect();
|
||||
|
||||
Ok(output_string)
|
||||
}
|
||||
}
|
||||
108
src-tauri/src/handler.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use std::net::UdpSocket;
|
||||
|
||||
use chrono::Local;
|
||||
use clipboard::{ClipboardContext, ClipboardProvider};
|
||||
use clipboard_master::{ClipboardHandler, CallbackResult};
|
||||
use regex::Regex;
|
||||
use rosc::{encoder, OscMessage, OscPacket, OscType};
|
||||
use tauri::{AppHandle, Manager};
|
||||
use crate::{config::{Config, OnCopyMode}, conversion::Conversion, Log, STATE};
|
||||
use anyhow::Result;
|
||||
|
||||
pub struct ConversionHandler {
|
||||
app_handle: AppHandle,
|
||||
conversion: Conversion,
|
||||
clipboard_ctx: ClipboardContext,
|
||||
last_text: String,
|
||||
}
|
||||
|
||||
impl ConversionHandler {
|
||||
pub fn new(app_handle: AppHandle) -> Result<Self> {
|
||||
let conversion = Conversion::new();
|
||||
let clipboard_ctx = ClipboardProvider::new().unwrap();
|
||||
|
||||
Ok(Self { app_handle, conversion, clipboard_ctx, last_text: String::new() })
|
||||
}
|
||||
|
||||
pub fn get_config(&self) -> Config {
|
||||
STATE.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ClipboardHandler for ConversionHandler {
|
||||
fn on_clipboard_change(&mut self) -> CallbackResult {
|
||||
let config = self.get_config();
|
||||
if let Ok(mut contents) = self.clipboard_ctx.get_contents() {
|
||||
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) {
|
||||
return CallbackResult::Next;
|
||||
}
|
||||
|
||||
let parsed_contents = if config.ignore_prefix { contents } else { contents.split_off(1) };
|
||||
let converted = match self.conversion.convert_text(&parsed_contents) {
|
||||
Ok(converted) => converted,
|
||||
Err(err) => {
|
||||
println!("Error: {:?}", err);
|
||||
format!("Error: {:?}", err)
|
||||
}
|
||||
};
|
||||
|
||||
self.last_text = converted.clone();
|
||||
|
||||
match config.on_copy_mode {
|
||||
OnCopyMode::ReturnToClipboard => {
|
||||
let mut count = 0;
|
||||
while self.clipboard_ctx.set_contents(converted.clone()).is_err() {
|
||||
if count > 4 {
|
||||
break;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
},
|
||||
OnCopyMode::ReturnToChatbox => {
|
||||
let sock = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let msg_buf = encoder::encode(&OscPacket::Message(OscMessage {
|
||||
addr: "/chatbox/input".to_string(),
|
||||
args: vec![
|
||||
OscType::String(converted.clone()),
|
||||
OscType::Bool(false),
|
||||
OscType::Bool(true)
|
||||
]
|
||||
})).unwrap();
|
||||
|
||||
sock.send_to(&msg_buf, "127.0.0.1:9000").unwrap();
|
||||
},
|
||||
OnCopyMode::SendDirectly => {
|
||||
let sock = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let msg_buf = encoder::encode(&OscPacket::Message(OscMessage {
|
||||
addr: "/chatbox/input".to_string(),
|
||||
args: vec![
|
||||
OscType::String(converted.clone()),
|
||||
OscType::Bool(true),
|
||||
OscType::Bool(true)
|
||||
]
|
||||
})).unwrap();
|
||||
|
||||
sock.send_to(&msg_buf, "127.0.0.1:9000").unwrap();
|
||||
},
|
||||
}
|
||||
|
||||
let datetime = Local::now();
|
||||
if self.app_handle
|
||||
.emit_all("addLog", Log {
|
||||
time: datetime.format("%Y %m/%d %H:%M:%S").to_string(),
|
||||
original: parsed_contents,
|
||||
converted
|
||||
}).is_err() {
|
||||
println!("App handle add log failed.");
|
||||
}
|
||||
} else {
|
||||
self.last_text = contents;
|
||||
}
|
||||
}
|
||||
}
|
||||
CallbackResult::Next
|
||||
}
|
||||
}
|
||||
82
src-tauri/src/main.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
//#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
mod com;
|
||||
mod felanguage;
|
||||
mod handler;
|
||||
mod conversion;
|
||||
mod config;
|
||||
mod converter;
|
||||
mod transform_rule;
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{Manager, State};
|
||||
|
||||
use clipboard_master::Master;
|
||||
use com::Com;
|
||||
use config::Config;
|
||||
use handler::ConversionHandler;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct Log {
|
||||
pub time: String,
|
||||
pub original: String,
|
||||
pub converted: String,
|
||||
}
|
||||
|
||||
struct AppState {
|
||||
config: Mutex<Config>,
|
||||
}
|
||||
|
||||
static STATE: Lazy<Mutex<Config>> = Lazy::new(|| Mutex::new(Config::load().unwrap()));
|
||||
|
||||
#[tauri::command]
|
||||
fn load_settings(state: State<AppState>) -> Result<Config, String> {
|
||||
match Config::load() {
|
||||
Ok(config) => {
|
||||
let mut app_config = state.config.lock().unwrap();
|
||||
*app_config = config.clone();
|
||||
Ok(config)
|
||||
},
|
||||
Err(e) => Err(format!("Failed to load settings: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn save_settings(config: Config, state: State<AppState>) -> Result<(), String> {
|
||||
*STATE.lock().unwrap() = config.clone();
|
||||
config.save(state)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("VRClipboard-IME Logs\nバグがあった場合はこのログを送ってください。");
|
||||
tauri::Builder::default()
|
||||
.manage(AppState {
|
||||
config: Mutex::new(Config::load().unwrap_or_else(|_| {
|
||||
Config::generate_default_config().expect("Failed to generate default config");
|
||||
Config::load().expect("Failed to load default config")
|
||||
})),
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![load_settings, save_settings])
|
||||
.setup(|app| {
|
||||
app.manage(STATE.lock().unwrap().clone());
|
||||
let app_handle = app.app_handle();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let _com = Com::new().unwrap();
|
||||
|
||||
let conversion_handler = ConversionHandler::new(app_handle).unwrap();
|
||||
|
||||
let mut master = Master::new(conversion_handler);
|
||||
|
||||
master.run().unwrap();
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
3
src-tauri/src/transform_rule.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub struct TransformRule {
|
||||
|
||||
}
|
||||
56
src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"beforeBuildCommand": "npm run build",
|
||||
"devPath": "http://localhost:1420",
|
||||
"distDir": "../dist"
|
||||
},
|
||||
"package": {
|
||||
"productName": "vrclipboard-ime-gui",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": false,
|
||||
"shell": {
|
||||
"all": false,
|
||||
"open": true
|
||||
},
|
||||
"window": {
|
||||
"maximize": true,
|
||||
"minimize": true,
|
||||
"hide": true,
|
||||
"startDragging": true,
|
||||
"show": true,
|
||||
"unmaximize": true,
|
||||
"unminimize": true,
|
||||
"close": true
|
||||
}
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"title": "VRClipboard IME",
|
||||
"width": 800,
|
||||
"height": 640,
|
||||
"visible": true,
|
||||
"decorations": false,
|
||||
"transparent": true
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"identifier": "dev.mii.vrclipboard-ime",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||