1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-22 22:42:48 +03:00

wayland: initial support for zwp_text_input_v3 / IME

refs: #1772
This commit is contained in:
Wez Furlong 2022-07-09 09:43:29 -07:00
parent 520a8e306f
commit e2b748a0d4
9 changed files with 290 additions and 47 deletions

View File

@ -22,6 +22,7 @@ As features stabilize some brief notes about them will accumulate here.
* New [wezterm.gui.screens()](config/lua/wezterm.gui/screens.md) function for getting information about the available screens/monitors/displays
* You may now use [wezterm.format](config/lua/wezterm/format.md) (or otherwise use strings with escape sequences) in the labels of the [Launcher Menu](config/launch.md#the-launcher-menu).
* You may now specify `assume_emoji_presentation = true` (or `false`) in [wezterm.font()](config/lua/wezterm/font.md) and [wezterm.font_with_fallback()](config/lua/wezterm/font_with_fallback.md)
* Wayland: `zwp_text_input_v3` is now supported, which enables IME to work in wezterm if your compositor also implements this protocol.
#### Fixed
* [ActivateKeyTable](config/lua/keyassignment/ActivateKeyTable.md)'s `replace_current` field was not actually optional. Made it optional. [#2179](https://github.com/wez/wezterm/issues/2179)

View File

@ -11,7 +11,7 @@ IME support is a platform dependent feature
|Windows |Forever |Always enabled, cannot be disabled|
|macOS |20200113-214446-bb6251f|defaults to enabled starting in 20220319-142410-0fcdea07. Earlier versions had problems with key repeat when enabled|
|X11 |20211204-082213-a66c61ee9|[XIM](https://en.wikipedia.org/wiki/X_Input_Method) based. Your system needs to have a running input method engine (such as ibus or fcitx) that support the XIM protocol in order for wezterm to use it.|
|Wayland |Not yet|[#1772](https://github.com/wez/wezterm/issues/1772) is tracking IME support|
|Wayland |Nightly builds only|Your compositor must support `zwp_text_input_v3`|
You can control whether the IME is enabled in your configuration file:

View File

@ -2,11 +2,12 @@
use super::pointer::*;
use super::window::*;
use crate::connection::ConnectionOps;
use crate::os::wayland::inputhandler::InputHandler;
use crate::os::wayland::output::OutputHandler;
use crate::os::x11::keyboard::Keyboard;
use crate::screen::{ScreenInfo, Screens};
use crate::spawn::*;
use crate::{Connection, ScreenRect};
use crate::{Connection, ScreenRect, WindowEvent};
use anyhow::{bail, Context};
use mio::unix::SourceFd;
use mio::{Events, Interest, Poll, Token};
@ -27,11 +28,19 @@ use wayland_client::{EventQueue, Main};
toolkit::default_environment!(MyEnvironment, desktop,
fields=[
output_handler: OutputHandler,
input_handler: InputHandler,
],
singles=[
wayland_protocols::wlr::unstable::output_management::v1::client::zwlr_output_manager_v1::ZwlrOutputManagerV1 => output_handler,
wayland_protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3 => input_handler,
]);
impl MyEnvironment {
pub fn input_handler(&mut self) -> &mut InputHandler {
&mut self.input_handler
}
}
pub struct WaylandConnection {
should_terminate: RefCell<bool>,
pub(crate) next_window_id: AtomicUsize,
@ -61,7 +70,7 @@ pub struct WaylandConnection {
pub(crate) key_repeat_delay: RefCell<i32>,
pub(crate) last_serial: RefCell<u32>,
seat_listener: SeatListener,
pub(crate) environment: RefCell<Environment<MyEnvironment>>,
pub(crate) environment: Environment<MyEnvironment>,
event_q: RefCell<EventQueue>,
pub(crate) display: RefCell<Display>,
}
@ -71,7 +80,10 @@ impl WaylandConnection {
let (environment, display, event_q) = toolkit::new_default_environment!(
MyEnvironment,
desktop,
fields = [output_handler: OutputHandler::new()]
fields = [
output_handler: OutputHandler::new(),
input_handler: InputHandler::new(),
]
)?;
let mut pointer = None;
@ -95,6 +107,7 @@ impl WaylandConnection {
log::error!("keyboard_event: {:#}", err);
}
});
environment.with_inner(|env| env.input_handler.advise_seat(&seat, &keyboard));
seat_keyboards.insert(name, keyboard);
}
if has_ptr {
@ -111,6 +124,7 @@ impl WaylandConnection {
let seat_listener;
{
let env = environment.clone();
seat_listener = environment.listen_for_seats(move |seat, seat_data, _| {
if seat_data.has_keyboard {
if !seat_data.defunct {
@ -131,14 +145,18 @@ impl WaylandConnection {
// up handling key events twice.
if !seat_keyboards.contains_key(&seat_data.name) {
let keyboard = seat.get_keyboard();
keyboard.quick_assign(|keyboard, event, _| {
let conn = Connection::get().unwrap().wayland();
if let Err(err) = conn.keyboard_event(keyboard, event) {
log::error!("keyboard_event: {:#}", err);
}
});
env.with_inner(|env| env.input_handler.advise_seat(&seat, &keyboard));
seat_keyboards.insert(seat_data.name.clone(), keyboard);
}
} else {
env.with_inner(|env| env.input_handler.seat_defunct(&seat));
}
} else {
// If we previously had a keyboard object on this seat, it's no longer valid if
@ -158,7 +176,7 @@ impl WaylandConnection {
Ok(Self {
display: RefCell::new(display),
environment: RefCell::new(environment),
environment,
should_terminate: RefCell::new(false),
next_window_id: AtomicUsize::new(1),
windows: RefCell::new(HashMap::new()),
@ -179,7 +197,7 @@ impl WaylandConnection {
fn keyboard_event(
&self,
_pointer: Main<WlKeyboard>,
keyboard: Main<WlKeyboard>,
event: WlKeyboardEvent,
) -> anyhow::Result<()> {
match &event {
@ -196,13 +214,30 @@ impl WaylandConnection {
.get(&surface.as_ref().id())
{
self.keyboard_window_id.borrow_mut().replace(window_id);
self.environment.with_inner(|env| {
if let Some(input) =
env.input_handler.get_text_input_for_keyboard(&keyboard)
{
input.enable();
input.commit();
}
env.input_handler.advise_surface(&surface, &keyboard);
});
} else {
log::warn!("{:?}, no known surface", event);
}
}
WlKeyboardEvent::Leave { serial, .. }
| WlKeyboardEvent::Key { serial, .. }
| WlKeyboardEvent::Modifiers { serial, .. } => {
WlKeyboardEvent::Leave { serial, .. } => {
if let Some(input) = self
.environment
.with_inner(|env| env.input_handler.get_text_input_for_keyboard(&keyboard))
{
input.disable();
input.commit();
}
*self.last_serial.borrow_mut() = *serial;
}
WlKeyboardEvent::Key { serial, .. } | WlKeyboardEvent::Modifiers { serial, .. } => {
*self.last_serial.borrow_mut() = *serial;
}
WlKeyboardEvent::RepeatInfo { rate, delay } => {
@ -245,6 +280,15 @@ impl WaylandConnection {
Ok(())
}
pub(crate) fn dispatch_to_focused_window(&self, event: WindowEvent) {
if let Some(&window_id) = self.keyboard_window_id.borrow().as_ref() {
if let Some(win) = self.window_by_id(window_id) {
let mut inner = win.borrow_mut();
inner.events.dispatch(event);
}
}
}
pub(crate) fn next_window_id(&self) -> usize {
self.next_window_id
.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed)
@ -380,7 +424,6 @@ impl ConnectionOps for WaylandConnection {
fn screens(&self) -> anyhow::Result<Screens> {
if let Some(screens) = self
.environment
.borrow()
.with_inner(|env| env.output_handler.screens())
{
return Ok(screens);
@ -388,7 +431,7 @@ impl ConnectionOps for WaylandConnection {
let mut by_name = HashMap::new();
let mut virtual_rect: ScreenRect = euclid::rect(0, 0, 0, 0);
for output in self.environment.borrow().get_all_outputs() {
for output in self.environment.get_all_outputs() {
toolkit::output::with_output_info(&output, |info| {
let name = if info.name.is_empty() {
format!("{} {}", info.model, info.make)

View File

@ -78,7 +78,6 @@ impl CopyAndPaste {
let pointer = conn.pointer.borrow();
let primary_selection = if let Clipboard::PrimarySelection = clipboard {
conn.environment
.borrow()
.get_primary_selection_manager()
.zip(pointer.primary_selection_device.as_ref())
} else {
@ -111,7 +110,6 @@ impl CopyAndPaste {
None => {
let source = conn
.environment
.borrow()
.require_global::<WlDataDeviceManager>()
.create_data_source();
source.quick_assign(move |_source, event, _dispatch_data| {

View File

@ -0,0 +1,165 @@
//! Implements zwp_text_input_v3 for handling IME
use crate::connection::ConnectionOps;
use crate::os::wayland::{wl_id, WaylandConnection};
use crate::{DeadKeyStatus, KeyCode, KeyEvent, Modifiers, WindowEvent};
use smithay_client_toolkit::environment::GlobalHandler;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use wayland_client::protocol::wl_keyboard::WlKeyboard;
use wayland_client::protocol::wl_registry::WlRegistry;
use wayland_client::protocol::wl_seat::WlSeat;
use wayland_client::protocol::wl_surface::WlSurface;
use wayland_client::{Attached, DispatchData, Main};
use wayland_protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use wayland_protocols::unstable::text_input::v3::client::zwp_text_input_v3::{
Event, ZwpTextInputV3,
};
#[derive(Debug, Default)]
struct Inner {
input_by_seat: HashMap<u32, Attached<ZwpTextInputV3>>,
keyboard_to_seat: HashMap<u32, u32>,
surface_to_keyboard: HashMap<u32, u32>,
}
impl Inner {
fn handle_event(
&mut self,
_input: Main<ZwpTextInputV3>,
event: Event,
_ddata: DispatchData,
_inner: &Arc<Mutex<Self>>,
) {
log::trace!("{event:?}");
let conn = WaylandConnection::get().unwrap().wayland();
match event {
Event::PreeditString {
text,
cursor_begin: _,
cursor_end: _,
} => {
conn.dispatch_to_focused_window(WindowEvent::AdviseDeadKeyStatus(match text {
Some(text) => DeadKeyStatus::Composing(text),
None => DeadKeyStatus::None,
}));
}
Event::CommitString { text } => {
conn.dispatch_to_focused_window(match text {
Some(text) => WindowEvent::KeyEvent(KeyEvent {
key: KeyCode::composed(&text),
modifiers: Modifiers::NONE,
repeat_count: 1,
key_is_down: true,
raw: None,
}),
None => WindowEvent::AdviseDeadKeyStatus(DeadKeyStatus::None),
});
}
Event::Done { serial: _ } => {
conn.dispatch_to_focused_window(WindowEvent::AdviseDeadKeyStatus(
DeadKeyStatus::None,
));
}
_ => {}
}
}
}
pub struct InputHandler {
mgr: Option<Attached<ZwpTextInputManagerV3>>,
inner: Arc<Mutex<Inner>>,
}
impl InputHandler {
pub fn new() -> Self {
Self {
mgr: None,
inner: Arc::new(Mutex::new(Inner::default())),
}
}
pub fn get_text_input_for_keyboard(
&self,
keyboard: &WlKeyboard,
) -> Option<Attached<ZwpTextInputV3>> {
let inner = self.inner.lock().unwrap();
let keyboard_id = wl_id(keyboard);
let seat_id = inner.keyboard_to_seat.get(&keyboard_id)?;
inner.input_by_seat.get(&seat_id).cloned()
}
pub fn get_text_input_for_surface(
&self,
surface: &WlSurface,
) -> Option<Attached<ZwpTextInputV3>> {
let inner = self.inner.lock().unwrap();
let surface_id = wl_id(surface);
let keyboard_id = inner.surface_to_keyboard.get(&surface_id)?;
let seat_id = inner.keyboard_to_seat.get(&keyboard_id)?;
inner.input_by_seat.get(&seat_id).cloned()
}
pub fn get_text_input_for_seat(&self, seat: &WlSeat) -> Option<Attached<ZwpTextInputV3>> {
let mgr = self.mgr.as_ref()?;
let mut inner = self.inner.lock().unwrap();
let seat_id = wl_id(seat);
let input = inner.input_by_seat.entry(seat_id).or_insert_with(|| {
let input = mgr.get_text_input(seat);
let inner = Arc::clone(&self.inner);
input.quick_assign(move |input, event, ddat| {
inner
.lock()
.unwrap()
.handle_event(input, event, ddat, &inner);
});
input.into()
});
Some(input.clone())
}
pub fn advise_surface(&self, surface: &WlSurface, keyboard: &WlKeyboard) {
let surface_id = wl_id(surface);
let keyboard_id = wl_id(keyboard);
self.inner
.lock()
.unwrap()
.surface_to_keyboard
.insert(surface_id, keyboard_id);
}
pub fn advise_seat(&self, seat: &WlSeat, keyboard: &WlKeyboard) {
self.get_text_input_for_seat(seat);
let keyboard_id = wl_id(keyboard);
let seat_id = wl_id(seat);
self.inner
.lock()
.unwrap()
.keyboard_to_seat
.insert(keyboard_id, seat_id);
}
pub fn seat_defunct(&self, seat: &WlSeat) {
let seat_id = wl_id(seat);
self.inner.lock().unwrap().input_by_seat.remove(&seat_id);
}
}
impl GlobalHandler<ZwpTextInputManagerV3> for InputHandler {
fn created(
&mut self,
registry: Attached<WlRegistry>,
id: u32,
version: u32,
_ddata: DispatchData,
) {
log::debug!("created ZwpTextInputV3 {id} {version}");
let mgr = registry.bind::<ZwpTextInputManagerV3>(1, id);
self.mgr.replace(mgr.into());
}
fn get(&self) -> std::option::Option<Attached<ZwpTextInputManagerV3>> {
self.mgr.clone()
}
}

View File

@ -1,6 +1,7 @@
#![cfg(all(unix, not(target_os = "macos")))]
pub mod connection;
pub mod inputhandler;
pub mod output;
pub mod window;
pub use self::window::*;
@ -10,3 +11,16 @@ mod copy_and_paste;
mod drag_and_drop;
mod frame;
mod pointer;
/// Returns the id of a wayland proxy object, suitable for using
/// a key into hash maps
pub fn wl_id<I, T>(obj: T) -> u32
where
I: wayland_client::Interface,
T: AsRef<wayland_client::Proxy<I>>,
I: AsRef<wayland_client::Proxy<I>>,
I: From<wayland_client::Proxy<I>>,
{
let proxy: &wayland_client::Proxy<I> = obj.as_ref();
proxy.id()
}

View File

@ -1,5 +1,6 @@
//! Dealing with Wayland outputs
use crate::os::wayland::wl_id;
use crate::screen::{ScreenInfo, Screens};
use crate::ScreenRect;
use smithay_client_toolkit::environment::GlobalHandler;
@ -7,7 +8,7 @@ use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use wayland_client::protocol::wl_output::Transform;
use wayland_client::protocol::wl_registry::WlRegistry;
use wayland_client::{Attached, DispatchData, Interface, Main, Proxy};
use wayland_client::{Attached, DispatchData, Main};
use wayland_protocols::wlr::unstable::output_management::v1::client::zwlr_output_head_v1::{
Event as ZwlrOutputHeadEvent, ZwlrOutputHeadV1,
};
@ -55,17 +56,6 @@ struct Inner {
zwlr_head_info: HashMap<u32, HeadInfo>,
}
fn wl_id<I, T>(obj: T) -> u32
where
I: Interface,
T: AsRef<Proxy<I>>,
I: AsRef<wayland_client::Proxy<I>>,
I: From<wayland_client::Proxy<I>>,
{
let proxy: &Proxy<I> = obj.as_ref();
proxy.id()
}
impl Inner {
fn handle_zwlr_mode_event(
&mut self,

View File

@ -3,9 +3,10 @@ use super::frame::{ConceptConfig, ConceptFrame};
use super::pointer::*;
use crate::connection::ConnectionOps;
use crate::os::wayland::connection::WaylandConnection;
use crate::os::wayland::wl_id;
use crate::os::x11::keyboard::Keyboard;
use crate::{
Clipboard, Connection, Dimensions, MouseCursor, Point, RequestedWindowGeometry,
Clipboard, Connection, Dimensions, MouseCursor, Point, Rect, RequestedWindowGeometry,
ResolvedGeometry, ScreenPoint, Window, WindowEvent, WindowEventSender, WindowKeyEvent,
WindowOps, WindowState,
};
@ -124,7 +125,7 @@ impl KeyRepeatState {
pub struct WaylandWindowInner {
window_id: usize,
events: WindowEventSender,
pub(crate) events: WindowEventSender,
surface: Attached<WlSurface>,
surface_factor: i32,
copy_and_paste: Arc<Mutex<CopyAndPaste>>,
@ -144,6 +145,7 @@ pub struct WaylandWindowInner {
frame_callback: Option<Main<WlCallback>>,
invalidated: bool,
font_config: Rc<FontConfiguration>,
text_cursor: Option<Rect>,
config: Option<ConfigHandle>,
// cache the title for comparison to avoid spamming
// the compositor with updates that don't actually change it
@ -254,24 +256,21 @@ impl WaylandWindow {
let (pending_first_configure, wait_configure) = async_channel::bounded(1);
let surface = conn
.environment
.borrow_mut()
.create_surface_with_scale_callback({
let pending_event = Arc::clone(&pending_event);
move |dpi, surface, _dispatch_data| {
pending_event.lock().unwrap().dpi.replace(dpi);
log::debug!(
"surface id={} dpi scale changed to {}",
surface.as_ref().id(),
dpi
);
WaylandConnection::with_window_inner(window_id, move |inner| {
inner.dispatch_pending_event();
Ok(())
});
}
});
let surface = conn.environment.create_surface_with_scale_callback({
let pending_event = Arc::clone(&pending_event);
move |dpi, surface, _dispatch_data| {
pending_event.lock().unwrap().dpi.replace(dpi);
log::debug!(
"surface id={} dpi scale changed to {}",
surface.as_ref().id(),
dpi
);
WaylandConnection::with_window_inner(window_id, move |inner| {
inner.dispatch_pending_event();
Ok(())
});
}
});
conn.surface_to_window_id
.borrow_mut()
.insert(surface.as_ref().id(), window_id);
@ -293,7 +292,6 @@ impl WaylandWindow {
let mut window = conn
.environment
.borrow()
.create_window::<ConceptFrame, _>(
surface.clone().detach(),
theme_manager,
@ -373,6 +371,7 @@ impl WaylandWindow {
title: None,
gl_state: None,
wegl_surface: None,
text_cursor: None,
}));
let window_handle = Window::Wayland(WaylandWindow(window_id));
@ -461,6 +460,7 @@ impl WaylandWindowInner {
mapper.update_modifier_state(0, 0, 0, 0);
self.key_repeat.take();
self.events.dispatch(WindowEvent::FocusChanged(focused));
self.text_cursor.take();
}
pub(crate) fn dispatch_dropped_files(&mut self, paths: Vec<PathBuf>) {
@ -897,6 +897,13 @@ impl WindowOps for WaylandWindow {
});
}
fn set_text_cursor_position(&self, cursor: Rect) {
WaylandConnection::with_window_inner(self.0, move |inner| {
inner.set_text_cursor_position(cursor);
Ok(())
});
}
fn set_title(&self, title: &str) {
let title = title.to_owned();
WaylandConnection::with_window_inner(self.0, move |inner| {
@ -1113,6 +1120,31 @@ impl WaylandWindowInner {
}
}
fn set_text_cursor_position(&mut self, rect: Rect) {
let surface_id = wl_id(&*self.surface);
let conn = Connection::get().unwrap().wayland();
if surface_id == *conn.active_surface_id.borrow() {
if self.text_cursor.map(|prior| prior != rect).unwrap_or(true) {
self.text_cursor.replace(rect);
conn.environment.with_inner(|env| {
if let Some(input) = env
.input_handler()
.get_text_input_for_surface(&self.surface)
{
input.set_cursor_rectangle(
rect.min_x() as i32,
rect.min_y() as i32,
rect.width() as i32,
rect.height() as i32,
);
input.commit();
}
});
}
}
}
/// Change the title for the window manager
fn set_title(&mut self, title: String) {
if let Some(last_title) = self.title.as_ref() {

View File

@ -344,7 +344,7 @@ impl WindowOps for Window {
match self {
Self::X11(x) => x.set_text_cursor_position(cursor),
#[cfg(feature = "wayland")]
Self::Wayland(_) => {}
Self::Wayland(w) => w.set_text_cursor_position(cursor),
}
}