diff --git a/Cargo.lock b/Cargo.lock index 9c2ed26a..2f62c488 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1158,7 +1158,7 @@ checksum = "652aa0009b1406e40114685c96ea2c01069e1c035ad6c340999fa08213fad4c5" dependencies = [ "fontconfig-parser", "log", - "memmap2", + "memmap2 0.5.8", "ttf-parser 0.18.1", ] @@ -1766,6 +1766,7 @@ dependencies = [ "pico-args", "wayland-client", "wayland-protocols", + "xkbcommon", "zwp-virtual-keyboard", ] @@ -1826,9 +1827,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libdbus-sys" @@ -1908,6 +1909,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memmap2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -2918,7 +2928,7 @@ dependencies = [ "dlib", "lazy_static", "log", - "memmap2", + "memmap2 0.5.8", "nix 0.24.3", "pkg-config", "wayland-client", @@ -3818,6 +3828,23 @@ dependencies = [ "bitflags", ] +[[package]] +name = "xkbcommon" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e" +dependencies = [ + "libc", + "memmap2 0.8.0", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" + [[package]] name = "xml-rs" version = "0.8.4" diff --git a/README.ko.md b/README.ko.md index d699603b..142f0f2f 100644 --- a/README.ko.md +++ b/README.ko.md @@ -151,6 +151,18 @@ kime은 kime 데몬을 위한 kime.desktop 파일을 /etc/xdg/autostart에 설 혹시 `i3`나 `sway`처럼 `시작 프로그램`을 지원하지 않는다면 해당 WM의 설정파일에서 `kime` 혹은 원하시는 서버 커맨드를 실행해주세요 +### KDE Plasma Wayland + +시스템 설정 > 하드웨어 > 입력 장치 > 가상 키보드에서 `kime 데몬`을 선택해야 합니다. +이후에 로그아웃을 하는 것을 권장합니다. + +### Weston +`~/.config/weston.ini`에 해당 내용이 있어야 합니다. +``` +[input-method] +path=/usr/bin/kime +``` + ### Configuration 자세한 옵션은 [CONFIGURATION.md](docs/CONFIGURATION.ko.md)를 참고하세요. @@ -170,6 +182,7 @@ kime은 kime 데몬을 위한 kime.desktop 파일을 /etc/xdg/autostart에 설 * xcb (candidate) * fontconfig (xim) * freetype (xim) +* libxkbcommon (wayland) ### 빌드타임 종속성 (바이너리 실행 시엔 필요 없습니다) @@ -190,3 +203,4 @@ kime은 kime 데몬을 위한 kime.desktop 파일을 /etc/xdg/autostart에 설 * xcb * fontconfig * freetype +* libxkbcommon diff --git a/README.md b/README.md index d33dcbf3..0fe425d4 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,19 @@ if you use X, append above lines to file `~/.xprofile` kime.desktop file is installed in /etc/xdg/autostart when installing kime. +### KDE Plasma Wayland + +It is required to select `kime daemon` under System Settings > Hardware > Input Devices > Virtual Keyboard. +A logout is recommended afterwards. + +### Weston + +It is required to have the following lines in `~/.config/weston.ini` +``` +[input-method] +path=/usr/bin/kime +``` + ### Configuration Read [CONFIGURATION.md](docs/CONFIGURATION.md) for detail options. @@ -169,6 +182,7 @@ These dependencies are optional depending on your environments. For example, qt6 * xcb (candidate) * fontconfig (xim) * freetype (xim) +* libxkbcommon (wayland) ### Build time (you don't need this on running compiled binary) @@ -189,3 +203,4 @@ These dependencies are optional depending on your environments. For example, qt6 * xcb * fontconfig * freetype +* libxkbcommon diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 36c50b15..47a11978 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,7 @@ * Fix KDE autostart [#576](https://github.com/Riey/kime/issues/576) * Add unicode prime symbols to math mode. (prime, double prime, triple prime, quadruple prime) * Fix to work on wlroots>=0.17.1 (Sway 1.9) [#664](https://github.com/Riey/kime/issues/664) +* Add wayland zwp_input_method_v1 support **[@Jhyub]** ## 3.0.2 diff --git a/res/kime.desktop b/res/kime.desktop index 2f8afc23..68f9e282 100644 --- a/res/kime.desktop +++ b/res/kime.desktop @@ -11,4 +11,5 @@ X-DBUS-StartupType=Unique X-GNOME-AutoRestart=false X-GNOME-Autostart-Notify=false X-KDE-StartupNotify=false +X-KDE-Wayland-VirtualKeyboard=true Icon=kime-hangul-black diff --git a/scripts/install.sh b/scripts/install.sh index 42698b2f..ef68352d 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -37,6 +37,10 @@ if [ -z "$KIME_AUTOSTART_DIR" ]; then KIME_AUTOSTART_DIR=etc/xdg/autostart fi +if [ -z "$KIME_DESKTOP_ENTRY_DIR" ]; then + KIME_DESKTOP_ENTRY_DIR=usr/share/applications +fi + if [ -z "$KIME_LIB_DIR" ]; then KIME_LIB_DIR=usr/lib fi @@ -95,6 +99,7 @@ if [ "${KIME_INSTALL_DOC}" -eq "1" ]; then fi install -Dm644 $KIME_OUT/*.desktop -t "$PREFIX/$KIME_AUTOSTART_DIR" +install -Dm644 $KIME_OUT/*.desktop -t "$PREFIX/$KIME_DESKTOP_ENTRY_DIR" install -Dm755 $KIME_OUT/kime-xdg-autostart -t "$PREFIX/$KIME_BIN_DIR" install -Dm644 $KIME_OUT/icons/64x64/*.png -t "$PREFIX/$KIME_ICON_DIR/hicolor/64x64/apps" install -Dm755 $KIME_OUT/libkime_engine.so -t "$PREFIX/$KIME_LIB_DIR" diff --git a/src/frontends/wayland/Cargo.toml b/src/frontends/wayland/Cargo.toml index 289e5125..3b65cd5b 100644 --- a/src/frontends/wayland/Cargo.toml +++ b/src/frontends/wayland/Cargo.toml @@ -12,6 +12,7 @@ kime-version = { path = "../../tools/version" } wayland-client = "0.29" wayland-protocols = { version = "0.29", features = ["client", "unstable_protocols"] } zwp-virtual-keyboard = "0.2.7" +xkbcommon = { version = "0.7.0", features = ["wayland"] } libc = "0.2.82" log = "0.4.13" diff --git a/src/frontends/wayland/src/input_method_v1.rs b/src/frontends/wayland/src/input_method_v1.rs new file mode 100644 index 00000000..db37ecd9 --- /dev/null +++ b/src/frontends/wayland/src/input_method_v1.rs @@ -0,0 +1,492 @@ +use std::error::Error; +use std::os::fd::{FromRawFd, OwnedFd}; +use std::time::{Duration, Instant}; + +use wayland_client::{ + event_enum, + protocol::wl_keyboard::{Event as KeyEvent, KeyState, WlKeyboard, REQ_RELEASE_SINCE}, + DispatchData, Display, EventQueue, Filter, GlobalManager, Main, +}; + +use wayland_protocols::unstable::input_method::v1::client::{ + zwp_input_method_context_v1::{Event as ImCtxEvent, ZwpInputMethodContextV1}, + zwp_input_method_v1::{Event as ImEvent, ZwpInputMethodV1}, +}; + +use kime_engine_cffi::*; + +use mio::{unix::SourceFd, Events as MioEvents, Interest, Poll, Token}; +use mio_timerfd::{ClockId, TimerFd}; +use wayland_client::protocol::wl_keyboard::KeymapFormat; +use xkbcommon::xkb::{ + Context, Keycode, Keymap, CONTEXT_NO_FLAGS, KEYMAP_COMPILE_NO_FLAGS, KEYMAP_FORMAT_TEXT_V1, +}; + +use crate::{PressState, RepeatInfo}; + +event_enum! { + Events | + Im => ZwpInputMethodV1, + ImCtx => ZwpInputMethodContextV1, + Key => WlKeyboard +} + +const ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_NONE: u32 = 1; +const ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_UNDERLINE: u32 = 5; + +struct KimeContext { + config: Config, + engine: InputEngine, + mod_state: ModifierState, + im_ctx: Option>, + keyboard: Option>, + numlock: bool, + engine_ready: bool, + keymap: Option, + grab_activate: bool, //? + serial: u32, + timer: TimerFd, + /// `None` if `KimeContext` have never received a `RepeatInfo` or repeat is disabled (i.e. rate + /// is zero). `Some(..)` if `RepeatInfo` is known and kime-wayland started tracking the press + /// state of keys. + repeat_state: Option<(RepeatInfo, PressState)>, +} + +impl Drop for KimeContext { + fn drop(&mut self) { + if let Some(im_ctx) = &mut self.im_ctx { + im_ctx.destroy(); + } + } +} + +impl KimeContext { + pub fn new(timer: TimerFd) -> Self { + let config = Config::load(); + Self { + engine: InputEngine::new(&config), + config, + mod_state: 0, + serial: 0, + numlock: false, + engine_ready: true, + keymap: None, + grab_activate: false, + im_ctx: None, + keyboard: None, + timer, + // Clients with older protocols might not provide repeat info. + // Therefore a default value is required. + repeat_state: Some(( + RepeatInfo { + rate: 20, + delay: 400, + }, + PressState::NotPressing, + )), + } + } + + pub fn new_data<'a>(data: &'a mut DispatchData) -> &'a mut Self { + data.get::().unwrap() + } + + fn process_input_result(&mut self, ret: InputResult) -> bool { + if ret & InputResult_NOT_READY != 0 { + self.engine_ready = false; + } + + if ret & InputResult_LANGUAGE_CHANGED != 0 { + self.engine.update_layout_state(); + } + + if ret & InputResult_HAS_COMMIT != 0 { + self.commit_string(self.engine.commit_str().into()); + self.engine.clear_commit(); + } + + if ret & InputResult_HAS_PREEDIT != 0 { + let preedit = self.engine.preedit_str().into(); + self.preedit(preedit); + } else { + self.clear_preedit(); + } + + ret & InputResult_CONSUMED == 0 + } + + fn commit_string(&mut self, s: String) { + if !s.is_empty() { + if let Some(im_ctx) = &mut self.im_ctx { + im_ctx.commit_string(self.serial, s); + } + } + } + + fn clear_preedit(&mut self) { + if let Some(im_ctx) = &mut self.im_ctx { + im_ctx.preedit_cursor(0); + im_ctx.preedit_styling(0, 0, ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_NONE); + im_ctx.preedit_string(self.serial, String::new(), String::new()); + } + } + + fn preedit(&mut self, s: String) { + if let Some(im_ctx) = &mut self.im_ctx { + im_ctx.preedit_cursor(s.len() as _); + im_ctx.preedit_styling(0, s.len() as _, ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_UNDERLINE); + im_ctx.preedit_string(self.serial, s.clone(), s); + } + } + + fn key(&mut self, time: u32, key: u32, state: KeyState) { + if let Some(im_ctx) = &mut self.im_ctx { + im_ctx.key(self.serial, time, key, state as _); + } + } + + pub fn handle_im_ctx_ev(&mut self, ev: ImCtxEvent) { + match ev { + ImCtxEvent::CommitState { serial } => { + self.serial = serial; + } + _ => {} + } + } + + pub fn handle_key_ev(&mut self, ev: KeyEvent) { + match ev { + KeyEvent::Keymap { format, fd, size } => { + if let KeymapFormat::XkbV1 = format { + unsafe { + self.keymap = Keymap::new_from_fd( + &Context::new(CONTEXT_NO_FLAGS), + OwnedFd::from_raw_fd(fd), + size as usize, + KEYMAP_FORMAT_TEXT_V1, + KEYMAP_COMPILE_NO_FLAGS, + ) + .unwrap_or(None); + } + } else { + unsafe { + libc::close(fd); + } + } + } + KeyEvent::Key { + time, key, state, .. + } => { + if state == KeyState::Pressed { + if self.grab_activate { + let ret = self.engine.press_key( + &self.config, + (key + 8) as u16, + self.numlock, + self.mod_state, + ); + + let bypassed = self.process_input_result(ret); + + if bypassed { + self.key(time, key, state); + } else { + // If the key was not bypassed by IME, key repeat should be handled by the + // IME. Start waiting for the key hold timer event. + match self.repeat_state { + Some((info, ref mut press_state)) + if !press_state.is_pressing(key) => + { + let duration = Duration::from_millis(info.delay as u64); + self.timer.set_timeout(&duration).unwrap(); + *press_state = PressState::Pressing { + pressed_at: Instant::now(), + is_repeating: false, + key, + wayland_time: time, + } + } + _ => {} + } + } + } else { + self.key(time, key, state); + } + } else { + if let Some((.., ref mut press_state)) = self.repeat_state { + if press_state.is_pressing(key) { + self.timer.disarm().unwrap(); + *press_state = PressState::NotPressing; + } + } + + self.key(time, key, state); + } + } + KeyEvent::Modifiers { + mods_depressed, + mods_latched, + mods_locked, + group, + .. + } => { + self.mod_state = 0; + if mods_depressed & 0x1 != 0 { + self.mod_state |= ModifierState_SHIFT; + } + if mods_depressed & 0x4 != 0 { + self.mod_state |= ModifierState_CONTROL; + } + if mods_depressed & 0x8 != 0 { + self.mod_state |= ModifierState_ALT; + } + if mods_depressed & 0x40 != 0 { + self.mod_state |= ModifierState_SUPER; + } + + self.numlock = mods_depressed & 0x10 != 0; + + if let Some(im_ctx) = &mut self.im_ctx { + im_ctx.modifiers( + self.serial, + mods_depressed, + mods_latched, + mods_locked, + group, + ); + } + } + KeyEvent::RepeatInfo { rate, delay } => { + self.repeat_state = if rate == 0 { + None + } else { + let info = RepeatInfo { rate, delay }; + let press_state = self.repeat_state.map(|pair| pair.1); + Some((info, press_state.unwrap_or(PressState::NotPressing))) + } + } + _ => {} + } + } + + pub fn handle_timer_ev(&mut self) -> std::io::Result<()> { + // Read timer, this MUST be called or timer will be broken + let overrun_count = self.timer.read()?; + if overrun_count != 1 { + log::warn!("Some timer events were not properly handled!"); + } + + if let Some(( + info, + PressState::Pressing { + pressed_at, + ref mut is_repeating, + key, + wayland_time, + }, + )) = self.repeat_state + { + if !*is_repeating { + if self + .keymap + .as_ref() + .map_or_else(|| true, |x| x.key_repeats(Keycode::new(key + 8))) + { + // Start repeat + log::trace!("Start repeating {}", key); + let interval = &Duration::from_secs_f64(1.0 / info.rate as f64); + self.timer.set_timeout_interval(interval)?; + *is_repeating = true; + } + } + + let ev = KeyEvent::Key { + serial: self.serial, // Is this fine? + time: wayland_time + pressed_at.elapsed().as_millis() as u32, + key, + state: KeyState::Pressed, + }; + self.handle_key_ev(ev); + } else { + log::warn!("Received timer event when it has never received RepeatInfo."); + } + Ok(()) + } + + pub fn activate(&mut self, im_ctx: Main, keyboard: Main) { + self.engine.update_layout_state(); + if !self.engine_ready { + if self.engine.check_ready() { + let ret = self.engine.end_ready(); + self.process_input_result(ret); + self.engine_ready = true; + } + } + self.grab_activate = true; + + let filter = Filter::new(|ev, _filter, mut data| { + let ctx = KimeContext::new_data(&mut data); + + match ev { + Events::ImCtx { event, .. } => { + ctx.handle_im_ctx_ev(event); + } + Events::Key { event, .. } => { + ctx.handle_key_ev(event); + } + _ => {} + } + }); + + im_ctx.assign(filter.clone()); + keyboard.assign(filter); + + self.im_ctx = Some(im_ctx); + self.keyboard = Some(keyboard); + } + + pub fn deactivate(&mut self) { + // Focus lost, reset states + if self.engine_ready { + self.engine.reset(); + } + self.grab_activate = false; + + // Input deactivated, stop repeating + self.timer.disarm().unwrap(); + if let Some((_, ref mut press_state)) = self.repeat_state { + *press_state = PressState::NotPressing + } + + if let Some(im_ctx) = &mut self.im_ctx { + im_ctx.destroy(); + } + self.im_ctx = None; + + if let Some(keyboard) = &mut self.keyboard { + if keyboard.as_ref().version() >= REQ_RELEASE_SINCE { + keyboard.release(); + } + } + self.keyboard = None; + } +} + +pub fn run( + display: &Display, + event_queue: &mut EventQueue, + globals: &GlobalManager, +) -> Result<(), Box> { + let im_filter = Filter::new(|ev, _filter, mut data| { + let ctx = KimeContext::new_data(&mut data); + match ev { + Events::Im { event, .. } => match event { + ImEvent::Activate { id: im_ctx } => { + let keyboard = im_ctx.grab_keyboard(); + ctx.activate(im_ctx, keyboard); + } + ImEvent::Deactivate { .. } => { + ctx.deactivate(); + } + _ => {} + }, + _ => {} + } + }); + + let im = globals.instantiate_exact::(1)?; + im.assign(im_filter); + + let mut timer = TimerFd::new(ClockId::Monotonic).expect("Initialize timer"); + + let mut poll = Poll::new().expect("Initialize epoll()"); + let registry = poll.registry(); + + const POLL_WAYLAND: Token = Token(0); + registry + .register( + &mut SourceFd(&display.get_connection_fd()), + POLL_WAYLAND, + Interest::READABLE | Interest::WRITABLE, + ) + .expect("Register wayland socket to the epoll()"); + + const POLL_TIMER: Token = Token(1); + registry + .register(&mut timer, POLL_TIMER, Interest::READABLE) + .expect("Register timer to the epoll()"); + + // Initialize kime context + let mut kime_ctx = KimeContext::new(timer); + event_queue + .sync_roundtrip(&mut kime_ctx, |_, _, _| ()) + .unwrap(); + + log::info!("Server init success!"); + + // Non-blocking event loop + // + // Reference: + // https://docs.rs/wayland-client/0.28.3/wayland_client/struct.EventQueue.html + let mut events = MioEvents::with_capacity(1024); + let stop_reason = 'main: loop { + use std::io::ErrorKind; + + // Sleep until next event + if let Err(e) = poll.poll(&mut events, None) { + // Should retry on EINTR + // + // Reference: + // https://www.gnu.org/software/libc/manual/html_node/Interrupted-Primitives.html + if e.kind() == ErrorKind::Interrupted { + continue; + } + break Err(e); + } + + for event in &events { + match event.token() { + POLL_WAYLAND => {} + POLL_TIMER => { + if let Err(e) = kime_ctx.handle_timer_ev() { + break 'main Err(e); + } + } + _ => unreachable!(), + } + } + + // Perform read() only when it's ready, returns None when there're already pending events + if let Some(guard) = event_queue.prepare_read() { + if let Err(e) = guard.read_events() { + // EWOULDBLOCK here means there's no new messages to read + if e.kind() != ErrorKind::WouldBlock { + break Err(e); + } + } + } + + if let Err(e) = event_queue.dispatch_pending(&mut kime_ctx, |_, _, _| {}) { + break Err(e); + } + + // Flush pending writes + if let Err(e) = display.flush() { + // EWOULDBLOCK here means there're so many to write, retry later + if e.kind() != ErrorKind::WouldBlock { + break Err(e); + } + } + }; + + match stop_reason { + Ok(()) => { + log::info!("Server finished gracefully"); + Ok(()) + } + Err(e) => { + log::error!("Server aborted due to IO Error: {}", e); + Err(Box::from(e)) + } + } +} diff --git a/src/frontends/wayland/src/input_method_v2.rs b/src/frontends/wayland/src/input_method_v2.rs new file mode 100644 index 00000000..1916b973 --- /dev/null +++ b/src/frontends/wayland/src/input_method_v2.rs @@ -0,0 +1,471 @@ +use std::error::Error; +use std::time::{Duration, Instant}; + +use wayland_client::{ + event_enum, + protocol::{wl_keyboard::KeyState, wl_seat::WlSeat}, + DispatchData, Display, EventQueue, Filter, GlobalManager, Main, +}; + +use wayland_protocols::misc::zwp_input_method_v2::client::{ + zwp_input_method_keyboard_grab_v2::{Event as KeyEvent, ZwpInputMethodKeyboardGrabV2}, + zwp_input_method_manager_v2::ZwpInputMethodManagerV2, + zwp_input_method_v2::{Event as ImEvent, ZwpInputMethodV2}, +}; +use zwp_virtual_keyboard::virtual_keyboard_unstable_v1::{ + zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1, + zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1, +}; + +use kime_engine_cffi::*; + +use mio::{unix::SourceFd, Events as MioEvents, Interest, Poll, Token}; +use mio_timerfd::{ClockId, TimerFd}; + +use crate::{PressState, RepeatInfo}; + +event_enum! { + Events | + Key => ZwpInputMethodKeyboardGrabV2, + Im => ZwpInputMethodV2 +} + +struct InputMethodState { + activate: bool, + deactivate: bool, +} + +impl Default for InputMethodState { + fn default() -> Self { + Self { + activate: false, + deactivate: false, + } + } +} + +struct KimeContext { + config: Config, + engine: InputEngine, + mod_state: ModifierState, + current_state: InputMethodState, + pending_state: InputMethodState, + vk: Main, + im: Main, + grab: Main, + numlock: bool, + engine_ready: bool, + keymap_init: bool, + grab_activate: bool, + serial: u32, + // Have to concern Multi seats? + + // Key repeat contexts + timer: TimerFd, + /// `None` if `KimeContext` have never received a `RepeatInfo` or repeat is disabled (i.e. rate + /// is zero). `Some(..)` if `RepeatInfo` is known and kime-wayland started tracking the press + /// state of keys. + repeat_state: Option<(RepeatInfo, PressState)>, +} + +impl Drop for KimeContext { + fn drop(&mut self) { + self.grab.release(); + self.vk.destroy(); + self.im.destroy(); + } +} + +impl KimeContext { + pub fn new( + vk: Main, + im: Main, + grab: Main, + timer: TimerFd, + ) -> Self { + let config = Config::load(); + Self { + engine: InputEngine::new(&config), + config, + mod_state: 0, + current_state: InputMethodState::default(), + pending_state: InputMethodState::default(), + serial: 0, + numlock: false, + engine_ready: true, + keymap_init: false, + grab_activate: false, + vk, + im, + grab, + timer, + repeat_state: None, + } + } + + pub fn new_data<'a>(data: &'a mut DispatchData) -> &'a mut Self { + data.get::().unwrap() + } + + fn process_input_result(&mut self, ret: InputResult) -> bool { + if ret & InputResult_NOT_READY != 0 { + self.engine_ready = false; + } + + if ret & InputResult_LANGUAGE_CHANGED != 0 { + self.engine.update_layout_state(); + } + + if ret & InputResult_HAS_PREEDIT != 0 { + let preedit = self.engine.preedit_str().into(); + self.preedit(preedit); + } else { + self.clear_preedit(); + } + + if ret & InputResult_HAS_COMMIT != 0 { + self.commit_string(self.engine.commit_str().into()); + self.engine.clear_commit(); + } + + self.commit(); + + ret & InputResult_CONSUMED == 0 + } + + fn commit(&mut self) { + self.im.commit(self.serial); + } + + fn commit_string(&mut self, s: String) { + if !s.is_empty() { + self.im.commit_string(s); + } + } + + fn clear_preedit(&mut self) { + self.im.set_preedit_string(String::new(), -1, -1); + } + + fn preedit(&mut self, s: String) { + let len = s.len(); + self.im.set_preedit_string(s, 0, len as _); + } + + pub fn handle_im_ev(&mut self, ev: ImEvent) { + match ev { + ImEvent::Activate => { + self.pending_state.activate = true; + } + ImEvent::Deactivate => { + self.pending_state.deactivate = true; + } + ImEvent::Unavailable => { + log::error!("Receive Unavailable event is another server already running?"); + panic!("Unavailable") + } + ImEvent::Done => { + self.serial += 1; + if !self.current_state.activate && self.pending_state.activate { + self.engine.update_layout_state(); + if !self.engine_ready { + if self.engine.check_ready() { + let ret = self.engine.end_ready(); + self.process_input_result(ret); + self.engine_ready = true; + } + } + self.grab_activate = true; + } else if !self.current_state.deactivate && self.pending_state.deactivate { + // Focus lost, reset states + if self.engine_ready { + self.engine.reset(); + } + self.grab_activate = false; + + // Input deactivated, stop repeating + self.timer.disarm().unwrap(); + if let Some((_, ref mut press_state)) = self.repeat_state { + *press_state = PressState::NotPressing + } + } + self.current_state = std::mem::take(&mut self.pending_state); + } + _ => {} + } + } + + pub fn handle_key_ev(&mut self, ev: KeyEvent) { + match ev { + KeyEvent::Keymap { fd, format, size } => { + if !self.keymap_init { + self.vk.keymap(format as _, fd, size); + self.keymap_init = true; + } + unsafe { + libc::close(fd); + } + } + KeyEvent::Key { + state, key, time, .. + } => { + // NOTE: Never read `serial` of KeyEvent. You should rely on serial of KimeContext + if state == KeyState::Pressed { + if self.grab_activate { + let ret = self.engine.press_key( + &self.config, + (key + 8) as u16, + self.numlock, + self.mod_state, + ); + + let bypassed = self.process_input_result(ret); + + if bypassed { + // Bypassed key's repeat will be handled by the clients. + // + // Reference: + // https://github.com/swaywm/sway/pull/4932#issuecomment-774113129 + self.vk.key(time, key, state as _); + } else { + // If the key was not bypassed by IME, key repeat should be handled by the + // IME. Start waiting for the key hold timer event. + match self.repeat_state { + Some((info, ref mut press_state)) + if !press_state.is_pressing(key) => + { + let duration = Duration::from_millis(info.delay as u64); + self.timer.set_timeout(&duration).unwrap(); + *press_state = PressState::Pressing { + pressed_at: Instant::now(), + is_repeating: false, + key, + wayland_time: time, + }; + } + _ => {} + } + } + } else { + // not activated so just skip + self.vk.key(time, key, state as _); + } + } else { + // If user released the last pressed key, clear the timer and state + if let Some((.., ref mut press_state)) = self.repeat_state { + if press_state.is_pressing(key) { + self.timer.disarm().unwrap(); + *press_state = PressState::NotPressing; + } + } + + self.vk.key(time, key, state as _); + } + } + KeyEvent::Modifiers { + mods_depressed, + mods_latched, + mods_locked, + group, + .. + } => { + self.mod_state = 0; + if mods_depressed & 0x1 != 0 { + self.mod_state |= ModifierState_SHIFT; + } + if mods_depressed & 0x4 != 0 { + self.mod_state |= ModifierState_CONTROL; + } + if mods_depressed & 0x8 != 0 { + self.mod_state |= ModifierState_ALT; + } + if mods_depressed & 0x40 != 0 { + self.mod_state |= ModifierState_SUPER; + } + + self.numlock = mods_depressed & 0x10 != 0; + + self.vk + .modifiers(mods_depressed, mods_latched, mods_locked, group); + } + KeyEvent::RepeatInfo { rate, delay } => { + self.repeat_state = if rate == 0 { + // Zero rate means disabled repeat + // + // Reference: + // https://github.com/swaywm/wlroots/blob/3d46d3f7/protocol/input-method-unstable-v2.xml#L444-L455 + None + } else { + let info = RepeatInfo { rate, delay }; + let press_state = self.repeat_state.map(|pair| pair.1); + Some((info, press_state.unwrap_or(PressState::NotPressing))) + } + } + _ => {} + } + } + + pub fn handle_timer_ev(&mut self) -> std::io::Result<()> { + // Read timer, this MUST be called or timer will be broken + let overrun_count = self.timer.read()?; + if overrun_count != 1 { + log::warn!("Some timer events were not properly handled!"); + } + + if let Some(( + info, + PressState::Pressing { + pressed_at, + ref mut is_repeating, + key, + wayland_time, + }, + )) = self.repeat_state + { + if !*is_repeating { + // Start repeat + log::trace!("Start repeating {}", key); + let interval = &Duration::from_secs_f64(1.0 / info.rate as f64); + self.timer.set_timeout_interval(interval)?; + *is_repeating = true; + } + + // Emit key repeat event + let ev = KeyEvent::Key { + serial: self.serial, + time: wayland_time + pressed_at.elapsed().as_millis() as u32, + key, + state: KeyState::Pressed, + }; + self.handle_key_ev(ev); + } else { + log::warn!("Received timer event when it has never received RepeatInfo."); + } + + Ok(()) + } +} + +pub fn run( + display: &Display, + event_queue: &mut EventQueue, + globals: &GlobalManager, +) -> Result<(), Box> { + let im_manager = globals.instantiate_exact::(1)?; + let vk_manager = globals.instantiate_exact::(1)?; + let seat = globals.instantiate_exact::(1).expect("Load Seat"); + + let filter = Filter::new(|ev, _filter, mut data| { + let ctx = KimeContext::new_data(&mut data); + + match ev { + Events::Key { event, .. } => { + ctx.handle_key_ev(event); + } + Events::Im { event, .. } => { + ctx.handle_im_ev(event); + } + } + }); + + let vk = vk_manager.create_virtual_keyboard(&seat); + let im = im_manager.get_input_method(&seat); + let grab = im.grab_keyboard(); + grab.assign(filter.clone()); + im.assign(filter); + + // Initialize timer + let mut timer = TimerFd::new(ClockId::Monotonic).expect("Initialize timer"); + + // Initialize epoll() object + let mut poll = Poll::new().expect("Initialize epoll()"); + let registry = poll.registry(); + + const POLL_WAYLAND: Token = Token(0); + registry + .register( + &mut SourceFd(&display.get_connection_fd()), + POLL_WAYLAND, + Interest::READABLE | Interest::WRITABLE, + ) + .expect("Register wayland socket to the epoll()"); + + const POLL_TIMER: Token = Token(1); + registry + .register(&mut timer, POLL_TIMER, Interest::READABLE) + .expect("Register timer to the epoll()"); + + // Initialize kime context + let mut kime_ctx = KimeContext::new(vk, im, grab, timer); + event_queue + .sync_roundtrip(&mut kime_ctx, |_, _, _| ()) + .unwrap(); + + log::info!("Server init success!"); + + // Non-blocking event loop + // + // Reference: + // https://docs.rs/wayland-client/0.28.3/wayland_client/struct.EventQueue.html + let mut events = MioEvents::with_capacity(1024); + let stop_reason = 'main: loop { + use std::io::ErrorKind; + + // Sleep until next event + if let Err(e) = poll.poll(&mut events, None) { + // Should retry on EINTR + // + // Reference: + // https://www.gnu.org/software/libc/manual/html_node/Interrupted-Primitives.html + if e.kind() == ErrorKind::Interrupted { + continue; + } + break Err(e); + } + + for event in &events { + match event.token() { + POLL_WAYLAND => {} + POLL_TIMER => { + if let Err(e) = kime_ctx.handle_timer_ev() { + break 'main Err(e); + } + } + _ => unreachable!(), + } + } + + // Perform read() only when it's ready, returns None when there're already pending events + if let Some(guard) = event_queue.prepare_read() { + if let Err(e) = guard.read_events() { + // EWOULDBLOCK here means there's no new messages to read + if e.kind() != ErrorKind::WouldBlock { + break Err(e); + } + } + } + + if let Err(e) = event_queue.dispatch_pending(&mut kime_ctx, |_, _, _| {}) { + break Err(e); + } + + // Flush pending writes + if let Err(e) = display.flush() { + // EWOULDBLOCK here means there're so many to write, retry later + if e.kind() != ErrorKind::WouldBlock { + break Err(e); + } + } + }; + + match stop_reason { + Ok(()) => { + log::info!("Server finished gracefully"); + Ok(()) + } + Err(e) => { + log::error!("Server aborted due to IO Error: {}", e); + Err(Box::from(e)) + } + } +} diff --git a/src/frontends/wayland/src/lib.rs b/src/frontends/wayland/src/lib.rs new file mode 100644 index 00000000..77444a30 --- /dev/null +++ b/src/frontends/wayland/src/lib.rs @@ -0,0 +1,49 @@ +use std::time::Instant; + +pub mod input_method_v1; +pub mod input_method_v2; + +#[derive(Clone, Copy)] +pub struct RepeatInfo { + /// The rate of repeating keys in characters per second + rate: i32, + /// Delay in milliseconds since key down until repeating starts + delay: i32, +} + +#[derive(Clone, Copy)] +pub enum PressState { + /// User is pressing no key, or user lifted last pressed key. But kime-wayland is ready for key + /// long-press. + NotPressing, + /// User is pressing a key. + Pressing { + /// User started pressing a key at this moment. + pressed_at: Instant, + /// `false` if user just started pressing a key. Soon, key repeating will be begin. `true` + /// if user have pressed a key for a long enough time, key repeating is happening right + /// now. + is_repeating: bool, + + /// Key code used by wayland + key: u32, + /// Timestamp with millisecond granularity used by wayland. Their base is undefined, so + /// they can't be compared against system time (as obtained with clock_gettime or + /// gettimeofday). They can be compared with each other though, and for instance be used to + /// identify sequences of button presses as double or triple clicks. + /// + /// #### Reference + /// - https://wayland.freedesktop.org/docs/html/ch04.html#sect-Protocol-Input + wayland_time: u32, + }, +} + +impl PressState { + pub fn is_pressing(&self, query_key: u32) -> bool { + if let PressState::Pressing { key, .. } = self { + *key == query_key + } else { + false + } + } +} diff --git a/src/frontends/wayland/src/main.rs b/src/frontends/wayland/src/main.rs index 18a7ee8c..069a5846 100644 --- a/src/frontends/wayland/src/main.rs +++ b/src/frontends/wayland/src/main.rs @@ -1,392 +1,4 @@ -use std::time::{Duration, Instant}; - -use wayland_client::{ - event_enum, - protocol::{wl_keyboard::KeyState, wl_seat::WlSeat}, - DispatchData, Display, Filter, GlobalManager, Main, -}; - -use wayland_protocols::misc::zwp_input_method_v2::client::{ - zwp_input_method_keyboard_grab_v2::{Event as KeyEvent, ZwpInputMethodKeyboardGrabV2}, - zwp_input_method_manager_v2::ZwpInputMethodManagerV2, - zwp_input_method_v2::{Event as ImEvent, ZwpInputMethodV2}, -}; -use zwp_virtual_keyboard::virtual_keyboard_unstable_v1::{ - zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1, - zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1, -}; - -use kime_engine_cffi::*; - -use mio::{unix::SourceFd, Events as MioEvents, Interest, Poll, Token}; -use mio_timerfd::{ClockId, TimerFd}; - -event_enum! { - Events | - Key => ZwpInputMethodKeyboardGrabV2, - Im => ZwpInputMethodV2 -} - -struct InputMethodState { - activate: bool, - deactivate: bool, -} - -impl Default for InputMethodState { - fn default() -> Self { - Self { - activate: false, - deactivate: false, - } - } -} - -#[derive(Clone, Copy)] -struct RepeatInfo { - /// The rate of repeating keys in characters per second - rate: i32, - /// Delay in milliseconds since key down until repeating starts - delay: i32, -} - -#[derive(Clone, Copy)] -enum PressState { - /// User is pressing no key, or user lifted last pressed key. But kime-wayland is ready for key - /// long-press. - NotPressing, - /// User is pressing a key. - Pressing { - /// User started pressing a key at this moment. - pressed_at: Instant, - /// `false` if user just started pressing a key. Soon, key repeating will be begin. `true` - /// if user have pressed a key for a long enough time, key repeating is happening right - /// now. - is_repeating: bool, - - /// Key code used by wayland - key: u32, - /// Timestamp with millisecond granularity used by wayland. Their base is undefined, so - /// they can't be compared against system time (as obtained with clock_gettime or - /// gettimeofday). They can be compared with each other though, and for instance be used to - /// identify sequences of button presses as double or triple clicks. - /// - /// #### Reference - /// - https://wayland.freedesktop.org/docs/html/ch04.html#sect-Protocol-Input - wayland_time: u32, - }, -} - -impl PressState { - fn is_pressing(&self, query_key: u32) -> bool { - if let PressState::Pressing { key, .. } = self { - *key == query_key - } else { - false - } - } -} - -struct KimeContext { - config: Config, - engine: InputEngine, - mod_state: ModifierState, - current_state: InputMethodState, - pending_state: InputMethodState, - vk: Main, - im: Main, - grab: Main, - numlock: bool, - engine_ready: bool, - keymap_init: bool, - grab_activate: bool, - serial: u32, - // Have to concern Multi seats? - - // Key repeat contexts - timer: TimerFd, - /// `None` if `KimeContext` have never received a `RepeatInfo` or repeat is disabled (i.e. rate - /// is zero). `Some(..)` if `RepeatInfo` is known and kime-wayland started tracking the press - /// state of keys. - repeat_state: Option<(RepeatInfo, PressState)>, -} - -impl Drop for KimeContext { - fn drop(&mut self) { - self.grab.release(); - self.vk.destroy(); - self.im.destroy(); - } -} - -impl KimeContext { - pub fn new( - vk: Main, - im: Main, - grab: Main, - timer: TimerFd, - ) -> Self { - let config = Config::load(); - Self { - engine: InputEngine::new(&config), - config, - mod_state: 0, - current_state: InputMethodState::default(), - pending_state: InputMethodState::default(), - serial: 0, - numlock: false, - engine_ready: true, - keymap_init: false, - grab_activate: false, - vk, - im, - grab, - timer, - repeat_state: None, - } - } - - pub fn new_data<'a>(data: &'a mut DispatchData) -> &'a mut Self { - data.get::().unwrap() - } - - fn process_input_result(&mut self, ret: InputResult) -> bool { - if ret & InputResult_NOT_READY != 0 { - self.engine_ready = false; - } - - if ret & InputResult_LANGUAGE_CHANGED != 0 { - self.engine.update_layout_state(); - } - - if ret & InputResult_HAS_PREEDIT != 0 { - let preedit = self.engine.preedit_str().into(); - self.preedit(preedit); - } else { - self.clear_preedit(); - } - - if ret & InputResult_HAS_COMMIT != 0 { - self.commit_string(self.engine.commit_str().into()); - self.engine.clear_commit(); - } - - self.commit(); - - ret & InputResult_CONSUMED == 0 - } - - fn commit(&mut self) { - self.im.commit(self.serial); - } - - fn commit_string(&mut self, s: String) { - if !s.is_empty() { - self.im.commit_string(s); - } - } - - fn clear_preedit(&mut self) { - self.im.set_preedit_string(String::new(), -1, -1); - } - - fn preedit(&mut self, s: String) { - let len = s.len(); - self.im.set_preedit_string(s, 0, len as _); - } - - pub fn handle_im_ev(&mut self, ev: ImEvent) { - match ev { - ImEvent::Activate => { - self.pending_state.activate = true; - } - ImEvent::Deactivate => { - self.pending_state.deactivate = true; - } - ImEvent::Unavailable => { - log::error!("Receive Unavailable event is another server already running?"); - panic!("Unavailable") - } - ImEvent::Done => { - self.serial += 1; - if !self.current_state.activate && self.pending_state.activate { - self.engine.update_layout_state(); - if !self.engine_ready { - if self.engine.check_ready() { - let ret = self.engine.end_ready(); - self.process_input_result(ret); - self.engine_ready = true; - } - } - self.grab_activate = true; - } else if !self.current_state.deactivate && self.pending_state.deactivate { - // Focus lost, reset states - if self.engine_ready { - self.engine.reset(); - } - self.grab_activate = false; - - // Input deactivated, stop repeating - self.timer.disarm().unwrap(); - if let Some((_, ref mut press_state)) = self.repeat_state { - *press_state = PressState::NotPressing - } - } - self.current_state = std::mem::take(&mut self.pending_state); - } - _ => {} - } - } - - pub fn handle_key_ev(&mut self, ev: KeyEvent) { - match ev { - KeyEvent::Keymap { fd, format, size } => { - if !self.keymap_init { - self.vk.keymap(format as _, fd, size); - self.keymap_init = true; - } - unsafe { - libc::close(fd); - } - } - KeyEvent::Key { - state, key, time, .. - } => { - // NOTE: Never read `serial` of KeyEvent. You should rely on serial of KimeContext - if state == KeyState::Pressed { - if self.grab_activate { - let ret = self.engine.press_key( - &self.config, - (key + 8) as u16, - self.numlock, - self.mod_state, - ); - - let bypassed = self.process_input_result(ret); - - if bypassed { - // Bypassed key's repeat will be handled by the clients. - // - // Reference: - // https://github.com/swaywm/sway/pull/4932#issuecomment-774113129 - self.vk.key(time, key, state as _); - } else { - // If the key was not bypassed by IME, key repeat should be handled by the - // IME. Start waiting for the key hold timer event. - match self.repeat_state { - Some((info, ref mut press_state)) - if !press_state.is_pressing(key) => - { - let duration = Duration::from_millis(info.delay as u64); - self.timer.set_timeout(&duration).unwrap(); - *press_state = PressState::Pressing { - pressed_at: Instant::now(), - is_repeating: false, - key, - wayland_time: time, - }; - } - _ => {} - } - } - } else { - // not activated so just skip - self.vk.key(time, key, state as _); - } - } else { - // If user released the last pressed key, clear the timer and state - if let Some((.., ref mut press_state)) = self.repeat_state { - if press_state.is_pressing(key) { - self.timer.disarm().unwrap(); - *press_state = PressState::NotPressing; - } - } - - self.vk.key(time, key, state as _); - } - } - KeyEvent::Modifiers { - mods_depressed, - mods_latched, - mods_locked, - group, - .. - } => { - self.mod_state = 0; - if mods_depressed & 0x1 != 0 { - self.mod_state |= ModifierState_SHIFT; - } - if mods_depressed & 0x4 != 0 { - self.mod_state |= ModifierState_CONTROL; - } - if mods_depressed & 0x8 != 0 { - self.mod_state |= ModifierState_ALT; - } - if mods_depressed & 0x40 != 0 { - self.mod_state |= ModifierState_SUPER; - } - - self.numlock = mods_depressed & 0x10 != 0; - - self.vk - .modifiers(mods_depressed, mods_latched, mods_locked, group); - } - KeyEvent::RepeatInfo { rate, delay } => { - self.repeat_state = if rate == 0 { - // Zero rate means disabled repeat - // - // Reference: - // https://github.com/swaywm/wlroots/blob/3d46d3f7/protocol/input-method-unstable-v2.xml#L444-L455 - None - } else { - let info = RepeatInfo { rate, delay }; - let press_state = self.repeat_state.map(|pair| pair.1); - Some((info, press_state.unwrap_or(PressState::NotPressing))) - } - } - _ => {} - } - } - - pub fn handle_timer_ev(&mut self) -> std::io::Result<()> { - // Read timer, this MUST be called or timer will be broken - let overrun_count = self.timer.read()?; - if overrun_count != 1 { - log::warn!("Some timer events were not properly handled!"); - } - - if let Some(( - info, - PressState::Pressing { - pressed_at, - ref mut is_repeating, - key, - wayland_time, - }, - )) = self.repeat_state - { - if !*is_repeating { - // Start repeat - log::trace!("Start repeating {}", key); - let interval = &Duration::from_secs_f64(1.0 / info.rate as f64); - self.timer.set_timeout_interval(interval)?; - *is_repeating = true; - } - - // Emit key repeat event - let ev = KeyEvent::Key { - serial: self.serial, - time: wayland_time + pressed_at.elapsed().as_millis() as u32, - key, - state: KeyState::Pressed, - }; - self.handle_key_ev(ev); - } else { - log::warn!("Received timer event when it has never received RepeatInfo."); - } - - Ok(()) - } -} +use wayland_client::{Display, GlobalManager}; fn main() { kime_version::cli_boilerplate!((),); @@ -403,119 +15,9 @@ fn main() { event_queue.sync_roundtrip(&mut (), |_, _, _| ()).unwrap(); - let seat = globals.instantiate_exact::(1).expect("Load Seat"); - let im_manager = globals - .instantiate_exact::(1) - .expect("Load InputManager"); - let vk_manager = globals - .instantiate_exact::(1) - .expect("Load VirtualKeyboardManager"); - - let filter = Filter::new(|ev, _filter, mut data| { - let ctx = KimeContext::new_data(&mut data); - - match ev { - Events::Key { event, .. } => { - ctx.handle_key_ev(event); - } - Events::Im { event, .. } => { - ctx.handle_im_ev(event); - } - } - }); - - let vk = vk_manager.create_virtual_keyboard(&seat); - let im = im_manager.get_input_method(&seat); - let grab = im.grab_keyboard(); - grab.assign(filter.clone()); - im.assign(filter); - - // Initialize timer - let mut timer = TimerFd::new(ClockId::Monotonic).expect("Initialize timer"); - - // Initialize epoll() object - let mut poll = Poll::new().expect("Initialize epoll()"); - let registry = poll.registry(); - - const POLL_WAYLAND: Token = Token(0); - registry - .register( - &mut SourceFd(&display.get_connection_fd()), - POLL_WAYLAND, - Interest::READABLE | Interest::WRITABLE, - ) - .expect("Register wayland socket to the epoll()"); - - const POLL_TIMER: Token = Token(1); - registry - .register(&mut timer, POLL_TIMER, Interest::READABLE) - .expect("Register timer to the epoll()"); - - // Initialize kime context - let mut kime_ctx = KimeContext::new(vk, im, grab, timer); - event_queue - .sync_roundtrip(&mut kime_ctx, |_, _, _| ()) - .unwrap(); - - log::info!("Server init success!"); - - // Non-blocking event loop - // - // Reference: - // https://docs.rs/wayland-client/0.28.3/wayland_client/struct.EventQueue.html - let mut events = MioEvents::with_capacity(1024); - let stop_reason = 'main: loop { - use std::io::ErrorKind; - - // Sleep until next event - if let Err(e) = poll.poll(&mut events, None) { - // Should retry on EINTR - // - // Reference: - // https://www.gnu.org/software/libc/manual/html_node/Interrupted-Primitives.html - if e.kind() == ErrorKind::Interrupted { - continue; - } - break Err(e); - } - - for event in &events { - match event.token() { - POLL_WAYLAND => {} - POLL_TIMER => { - if let Err(e) = kime_ctx.handle_timer_ev() { - break 'main Err(e); - } - } - _ => unreachable!(), - } - } - - // Perform read() only when it's ready, returns None when there're already pending events - if let Some(guard) = event_queue.prepare_read() { - if let Err(e) = guard.read_events() { - // EWOULDBLOCK here means there's no new messages to read - if e.kind() != ErrorKind::WouldBlock { - break Err(e); - } - } - } - - if let Err(e) = event_queue.dispatch_pending(&mut kime_ctx, |_, _, _| {}) { - break Err(e); - } - - // Flush pending writes - if let Err(e) = display.flush() { - // EWOULDBLOCK here means there're so many to write, retry later - if e.kind() != ErrorKind::WouldBlock { - break Err(e); - } - } - }; + let result = kime_wayland::input_method_v2::run(&display, &mut event_queue, &globals); - match stop_reason { - Ok(()) => log::info!("Server finished gracefully"), - Err(e) => log::error!("Server aborted due to IO Error: {}", e), + if let Err(_) = result { + kime_wayland::input_method_v1::run(&display, &mut event_queue, &globals).unwrap(); } } diff --git a/src/tools/check/src/main.rs b/src/tools/check/src/main.rs index a4d19741..9d4cbbbe 100644 --- a/src/tools/check/src/main.rs +++ b/src/tools/check/src/main.rs @@ -7,6 +7,7 @@ use kime_engine_cffi::{ }; use pad::PadStr; use std::env; +use std::io::BufRead; use strum::{EnumIter, EnumMessage, IntoEnumIterator, IntoStaticStr}; #[derive(Clone, PartialEq, Eq, IntoStaticStr)] @@ -34,7 +35,7 @@ impl CondResult { print!( "{} {}", c.paint(<&str>::from(self).pad_to_width(8)), - Color::White.bold().paint(message.pad_to_width(30)) + Color::White.bold().paint(message.pad_to_width(40)) ); match self { @@ -62,6 +63,8 @@ enum Check { QtImModule, #[strum(message = "LANG has UTF-8")] Lang, + #[strum(message = "Plasma virtual keyboard has kime")] + PlasmaVirtualKeyboard, } impl Check { @@ -183,6 +186,57 @@ impl Check { }, "set LANG encoding UTF-8", ), + Check::PlasmaVirtualKeyboard => { + let current_desktop = env::var("XDG_CURRENT_DESKTOP").map_or(String::new(), |x| x); + let session_type = env::var("XDG_SESSION_TYPE").map_or(String::new(), |x| x); + if current_desktop.contains("KDE") && session_type == "wayland" { + let dirs = xdg::BaseDirectories::new().expect("Load xdg dirs"); + let config_path = match dirs.find_config_file("kwinrc") { + Some(path) => path, + _ => { + return CondResult::Fail( + "kwinrc configuration file doesn't exist".into(), + ) + } + }; + + println!("Loading kwinrc: {}", config_path.display()); + + let file = std::fs::File::open(config_path).expect("Open kwinrc"); + let lines = std::io::BufReader::new(file).lines(); + + let mut given_input_method = String::new(); + + for line in lines { + if let Ok(s) = line { + if s.contains("=") { + let splits: Vec<&str> = s.split('=').collect(); + if splits[0].contains("InputMethod") { + if splits[1].contains("kime.desktop") { + return CondResult::Ok; + } else { + given_input_method = String::from(splits[1]); + } + } + } + } + } + + if given_input_method.is_empty() { + CondResult::Fail("Virtual keyboard is not set".to_string()) + } else { + CondResult::Fail(format!( + "Virtual keyboard is set to {} not kime", + given_input_method + )) + } + } else { + CondResult::Ignore(format!( + "Current desktop and session type is {} and {}, not KDE and wayland", + current_desktop, session_type + )) + } + } } } }