diff --git a/.github/workflows/gen_centos7.yml b/.github/workflows/gen_centos7.yml index 7e351a348..8c2e8ec87 100644 --- a/.github/workflows/gen_centos7.yml +++ b/.github/workflows/gen_centos7.yml @@ -31,9 +31,10 @@ jobs: - name: "Install Git from source" shell: bash run: | - - yum install -y wget curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc perl-ExtUtils-MakeMaker make - + + yum install -y wget curl-devel expat-devel gettext-devel openssl-devel zlib-devel centos-release-scl-rh perl-ExtUtils-MakeMaker make + yum install -y devtoolset-9-gcc devtoolset-9-gcc-c++ + if test ! -x /usr/local/git/bin/git ; then cd /tmp wget https://github.com/git/git/archive/v2.26.2.tar.gz @@ -41,9 +42,9 @@ jobs: cd git-2.26.2 make prefix=/usr/local/git install fi - + ln -s /usr/local/git/bin/git /usr/local/bin/git - + - name: "Install curl" shell: bash @@ -84,10 +85,10 @@ jobs: run: "cargo fmt --all -- --check" - name: "Build (Release mode)" shell: bash - run: "cargo build --all --release" + run: "source /opt/rh/devtoolset-9/enable && cargo build --all --release" - name: "Test (Release mode)" shell: bash - run: "cargo test --all --release" + run: "source /opt/rh/devtoolset-9/enable && cargo test --all --release" - name: "Package" shell: bash run: "bash ci/deploy.sh" @@ -96,7 +97,7 @@ jobs: run: | mkdir pkg_ mv ~/rpmbuild/RPMS/*/*.rpm pkg_ - + - name: "Upload artifact" uses: actions/upload-artifact@master diff --git a/Cargo.lock b/Cargo.lock index 38df28923..ded58d6d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5339,6 +5339,7 @@ dependencies = [ "winreg 0.6.2", "x11", "xcb 0.9.0", + "xcb-imdkit", "xcb-util", "xkbcommon", ] @@ -5453,6 +5454,18 @@ dependencies = [ "x11", ] +[[package]] +name = "xcb-imdkit" +version = "0.1.0" +source = "git+https://github.com/H-M-H/xcb-imdkit-rs#3cb940863f64d13a9cdbc09eea67d2a69e0a2737" +dependencies = [ + "bitflags", + "cc", + "lazy_static", + "pkg-config", + "xcb 0.9.0", +] + [[package]] name = "xcb-util" version = "0.3.0" diff --git a/get-deps b/get-deps index 1626ef84d..af81411c2 100755 --- a/get-deps +++ b/get-deps @@ -77,6 +77,7 @@ if test -e /etc/centos-release || test -e /etc/fedora-release; then libxkbcommon-x11-devel \ wayland-devel \ mesa-libEGL-devel \ + xcb-util-devel \ xcb-util-keysyms-devel \ xcb-util-image-devel \ xcb-util-wm-devel \ @@ -101,6 +102,7 @@ if test -x /usr/bin/lsb_release && test `lsb_release -si` = "openSUSE"; then libxkbcommon-x11-devel \ wayland-devel \ Mesa-libEGL-devel \ + xcb-util-devel \ xcb-util-keysyms-devel \ xcb-util-image-devel \ xcb-util-wm-devel \ @@ -110,6 +112,7 @@ fi if test -e /etc/debian_version ; then APT="$SUDO apt-get" + apt-cache show libxcb-util-dev > /dev/null 2>&1 && XCBUTIL="libxcb-util-dev" || XCBUTIL="libxcb-util0-dev" $APT install -y \ bsdutils \ cmake \ @@ -130,6 +133,7 @@ if test -e /etc/debian_version ; then libxcb-xkb-dev \ libxkbcommon-dev \ libxkbcommon-x11-dev \ + "$XCBUTIL" \ lsb-release \ python3 \ xdg-utils \ @@ -151,6 +155,7 @@ if test -e /etc/arch-release ; then 'python3' \ 'rust' \ 'wayland' \ + 'xcb-util' \ 'xcb-util-image' \ 'xcb-util-keysyms' \ 'xcb-util-wm' @@ -183,6 +188,8 @@ case $OSTYPE in python3 \ rust \ wayland \ + libxcb \ + xcb-util \ xcb-util-image \ xcb-util-keysyms \ xcb-util-wm \ diff --git a/window/Cargo.toml b/window/Cargo.toml index 8fdfe53c2..5342354fb 100644 --- a/window/Cargo.toml +++ b/window/Cargo.toml @@ -73,6 +73,7 @@ smithay-client-toolkit = {version = "0.14", default-features=false, optional=tru wayland-protocols = {version="0.28", optional=true} wayland-client = {version="0.28", optional=true} wayland-egl = {version="0.28", optional=true} +xcb-imdkit = { version="0.1", git = "https://github.com/H-M-H/xcb-imdkit-rs" } [target.'cfg(target_os="macos")'.dependencies] cocoa = "0.20" diff --git a/window/src/os/x11/connection.rs b/window/src/os/x11/connection.rs index c621df9ef..81e6d4e78 100644 --- a/window/src/os/x11/connection.rs +++ b/window/src/os/x11/connection.rs @@ -46,6 +46,8 @@ pub struct XConnection { pub(crate) visual: xcb::xproto::Visualtype, pub(crate) depth: u8, pub(crate) gl_connection: RefCell>>, + pub(crate) ime: RefCell>>, + pub(crate) ime_process_event_result: RefCell>, } impl std::ops::Deref for XConnection { @@ -275,7 +277,7 @@ impl XConnection { } }, Some(event) => { - if let Err(err) = self.process_xcb_event(&event) { + if let Err(err) = self.process_xcb_event_ime(&event) { return Err(err); } } @@ -285,12 +287,23 @@ impl XConnection { loop { match self.conn.poll_for_queued_event() { None => return Ok(()), - Some(event) => self.process_xcb_event(&event)?, + Some(event) => self.process_xcb_event_ime(&event)?, } self.conn.flush(); } } + fn process_xcb_event_ime(&self, event: &xcb::GenericEvent) -> anyhow::Result<()> { + // check for previous errors produced by the IME forward_event callback + self.ime_process_event_result.replace(Ok(()))?; + + if config::configuration().use_ime && self.ime.borrow_mut().process_event(event) { + self.ime_process_event_result.replace(Ok(())) + } else { + self.process_xcb_event(event) + } + } + fn process_xcb_event(&self, event: &xcb::GenericEvent) -> anyhow::Result<()> { if let Some(window_id) = window_id_from_event(event) { self.process_window_event(window_id, event)?; @@ -326,7 +339,7 @@ impl XConnection { Ok(()) } - pub(crate) fn create_new() -> anyhow::Result { + pub(crate) fn create_new() -> anyhow::Result> { let (conn, screen_num) = connect_with_xlib_display()?; let conn = xcb_util::ewmh::Connection::connect(conn) .map_err(|_| anyhow!("failed to init ewmh"))?; @@ -441,7 +454,17 @@ impl XConnection { let default_dpi = RefCell::new(compute_default_dpi(&xrm, &xsettings)); - let conn = XConnection { + xcb_imdkit::ImeClient::set_logger(|msg| log::debug!("Ime: {}", msg)); + let ime = unsafe { + xcb_imdkit::ImeClient::unsafe_new( + &conn, + screen_num, + xcb_imdkit::InputStyle::DEFAULT, + None, + ) + }; + + let conn = Rc::new(XConnection { conn, default_dpi, xsettings: RefCell::new(xsettings), @@ -472,7 +495,36 @@ impl XConnection { depth, visual, gl_connection: RefCell::new(None), - }; + ime: RefCell::new(ime), + ime_process_event_result: RefCell::new(Ok(())), + }); + + { + let conn = conn.clone(); + conn.clone() + .ime + .borrow_mut() + .set_commit_string_cb(move |window_id, input| { + if let Some(window) = conn.window_by_id(window_id) { + let mut inner = window.lock().unwrap(); + inner.dispatch_ime_text(input); + } + }); + } + { + let conn = conn.clone(); + conn.clone() + .ime + .borrow_mut() + .set_forward_event_cb(move |_win, e| { + if let err @ Err(_) = conn.process_xcb_event(unsafe { std::mem::transmute(e) }) + { + if let Err(err) = conn.ime_process_event_result.replace(err) { + log::warn!("IME process event error dropped: {}", err); + } + } + }); + } Ok(conn) } diff --git a/window/src/os/x11/window.rs b/window/src/os/x11/window.rs index 3e62d7e51..d7a6497c1 100644 --- a/window/src/os/x11/window.rs +++ b/window/src/os/x11/window.rs @@ -5,8 +5,8 @@ use crate::os::xkeysyms; use crate::os::{Connection, Window}; use crate::{ Appearance, Clipboard, Dimensions, MouseButtons, MouseCursor, MouseEvent, MouseEventKind, - MousePress, Point, ScreenPoint, WindowDecorations, WindowEvent, WindowEventSender, WindowOps, - WindowState, + MousePress, Point, Rect, ScreenPoint, WindowDecorations, WindowEvent, WindowEventSender, + WindowOps, WindowState, }; use anyhow::{anyhow, Context as _}; use async_trait::async_trait; @@ -19,6 +19,7 @@ use std::convert::TryInto; use std::rc::{Rc, Weak}; use std::sync::{Arc, Mutex}; use wezterm_font::FontConfiguration; +use wezterm_input_types::{KeyCode, KeyEvent, Modifiers}; #[derive(Default)] struct CopyAndPaste { @@ -64,6 +65,8 @@ pub(crate) struct XWindowInner { config: ConfigHandle, appearance: Appearance, title: String, + has_focus: bool, + last_cursor_position: Rect, } impl Drop for XWindowInner { @@ -167,6 +170,8 @@ impl XWindowInner { self.expose(expose.x(), expose.y(), expose.width(), expose.height()); } xcb::CONFIGURE_NOTIFY => { + self.update_ime_position(); + let cfg: &xcb::ConfigureNotifyEvent = unsafe { xcb::cast_event(event) }; let width = cfg.width(); let height = cfg.height(); @@ -331,10 +336,13 @@ impl XWindowInner { } } xcb::FOCUS_IN => { + self.has_focus = true; + self.update_ime_position(); log::trace!("Calling focus_change(true)"); self.events.dispatch(WindowEvent::FocusChanged(true)); } xcb::FOCUS_OUT => { + self.has_focus = false; log::trace!("Calling focus_change(false)"); self.events.dispatch(WindowEvent::FocusChanged(false)); } @@ -346,6 +354,20 @@ impl XWindowInner { Ok(()) } + pub fn dispatch_ime_text(&mut self, text: &str) { + let key_event = KeyEvent { + key: KeyCode::Composed(text.into()), + raw_key: None, + raw_modifiers: Modifiers::NONE, + raw_code: None, + modifiers: Modifiers::NONE, + repeat_count: 1, + key_is_down: true, + } + .normalize_shift(); + self.events.dispatch(WindowEvent::KeyEvent(key_event)); + } + /// If we own the selection, make sure that the X server reflects /// that and vice versa. fn update_selection_owner(&mut self, clipboard: Clipboard) { @@ -758,6 +780,8 @@ impl XWindow { copy_and_paste: CopyAndPaste::default(), cursors: CursorInfo::new(&conn), config: config.clone(), + has_focus: false, + last_cursor_position: Rect::default(), })) }; @@ -872,6 +896,25 @@ impl XWindowInner { } } + fn set_text_cursor_position(&mut self, cursor: Rect) { + if self.last_cursor_position == cursor { + return; + } + self.last_cursor_position = cursor; + self.update_ime_position(); + } + + fn update_ime_position(&mut self) { + if !self.has_focus { + return; + } + self.conn().ime.borrow_mut().update_pos( + self.window_id, + self.last_cursor_position.min_x() as i16, + self.last_cursor_position.max_y() as i16, + ); + } + fn set_icon(&mut self, image: &dyn BitmapImage) { let (width, height) = image.image_dimensions(); @@ -1012,6 +1055,13 @@ impl WindowOps for XWindow { }); } + fn set_text_cursor_position(&self, cursor: Rect) { + XConnection::with_window_inner(self.0, move |inner| { + inner.set_text_cursor_position(cursor); + Ok(()) + }); + } + fn set_icon(&self, image: Image) { XConnection::with_window_inner(self.0, move |inner| { inner.set_icon(&image); diff --git a/window/src/os/x_and_wayland.rs b/window/src/os/x_and_wayland.rs index c6ff60c3c..bbb405a0f 100644 --- a/window/src/os/x_and_wayland.rs +++ b/window/src/os/x_and_wayland.rs @@ -7,7 +7,7 @@ use crate::os::wayland::connection::WaylandConnection; use crate::os::wayland::window::WaylandWindow; use crate::os::x11::connection::XConnection; use crate::os::x11::window::XWindow; -use crate::{Clipboard, MouseCursor, ScreenPoint, WindowEvent, WindowOps}; +use crate::{Clipboard, MouseCursor, Rect, ScreenPoint, WindowEvent, WindowOps}; use async_trait::async_trait; use config::ConfigHandle; use promise::*; @@ -43,7 +43,7 @@ impl Connection { } } } - Ok(Connection::X11(Rc::new(XConnection::create_new()?))) + Ok(Connection::X11(XConnection::create_new()?)) } pub async fn new_window( @@ -277,6 +277,12 @@ impl WindowOps for Window { } } + fn set_text_cursor_position(&self, cursor: Rect) { + if let Self::X11(x) = self { + x.set_text_cursor_position(cursor); + } + } + fn get_clipboard(&self, clipboard: Clipboard) -> Future { match self { Self::X11(x) => x.get_clipboard(clipboard),