use crate::geometry::*;
use windows::Win32::{Foundation::*, Globalization::*, UI::Input::Ime::*};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Attribute {
Input,
TargetConverted,
Converted,
TargetNotConverted,
Error,
FixedConverted,
}
#[derive(Debug)]
pub struct CompositionChar {
pub ch: char,
pub attr: Attribute,
}
#[derive(Debug)]
pub struct Composition {
chars: Vec<CompositionChar>,
clause: Vec<std::ops::Range<usize>>,
}
impl Composition {
pub(crate) fn new(
s: String,
attrs: Vec<Attribute>,
clause: Vec<std::ops::Range<usize>>,
) -> Self {
Self {
chars: s
.chars()
.zip(attrs.into_iter())
.map(|(ch, attr)| CompositionChar { ch, attr })
.collect::<Vec<_>>(),
clause,
}
}
#[inline]
pub fn chars(&self) -> &Vec<CompositionChar> {
&self.chars
}
#[inline]
pub fn clause(&self) -> &Vec<std::ops::Range<usize>> {
&self.clause
}
}
#[derive(Debug)]
pub struct CandidateList {
list: Vec<String>,
selection: usize,
}
impl CandidateList {
pub(crate) fn new(list: Vec<String>, selection: usize) -> Self {
Self { list, selection }
}
#[inline]
pub fn is_empty(&self) -> bool {
self.list.is_empty()
}
#[inline]
pub fn len(&self) -> usize {
self.list.len()
}
#[inline]
pub fn iter(&self) -> impl Iterator + '_ {
self.list.iter()
}
#[inline]
pub fn selection(&self) -> (usize, &str) {
(self.selection, &self.list[self.selection])
}
}
impl std::ops::Index<usize> for CandidateList {
type Output = str;
fn index(&self, index: usize) -> &Self::Output {
&self.list[index]
}
}
pub struct ImmContext {
hwnd: HWND,
himc: HIMC,
}
impl ImmContext {
pub fn new(hwnd: HWND) -> Self {
unsafe {
let himc = ImmCreateContext();
ImmAssociateContextEx(hwnd, himc, IACE_CHILDREN);
Self { hwnd, himc }
}
}
#[inline]
pub fn enable(&self) {
unsafe {
ImmAssociateContextEx(self.hwnd, self.himc, IACE_CHILDREN);
}
}
#[inline]
pub fn disable(&self) {
unsafe {
ImmAssociateContextEx(self.hwnd, HIMC(0), IACE_IGNORENOCONTEXT);
}
}
}
impl Drop for ImmContext {
fn drop(&mut self) {
unsafe {
ImmAssociateContextEx(self.hwnd, HIMC(0), IACE_DEFAULT);
ImmDestroyContext(self.himc);
}
}
}
pub enum CompositionString {
CompStr(String),
CompAttr(Vec<Attribute>),
CompClause(Vec<std::ops::Range<usize>>),
ResultStr(String),
}
pub struct Imc {
hwnd: HWND,
himc: HIMC,
}
impl Imc {
#[inline]
pub fn get(hwnd: HWND) -> Self {
let himc = unsafe { ImmGetContext(hwnd) };
Self { hwnd, himc }
}
#[inline]
pub fn set_composition_window_position(&self, position: PhysicalPosition<i32>) {
unsafe {
let pt = POINT {
x: position.x,
y: position.y,
};
let form = COMPOSITIONFORM {
dwStyle: CFS_POINT,
ptCurrentPos: pt,
rcArea: RECT::default(),
};
ImmSetCompositionWindow(self.himc, &form);
}
}
pub fn set_candidate_window_position(
&self,
position: PhysicalPosition<i32>,
enable_exclude_rect: bool,
) {
unsafe {
let pt = POINT {
x: position.x,
y: position.y,
};
let form = CANDIDATEFORM {
dwStyle: CFS_CANDIDATEPOS,
dwIndex: 0,
ptCurrentPos: pt,
rcArea: RECT::default(),
};
ImmSetCandidateWindow(self.himc, &form);
if !enable_exclude_rect {
let form = CANDIDATEFORM {
dwStyle: CFS_EXCLUDE,
dwIndex: 0,
ptCurrentPos: pt,
rcArea: RECT {
left: pt.x,
top: pt.y,
right: pt.x,
bottom: pt.y,
},
};
ImmSetCandidateWindow(self.himc, &form);
}
}
}
pub fn get_composition_string(
&self,
index: IME_COMPOSITION_STRING,
) -> Option<CompositionString> {
unsafe fn get_string(himc: HIMC, index: IME_COMPOSITION_STRING) -> Option<String> {
let byte_len = ImmGetCompositionStringW(himc, index, None);
if byte_len == IMM_ERROR_NODATA || byte_len == IMM_ERROR_GENERAL {
return None;
}
let len = byte_len as usize / std::mem::size_of::<u16>();
let mut buf = Vec::new();
buf.resize(len, 0);
{
let tmp =
std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, byte_len as _);
ImmGetCompositionStringW(himc, index, Some(tmp));
}
let s = String::from_utf16_lossy(&buf);
if s.is_empty() {
None
} else {
Some(s)
}
}
unsafe fn get_attrs(himc: HIMC) -> Option<Vec<Attribute>> {
let byte_len = ImmGetCompositionStringW(himc, GCS_COMPATTR, None);
if byte_len == IMM_ERROR_NODATA || byte_len == IMM_ERROR_GENERAL {
return None;
}
let mut buf: Vec<u8> = Vec::new();
buf.resize(byte_len as _, 0);
ImmGetCompositionStringW(himc, GCS_COMPATTR, Some(&mut buf));
Some(
buf.into_iter()
.map(|v| match v as u32 {
ATTR_INPUT => Attribute::Input,
ATTR_TARGET_CONVERTED => Attribute::TargetConverted,
ATTR_CONVERTED => Attribute::Converted,
ATTR_TARGET_NOTCONVERTED => Attribute::TargetNotConverted,
ATTR_INPUT_ERROR => Attribute::Error,
ATTR_FIXEDCONVERTED => Attribute::FixedConverted,
_ => unreachable!(),
})
.collect::<Vec<_>>(),
)
}
unsafe fn get_clause(himc: HIMC) -> Option<Vec<std::ops::Range<usize>>> {
let byte_len = ImmGetCompositionStringW(himc, GCS_COMPCLAUSE, None);
if matches!(byte_len, IMM_ERROR_NODATA | IMM_ERROR_GENERAL) {
return None;
}
let mut buf: Vec<u8> = Vec::new();
buf.resize(byte_len as _, 0);
ImmGetCompositionStringW(himc, GCS_COMPCLAUSE, Some(&mut buf));
let buf = std::slice::from_raw_parts(
buf.as_ptr() as *const u32,
byte_len as usize / std::mem::size_of::<u32>(),
);
Some(
buf.windows(2)
.map(|a| a[0] as usize..a[1] as usize)
.collect(),
)
}
unsafe {
match index {
GCS_COMPSTR => get_string(self.himc, GCS_COMPSTR).map(CompositionString::CompStr),
GCS_COMPATTR => get_attrs(self.himc).map(CompositionString::CompAttr),
GCS_COMPCLAUSE => get_clause(self.himc).map(CompositionString::CompClause),
GCS_RESULTSTR => {
get_string(self.himc, GCS_RESULTSTR).map(CompositionString::ResultStr)
}
_ => None,
}
}
}
pub fn get_candidate_list(&self) -> Option<CandidateList> {
unsafe {
let size = ImmGetCandidateListW(self.himc, 0, None) as usize;
if size == 0 {
return None;
}
let mut buf: Vec<u8> = Vec::new();
buf.resize(size, 0);
let ret = ImmGetCandidateListW(self.himc, 0, Some(&mut buf));
if ret == 0 {
return None;
}
let obj = &*(buf.as_ptr() as *const CANDIDATELIST);
let mut list: Vec<String> = Vec::with_capacity(obj.dwCount as usize);
for i in 0..(obj.dwCount as usize) {
let offset =
std::slice::from_raw_parts(&obj.dwOffset as *const u32, obj.dwCount as usize);
let p = buf.as_ptr().offset(offset[i] as isize) as *const u16;
let len = (0..isize::MAX).position(|i| *p.offset(i) == 0).unwrap();
let slice = std::slice::from_raw_parts(p, len);
list.push(String::from_utf16_lossy(slice));
}
Some(CandidateList::new(list, obj.dwSelection as usize))
}
}
pub fn get_cursor_position(&self) -> usize {
unsafe { ImmGetCompositionStringW(self.himc, GCS_CURSORPOS, None) as usize }
}
}
impl Drop for Imc {
fn drop(&mut self) {
unsafe {
ImmReleaseContext(self.hwnd, self.himc);
}
}
}