witas 0.11.2

An asynchronous window library in Rust for Windows
Documentation
//! An IME composition string and a candidate list.

use crate::utility::*;
use crate::*;
use windows::Win32::{
    Foundation::{HWND, POINT, RECT},
    Globalization::*,
    UI::Input::Ime::*,
};

pub(crate) 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 }
        }
    }

    pub fn enable(&self) {
        unsafe {
            ImmAssociateContextEx(self.hwnd, self.himc, IACE_CHILDREN);
        }
    }

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

#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Clause {
    pub range: std::ops::Range<usize>,
    pub target: bool,
}

pub(crate) struct Imc {
    hwnd: HWND,
    himc: HIMC,
}

impl Imc {
    pub fn get(hwnd: HWND) -> Self {
        let himc = unsafe { ImmGetContext(hwnd) };
        Self { hwnd, himc }
    }

    pub fn set_candidate_window_position(
        &self,
        position: impl ToPhysical<i32, Output<i32> = PhysicalPosition<i32>>,
        enable_exclude_rect: bool,
    ) {
        let position = position.to_physical(get_dpi_for_window(self.hwnd) as _);
        let position = POINT {
            x: position.x,
            y: position.y,
        };
        let form = CANDIDATEFORM {
            dwStyle: CFS_CANDIDATEPOS,
            dwIndex: 0,
            ptCurrentPos: position,
            ..Default::default()
        };
        unsafe {
            ImmSetCandidateWindow(self.himc, &form);
        }
        if !enable_exclude_rect {
            let form = CANDIDATEFORM {
                dwStyle: CFS_EXCLUDE,
                dwIndex: 0,
                ptCurrentPos: position,
                rcArea: RECT {
                    left: position.x,
                    top: position.y,
                    right: position.x,
                    bottom: position.y,
                },
            };
            unsafe {
                ImmSetCandidateWindow(self.himc, &form);
            }
        }
    }

    pub fn get_composition_string(&self) -> Option<String> {
        unsafe {
            let byte_len = ImmGetCompositionStringW(self.himc, GCS_COMPSTR, None, 0);
            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![0; len];
            ImmGetCompositionStringW(
                self.himc,
                GCS_COMPSTR,
                Some(buf.as_mut_ptr() as _),
                byte_len as _,
            );
            let s = String::from_utf16_lossy(&buf);
            (!s.is_empty()).then_some(s)
        }
    }

    pub fn get_composition_clauses(&self) -> Option<Vec<Clause>> {
        let targets: Vec<bool> = unsafe {
            let byte_len = ImmGetCompositionStringW(self.himc, GCS_COMPATTR, None, 0);
            if byte_len == IMM_ERROR_NODATA || byte_len == IMM_ERROR_GENERAL {
                return None;
            }
            let mut buf = vec![0u8; byte_len as _];
            ImmGetCompositionStringW(
                self.himc,
                GCS_COMPATTR,
                Some(buf.as_mut_ptr() as _),
                byte_len as _,
            );
            buf.into_iter()
                .map(|a| a as u32 == ATTR_TARGET_CONVERTED)
                .collect()
        };
        let clauses: Vec<std::ops::Range<usize>> = unsafe {
            let byte_len = ImmGetCompositionStringW(self.himc, GCS_COMPCLAUSE, None, 0);
            if byte_len == IMM_ERROR_NODATA || byte_len == IMM_ERROR_GENERAL {
                return None;
            }
            let mut buf = vec![0u8; byte_len as _];
            ImmGetCompositionStringW(
                self.himc,
                GCS_COMPCLAUSE,
                Some(buf.as_mut_ptr() as _),
                byte_len as _,
            );
            let buf = std::slice::from_raw_parts(
                buf.as_ptr() as *const u32,
                byte_len as usize / std::mem::size_of::<u32>(),
            );
            buf.windows(2)
                .map(|a| a[0] as usize..a[1] as usize)
                .collect()
        };
        Some(
            clauses
                .into_iter()
                .map(|r| Clause {
                    target: targets[r.start],
                    range: r,
                })
                .collect(),
        )
    }

    pub fn get_composition_result(&self) -> Option<String> {
        unsafe {
            let byte_len = ImmGetCompositionStringW(self.himc, GCS_RESULTSTR, None, 0);
            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![0; len];
            ImmGetCompositionStringW(
                self.himc,
                GCS_RESULTSTR,
                Some(buf.as_mut_ptr() as _),
                byte_len as _,
            );
            let s = String::from_utf16_lossy(&buf);
            (!s.is_empty()).then_some(s)
        }
    }

    pub fn get_cursor_position(&self) -> usize {
        unsafe { ImmGetCompositionStringW(self.himc, GCS_CURSORPOS, None, 0) as usize }
    }
}

impl Drop for Imc {
    fn drop(&mut self) {
        unsafe {
            ImmReleaseContext(self.hwnd, self.himc);
        }
    }
}