Why ImmSetConversionStatus return 0 for GetForegroundWindow even it's injected to the foregound window's process?

62 Views Asked by At

Background

I try to develop a better im-select for my VSCode Vim mode, which only switches the conversion status of the CJK IME on Windows so that we don't need another IME or keyboard anymore. To accomplish this, there is two part for the whole process:

  • a dll to be injected into the VSCode's process. This dll is to acquire HIMC (through ImmGetContext of the VSCode process that is always the foreground window (through GetForegroundWindow)
  • an executable to inject the dll above and call back the dll's functions through dll-syringe

Functions to operate Windows' IME are provided by windows-rs.

Complete code is here, minimal demo is below:

// ime-convert/src/main.rs
fn main() {
    let cmd = Cmd::from_args();

    // Inject or findout the dll.
    // Get the foreground window and its process id(`pid`),
    let h_wnd: HWND = unsafe { GetForegroundWindow() };
    let mut pid = 0;
    let _thead_id = unsafe { GetWindowThreadProcessId(h_wnd, Some(&mut pid)) };
    let process = OwnedProcess::from_pid(pid)
        .expect("Get the process of the foreground window failed!");
    let syringe = Syringe::for_process(process);
    let injected_payload = syringe.find_or_inject("target/debug/im_conversion.dll")
        .expect("injection failed");

    let remote_backup = unsafe {
        syringe.get_raw_procedure::<extern "system" fn() -> u32>(injected_payload, "backup")
            .unwrap().unwrap()
    };

    // Send message.
    match cmd {
        Cmd::Backup => {
            let res = remote_backup.call().unwrap();
            println!("remote backup result: {res:?}");
        },

        // ...
    }

}
// im-conversions/src/lib.rs
/// Backup the IME conversion and set the convertion to `IME_CMODE_ALPHANUMERIC`
#[no_mangle]
#[allow(unused)]
extern "system" fn backup() -> u32 {
    // the ForegroundWindow of a process may change, so we have to
    // get the window first each time we are about to backup/recover
    // the IME conversion.
    let hwnd: HWND = unsafe { GetForegroundWindow() };
    let himc: HIMC = unsafe { ImmGetContext(hwnd) };

    let mut conversion = IME_CONVERSION_MODE::default();

    let mut result_code = 0;

    // backup the conversion into a static CONVERIONS map.

    // DEBUG1: confirm if the conversion is acquired
    // result_code = conversion.0;

    let set_res: BOOL = unsafe {
        ImmSetConversionStatus(
            himc,
            IME_CONVERSION_MODE::default(),
            IME_SENTENCE_MODE::default(),
        )
    };

    // DEBUG2: confirm if ImmSetConversionStatus return TRUE 
    // if set_res == FALSE {
    //     let res = unsafe { GetLastError() };
    //     if let Err(err) = res {
    //         result_code = err.code().0 as u32;
    //     } else {
    //         result_code = 2;
    //     }
    // }


    let release_res: BOOL = unsafe {
        ImmReleaseContext(hwnd, himc)
    };

    if release_res == FALSE {
        // todo!("failure for ImmReleaseContext need to be handled!");
        result_code = 3;
    }

    return result_code;
}

As is shown above, when DEBUG1 is uncommented, the conversion of himc is successfully returned which means both hwnd and himc are valid values. When DEBUG1 is commented and DEBUG2 is uncommented, the result_code is 2, which means even though the function ImmSetConversionStatus is run in the target process, it still can't set the IME's conversion status. Why? And how to set it up?

0

There are 0 best solutions below