Add isolated native Rime service and MiSans candidate UI

This commit is contained in:
aria committed 2026-10-28 00:58:38 +09:00
1 parent f21167e1a2
commit 8e9a5838ab
28 files changed
+2381 -17

No files matched your search

+1
View File
@@ -81,6 +81,7 @@ impl CompositorHandler for Ferese {
layer_shell::handle_commit(self, surface);
xdg_shell::handle_commit(self, surface);
self.position_committed_input_popup(surface);
crate::backends::direct::render_surface(self, surface);
}
+2
View File
@@ -116,6 +116,8 @@ delegate_protocols!(
misc::zwp_input_method_v2::server::zwp_input_method_manager_v2::ZwpInputMethodManagerV2,
misc::zwp_input_method_v2::server::zwp_input_method_v2::ZwpInputMethodV2,
misc::zwp_input_method_v2::server::zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2,
misc::zwp_virtual_keyboard_v1::server::zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1,
misc::zwp_virtual_keyboard_v1::server::zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1,
wlr::data_control::v1::server::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1,
wlr::data_control::v1::server::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1,
wlr::data_control::v1::server::zwlr_data_control_source_v1::ZwlrDataControlSourceV1,
+95
View File
@@ -1,12 +1,14 @@
use smithay::desktop::{PopupKind, WindowSurfaceType, layer_map_for_output};
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::utils::{Logical, Rectangle};
use smithay::utils::{Point, Size};
use smithay::wayland::input_method::{InputMethodHandler, PopupSurface};
use crate::Ferese;
impl InputMethodHandler for Ferese {
fn new_popup(&mut self, surface: PopupSurface) {
self.position_input_popup(&surface);
if let Err(error) = self
.popups
.track_popup(PopupKind::InputMethod(surface.clone()))
@@ -23,6 +25,7 @@ impl InputMethodHandler for Ferese {
}
fn popup_repositioned(&mut self, surface: PopupSurface) {
self.position_input_popup(&surface);
crate::backends::direct::render_surface(self, surface.wl_surface());
}
@@ -58,3 +61,95 @@ impl InputMethodHandler for Ferese {
.unwrap_or_default()
}
}
impl Ferese {
pub(crate) fn position_committed_input_popup(&mut self, surface: &WlSurface) {
if let Some(PopupKind::InputMethod(popup)) = self.popups.find_popup(surface) {
self.position_input_popup(&popup);
}
}
fn position_input_popup(&self, popup: &PopupSurface) {
let Some(parent) = popup.get_parent() else {
return;
};
let parent = self.parent_geometry(&parent.surface);
let cursor = popup.text_input_rectangle();
let caret = parent.loc + cursor.loc;
let output = self
.space
.outputs()
.filter_map(|output| self.space.output_geometry(output))
.find(|geometry| geometry.contains(caret))
.or_else(|| {
self.space
.outputs()
.find_map(|output| self.space.output_geometry(output))
});
let Some(output) = output else {
return;
};
let bounds = smithay::desktop::utils::bbox_from_surface_tree(popup.wl_surface(), (0, 0));
popup.set_location(input_popup_location(parent, cursor, bounds.size, output));
}
}
fn input_popup_location(
parent: Rectangle<i32, Logical>,
cursor: Rectangle<i32, Logical>,
size: Size<i32, Logical>,
output: Rectangle<i32, Logical>,
) -> Point<i32, Logical> {
let caret = parent.loc + cursor.loc;
let mut y = caret.y + cursor.size.h;
if y + size.h > output.loc.y + output.size.h {
y = caret.y - size.h;
}
let x = caret.x.clamp(
output.loc.x,
(output.loc.x + output.size.w - size.w).max(output.loc.x),
);
let y = y.clamp(
output.loc.y,
(output.loc.y + output.size.h - size.h).max(output.loc.y),
);
Point::from((x, y)) - parent.loc
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn input_popup_uses_cursor_and_flips_at_output_edges() {
let output = Rectangle::new((0, 0).into(), (1280, 800).into());
let parent = Rectangle::new((400, 36).into(), (880, 560).into());
assert_eq!(
input_popup_location(
parent,
Rectangle::new((28, 164).into(), (1, 24).into()),
(420, 219).into(),
output
),
Point::from((28, 188))
);
assert_eq!(
input_popup_location(
Rectangle::default(),
Rectangle::new((1230, 750).into(), (1, 20).into()),
(420, 220).into(),
output
),
Point::from((860, 530))
);
let left = Rectangle::new((-1280, 0).into(), (1280, 800).into());
assert_eq!(
input_popup_location(
Rectangle::default(),
Rectangle::new((-10, 780).into(), (1, 20).into()),
(420, 220).into(),
left
),
Point::from((-420, 560))
);
}
}
+1
View File
@@ -11,6 +11,7 @@ pub(crate) mod screenshot_worker;
pub(crate) mod security_context;
#[cfg(test)]
mod toplevel_icon_tests;
mod virtual_keyboard;
pub(crate) mod window_capture;
mod xdg_shell;
@@ -0,0 +1,85 @@
use smithay::backend::input::{Event, InputEvent, KeyboardKeyEvent};
use smithay::utils::SERIAL_COUNTER;
use smithay::wayland::input_method::InputMethodSeat;
use smithay::wayland::virtual_keyboard::{
VirtualKeyboardBackend, VirtualKeyboardHandler, VirtualKeyboardSpecialEvent,
};
use crate::Ferese;
impl VirtualKeyboardHandler for Ferese {
fn process_virtual_keyboard_event(&mut self, event: InputEvent<VirtualKeyboardBackend>) {
let keyboard = self.seat.get_keyboard().expect("seat has a keyboard");
if self.session_lock.active() {
// Synthetic input never enters a lock surface. Still reconcile
// keys from a departing/previously active auxiliary device.
let device = match &event {
InputEvent::Keyboard { event } => Some(event.device()),
InputEvent::DeviceRemoved { device } => Some(device.clone()),
_ => None,
};
if let Some(device) = device {
keyboard.release_source(self, device.source());
}
return;
}
match event {
InputEvent::DeviceAdded { device }
| InputEvent::Special(VirtualKeyboardSpecialEvent::KeymapChanged { device }) => {
if let Some(keymap) = device.keymap() {
let current = keyboard.with_xkb_state(self, |context| {
context.xkb().lock().unwrap().keymap_as_string()
});
if current != *keymap
&& let Err(error) =
keyboard.set_keymap_from_string(self, keymap.to_string())
{
tracing::warn!(%error, "invalid virtual keyboard keymap");
}
}
}
InputEvent::Keyboard { event } => {
let device = event.device();
let grab_client = self.seat.input_method().keyboard_grab_client();
if grab_client.is_some() && grab_client == device.client() {
// An IME forwards keys it did not consume. The physical path
// already updated XKB and compositor shortcuts: forwarding
// through that path again would recurse into the IME grab.
keyboard.input_forward_bypassing_grab(
self,
event.key_code(),
event.state(),
SERIAL_COUNTER.next_serial(),
event.time(),
true,
);
} else {
self.process_input_event_from_source::<VirtualKeyboardBackend>(
InputEvent::Keyboard { event },
device.source(),
);
}
}
InputEvent::Special(VirtualKeyboardSpecialEvent::Modifiers {
device,
mods_depressed,
mods_latched,
mods_locked,
group,
}) => {
let grab_client = self.seat.input_method().keyboard_grab_client();
if grab_client.is_some() && grab_client == device.client() {
// The physical path already owns these modifiers. An
// asynchronous IME echo must not restore an old mask after
// the physical release has arrived.
return;
}
keyboard.with_xkb_state(self, |mut context| {
context.set_modifier_mask(mods_depressed, mods_latched, mods_locked, group)
});
}
InputEvent::DeviceRemoved { device } => keyboard.release_source(self, device.source()),
_ => (),
}
}
}