use teksilo_core::window::SoftKeyboardSupport;
use crate::pointer_backend::PlatformKind;
pub const fn support_for(platform: PlatformKind) -> SoftKeyboardSupport {
match platform {
PlatformKind::Windows => SoftKeyboardSupport::Explicit,
PlatformKind::MacOs | PlatformKind::Unix => SoftKeyboardSupport::None,
}
}
pub const fn support() -> SoftKeyboardSupport {
support_for(PlatformKind::HOST)
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum SoftKeyboardAction {
Nothing,
Ask(bool),
}
pub const fn resolve(support: SoftKeyboardSupport, want_visible: bool) -> SoftKeyboardAction {
match support {
SoftKeyboardSupport::None | SoftKeyboardSupport::ViaAccessibility => {
SoftKeyboardAction::Nothing
}
SoftKeyboardSupport::Explicit => SoftKeyboardAction::Ask(want_visible),
_ => SoftKeyboardAction::Nothing,
}
}
pub const fn should_toggle(currently_visible: bool, want_visible: bool) -> bool {
currently_visible != want_visible
}
pub fn set_visible(window: &winit::window::Window, visible: bool) -> bool {
#[cfg(target_os = "windows")]
{
windows_impl::set_visible(window, visible)
}
#[cfg(not(target_os = "windows"))]
{
let _ = (window, visible);
false
}
}
pub fn keyboard_screen_rect() -> Option<(i32, i32, i32, i32)> {
#[cfg(target_os = "windows")]
{
windows_impl::keyboard_screen_rect()
}
#[cfg(not(target_os = "windows"))]
{
None
}
}
#[cfg(target_os = "windows")]
#[allow(non_snake_case)]
mod windows_impl {
use windows::Win32::Foundation::HWND;
use windows::Win32::System::Com::{
CLSCTX_INPROC_HANDLER, CLSCTX_LOCAL_SERVER, COINIT_APARTMENTTHREADED, CoCreateInstance,
CoInitializeEx,
};
use windows::Win32::UI::WindowsAndMessaging::{
FindWindowW, GetDesktopWindow, GetWindowRect, IsWindowVisible,
};
use windows::core::{GUID, HRESULT, IUnknown, IUnknown_Vtbl, interface, w};
const CLSID_UI_HOST_NO_LAUNCH: GUID = GUID::from_u128(0x4ce576fa_83dc_4f88_951c_9d0782b4e376);
fn keyboard_window() -> Option<HWND> {
unsafe {
if let Ok(hwnd) = FindWindowW(w!("IPTip_Main_Window"), None)
&& !hwnd.is_invalid()
{
return Some(hwnd);
}
if let Ok(hwnd) = FindWindowW(
w!("ApplicationFrameWindow"),
w!("Microsoft Text Input Application"),
) && !hwnd.is_invalid()
{
return Some(hwnd);
}
}
None
}
fn is_visible() -> bool {
keyboard_window().is_some_and(|hwnd| unsafe { IsWindowVisible(hwnd) }.as_bool())
}
#[interface("37c994e7-432b-4834-a2f7-dce1f13b834b")]
unsafe trait ITipInvocation: IUnknown {
fn Toggle(&self, hwnd: HWND) -> HRESULT;
}
pub(super) fn set_visible(_window: &winit::window::Window, visible: bool) -> bool {
if !super::should_toggle(is_visible(), visible) {
return false;
}
unsafe {
let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED);
let Ok(tip) = CoCreateInstance::<_, ITipInvocation>(
&CLSID_UI_HOST_NO_LAUNCH,
None,
CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER,
) else {
return false;
};
tip.Toggle(GetDesktopWindow()).is_ok()
}
}
pub(super) fn keyboard_screen_rect() -> Option<(i32, i32, i32, i32)> {
let hwnd = keyboard_window()?;
if !unsafe { IsWindowVisible(hwnd) }.as_bool() {
return None;
}
let mut rect = windows::Win32::Foundation::RECT::default();
unsafe { GetWindowRect(hwnd, &mut rect) }.ok()?;
Some((rect.left, rect.top, rect.right, rect.bottom))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_platform_row_is_stated() {
assert_eq!(
support_for(PlatformKind::Windows),
SoftKeyboardSupport::Explicit
);
assert_eq!(support_for(PlatformKind::MacOs), SoftKeyboardSupport::None);
assert_eq!(support_for(PlatformKind::Unix), SoftKeyboardSupport::None);
}
#[test]
fn a_request_never_reaches_the_ime_channel() {
assert_eq!(
resolve(SoftKeyboardSupport::ViaAccessibility, true),
SoftKeyboardAction::Nothing
);
assert_eq!(
resolve(SoftKeyboardSupport::ViaAccessibility, false),
SoftKeyboardAction::Nothing
);
assert_eq!(
resolve(SoftKeyboardSupport::None, true),
SoftKeyboardAction::Nothing
);
assert_eq!(
resolve(SoftKeyboardSupport::Explicit, true),
SoftKeyboardAction::Ask(true)
);
assert_eq!(
resolve(SoftKeyboardSupport::Explicit, false),
SoftKeyboardAction::Ask(false)
);
}
#[test]
fn a_toggle_only_control_is_poked_only_when_the_state_is_wrong() {
assert!(should_toggle(false, true), "hidden, asked to show");
assert!(should_toggle(true, false), "shown, asked to hide");
assert!(!should_toggle(true, true), "already shown");
assert!(!should_toggle(false, false), "already hidden");
}
}