wita 0.3.2

A window library in Rust for Windows
Documentation
use super::*;
use crate::ime;

pub const GCS_COMPSTR: DWORD = 0x0008;
pub const GCS_COMPATTR: DWORD = 0x0010;
pub const GCS_RESULTSTR: DWORD = 0x0800;
pub const ISC_SHOWUICOMPOSITIONWINDOW: LPARAM = 0x80000000;
pub const ISC_SHOWUICANDIDATEWINDOW: LPARAM = 0x00000001;
pub const IMM_ERROR_NODATA: LONG = -1;
pub const IMM_ERROR_GENERAL: LONG = -2;
pub const IACE_CHILDREN: DWORD = 0x0001;
pub const IACE_DEFAULT: DWORD = 0x0010;
pub const IACE_IGNORENOCONTEXT: DWORD = 0x0020;
pub const ATTR_INPUT: u8 = 0x00;
pub const ATTR_TARGET_CONVERTED: u8 = 0x01;
pub const ATTR_CONVERTED: u8 = 0x02;
pub const ATTR_TARGET_NOTCONVERTED: u8 = 0x03;
pub const ATTR_INPUT_ERROR: u8 = 0x04;
pub const ATTR_FIXEDCONVERTED: u8 = 0x5;

#[repr(C)]
#[allow(non_snake_case)]
struct CANDIDATEFORM {
    dwIndex: DWORD,
    dwStyle: DWORD,
    ptCurrentPos: POINT,
    rcArea: RECT,
}

#[repr(C)]
#[allow(non_snake_case)]
#[derive(Debug)]
struct CANDIDATELIST {
    dwSize: DWORD,
    dwStyle: DWORD,
    dwCount: DWORD,
    dwSelection: DWORD,
    dwPageStart: DWORD,
    dwPageSize: DWORD,
    dwOffset: [DWORD; 1],
}

extern "system" {
    fn ImmCreateContext() -> HIMC;
    fn ImmDestroyContext(himc: HIMC);
    fn ImmSetCandidateWindow(himc: HIMC, form: *mut CANDIDATEFORM) -> BOOL;
    fn ImmGetCompositionStringW(himc: HIMC, index: DWORD, lpbuf: LPVOID, buflen: DWORD) -> LONG;
    fn ImmGetCandidateListW(
        himc: HIMC,
        index: DWORD,
        list: *mut CANDIDATELIST,
        len: DWORD,
    ) -> DWORD;
    fn ImmAssociateContextEx(hwnd: HWND, himc: HIMC, flag: DWORD) -> BOOL;
}

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

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

    pub fn disable(&self) {
        unsafe {
            ImmAssociateContextEx(self.hwnd, std::ptr::null_mut(), IACE_IGNORENOCONTEXT);
        }
    }
}

impl Drop for ImmContext {
    fn drop(&mut self) {
        unsafe {
            ImmAssociateContextEx(self.hwnd, std::ptr::null_mut(), IACE_DEFAULT);
            ImmDestroyContext(self.himc);
        }
    }
}

pub enum CompositionString {
    CompStr(String),
    CompAttr(Vec<ime::Attribute>),
    ResultStr(String),
}

pub 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_composition_window_position(&self, position: PhysicalPosition<i32>) {
        unsafe {
            let pt = POINT {
                x: position.x,
                y: position.y,
            };
            let mut form = COMPOSITIONFORM {
                dwStyle: CFS_POINT,
                ptCurrentPos: pt,
                rcArea: RECT::default(),
            };
            ImmSetCompositionWindow(self.himc, &mut 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 mut form = CANDIDATEFORM {
                dwStyle: CFS_CANDIDATEPOS,
                dwIndex: 0,
                ptCurrentPos: pt,
                rcArea: RECT::default(),
            };
            ImmSetCandidateWindow(self.himc, &mut form);
            if !enable_exclude_rect {
                let mut 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, &mut form);
            }
        }
    }

    pub fn get_composition_string(&self, index: DWORD) -> Option<CompositionString> {
        unsafe fn get_string(himc: HIMC, index: DWORD) -> Option<String> {
            let byte_len = ImmGetCompositionStringW(himc, index, std::ptr::null_mut(), 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::with_capacity(len);
            buf.set_len(len);
            ImmGetCompositionStringW(himc, index, buf.as_mut_ptr() as *mut _, byte_len as DWORD);
            let s = String::from_utf16_lossy(&buf);
            if s == "" {
                None
            } else {
                Some(s)
            }
        }

        unsafe fn get_attrs(himc: HIMC) -> Option<Vec<ime::Attribute>> {
            let byte_len = ImmGetCompositionStringW(himc, GCS_COMPATTR, std::ptr::null_mut(), 0);
            if byte_len == IMM_ERROR_NODATA || byte_len == IMM_ERROR_GENERAL {
                return None;
            }
            let len = byte_len as usize;
            let mut buf: Vec<u8> = Vec::with_capacity(len);
            buf.set_len(len);
            ImmGetCompositionStringW(
                himc,
                GCS_COMPATTR,
                buf.as_mut_ptr() as *mut _,
                byte_len as DWORD,
            );
            Some(
                buf.into_iter()
                    .map(|v| match v {
                        ATTR_INPUT => ime::Attribute::Input,
                        ATTR_TARGET_CONVERTED => ime::Attribute::TargetConverted,
                        ATTR_CONVERTED => ime::Attribute::Converted,
                        ATTR_TARGET_NOTCONVERTED => ime::Attribute::TargetNotConverted,
                        ATTR_INPUT_ERROR => ime::Attribute::Error,
                        ATTR_FIXEDCONVERTED => ime::Attribute::FixedConverted,
                        _ => unreachable!(),
                    })
                    .collect::<Vec<_>>(),
            )
        }

        unsafe {
            match index {
                GCS_COMPSTR => {
                    get_string(self.himc, GCS_COMPSTR).map(|s| CompositionString::CompStr(s))
                }
                GCS_COMPATTR => get_attrs(self.himc).map(|v| CompositionString::CompAttr(v)),
                GCS_RESULTSTR => {
                    get_string(self.himc, GCS_RESULTSTR).map(|s| CompositionString::ResultStr(s))
                }
                _ => None,
            }
        }
    }

    pub fn get_candidate_list(&self) -> Option<ime::CandidateList> {
        unsafe {
            let size = ImmGetCandidateListW(self.himc, 0, std::ptr::null_mut(), 0) as usize;
            if size == 0 {
                return None;
            }
            let mut buf: Vec<u8> = Vec::with_capacity(size);
            buf.set_len(size);
            let ret = ImmGetCandidateListW(self.himc, 0, buf.as_mut_ptr() as *mut _, size as DWORD);
            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 DWORD, 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(ime::CandidateList::new(list, obj.dwSelection as usize))
        }
    }
}

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