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()),
_ => (),
}
}
}
+17 -4
View File
@@ -9,7 +9,7 @@ use smithay::backend::input::{
KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, PointerMotionEvent, Switch,
SwitchState, SwitchToggleEvent, TouchEvent,
};
use smithay::input::keyboard::{FilterResult, keysyms};
use smithay::input::keyboard::{FilterResult, KeyboardSource, keysyms};
use smithay::input::pointer::{
AxisFrame, ButtonEvent, Focus, GestureSwipeBeginEvent, GestureSwipeEndEvent,
GestureSwipeUpdateEvent, GrabStartData, MotionEvent, PointerHandle, RelativeMotionEvent,
@@ -133,11 +133,23 @@ impl Ferese {
}
pub fn process_input_event<I: InputBackend>(&mut self, event: InputEvent<I>) {
self.process_input_event_inner(event);
self.process_input_event_from_source(event, KeyboardSource::MAIN);
}
pub(crate) fn process_input_event_from_source<I: InputBackend>(
&mut self,
event: InputEvent<I>,
source: KeyboardSource,
) {
self.process_input_event_inner(event, source);
self.poll_window_interaction();
}
fn process_input_event_inner<I: InputBackend>(&mut self, event: InputEvent<I>) {
fn process_input_event_inner<I: InputBackend>(
&mut self,
event: InputEvent<I>,
source: KeyboardSource,
) {
if self.input_capture.active() {
self.set_binding_mode("default");
}
@@ -329,7 +341,8 @@ impl Ferese {
let client_grab = keyboard
.with_grab(|_, grab| grab.is::<smithay::desktop::PopupKeyboardGrab<Self>>())
.unwrap_or(true);
let intercepted = keyboard.input::<(), _>(
let intercepted = keyboard.input_from_source::<(), _>(
source,
self,
keycode,
state,
@@ -78,6 +78,43 @@ binding "Super+r" binding-mode resize
binding-mode resize { binding h resize left; binding l binding-mode launch; }
binding-mode launch { binding h binding-mode default; }
"#;
#[test]
fn nested_focus_loss_releases_modifiers_and_preserves_auxiliary_keys() {
if !crate::startup_tests::private_runtime(
"input::binding_mode_tests::nested_focus_loss_releases_modifiers_and_preserves_auxiliary_keys",
) {
return;
}
let mut events = EventLoop::try_new().unwrap();
let mut state = state(&mut events);
let keyboard = state.seat.get_keyboard().unwrap();
key(&mut state, 64, true); // Alt
key(&mut state, 133, true); // Super
let auxiliary = KeyboardSource::new_auxiliary();
keyboard.input_from_source::<(), _>(
auxiliary,
&mut state,
Keycode::new(38),
KeyState::Pressed,
SERIAL_COUNTER.next_serial(),
smithay::backend::input::InputTime::now(),
|_, _, _| FilterResult::Forward,
);
assert!(keyboard.modifier_state().alt);
assert!(keyboard.modifier_state().logo);
state.release_nested_keyboard();
assert!(!keyboard.modifier_state().alt);
assert!(!keyboard.modifier_state().logo);
assert_eq!(
keyboard.pressed_keys(),
[Keycode::new(38)].into_iter().collect()
);
assert!(state.binding_mode_pressed.is_empty());
assert!(state.binding_mode_repeats.is_empty());
keyboard.release_source(&mut state, auxiliary);
assert!(keyboard.pressed_keys().is_empty());
}
fn state(events: &mut EventLoop<'static, Ferese>) -> Ferese {
let (_, mut config, _) = crate::config::prepare(SOURCE, std::path::Path::new(".")).unwrap();
config.wallpaper.path = None;
+37 -11
View File
@@ -469,8 +469,31 @@ pub(super) fn rounded_window_elements(
let radius = corners.radius;
let surface = toplevel.wl_surface();
let mut input_popups = Vec::new();
let mut content = PopupManager::popups_for_surface(surface)
.flat_map(|(popup, popup_offset)| {
if matches!(popup, smithay::desktop::PopupKind::InputMethod(_)) {
// InputMethod geometry is the parent's global rectangle, not
// an xdg window-geometry origin. IME popups also extend beyond
// the parent and must not enter its rounded-window crop.
let offset = (geometry.loc + popup_offset).to_physical_precise_round(scale);
input_popups.extend(
render_elements_from_surface_tree::<
GlesRenderer,
WaylandSurfaceRenderElement<GlesRenderer>,
>(
renderer,
popup.wl_surface(),
location + offset,
scale,
alpha,
RenderElementKind::Unspecified,
)
.into_iter()
.map(AnimatedWindowRenderElement::RawSurface),
);
return Vec::new();
}
let offset = (geometry.loc + popup_offset - popup.geometry().loc)
.to_physical_precise_round(scale);
@@ -487,6 +510,7 @@ pub(super) fn rounded_window_elements(
)
.into_iter()
.map(WindowContentRenderElement::from)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
content.extend(
@@ -518,17 +542,19 @@ pub(super) fn rounded_window_elements(
}),
);
constrain_render_elements(
content,
location,
physical_constrain,
reference,
behavior,
ConstrainAlign::TOP | ConstrainAlign::LEFT,
scale,
)
.map(Into::into)
.collect()
input_popups.extend(
constrain_render_elements(
content,
location,
physical_constrain,
reference,
behavior,
ConstrainAlign::TOP | ConstrainAlign::LEFT,
scale,
)
.map(Into::into),
);
input_popups
}
pub(super) fn resize_content_behavior(intentional_scale: bool) -> ConstrainScaleBehavior {
+1 -1
View File
@@ -100,7 +100,7 @@ render_elements! {
Surface=SurfaceRenderElement,
Memory=MemoryRenderElement,
Solid=SolidColorRenderElement,
LockSurface=WaylandSurfaceRenderElement<GlesRenderer>,
RawSurface=WaylandSurfaceRenderElement<GlesRenderer>,
Border=SharedPixelShaderElement,
Blur=BlurRenderElement,
Effect=PhysicalShaderElement,
+7
View File
@@ -94,6 +94,7 @@ use smithay::wayland::single_pixel_buffer::SinglePixelBufferState;
use smithay::wayland::socket::ListeningSocketSource;
use smithay::wayland::text_input::TextInputManagerState;
use smithay::wayland::viewporter::ViewporterState;
use smithay::wayland::virtual_keyboard::VirtualKeyboardManagerState;
use smithay::wayland::xdg_activation::XdgActivationState;
use smithay::wayland::xdg_foreign::XdgForeignState;
use smithay::wayland::xdg_toplevel_icon::XdgToplevelIconManager;
@@ -571,6 +572,7 @@ pub struct Ferese {
pub idle_inhibit_state: IdleInhibitManagerState,
pub idle_notifier_state: IdleNotifierState<Self>,
pub input_method_manager_state: InputMethodManagerState,
pub virtual_keyboard_manager_state: VirtualKeyboardManagerState,
pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState,
pub output_manager_state: OutputManagerState,
pub pointer_constraints_state: PointerConstraintsState,
@@ -674,6 +676,10 @@ impl Ferese {
std::env::var_os("FERESE_ENABLE_INPUT_METHOD").is_some_and(|value| value == "1");
let input_method_manager_state =
InputMethodManagerState::new::<Self, _>(&display_handle, move |_| input_method_enabled);
let virtual_keyboard_manager_state =
VirtualKeyboardManagerState::new::<Self, _>(&display_handle, move |_| {
input_method_enabled
});
let keyboard_shortcuts_inhibit_state =
KeyboardShortcutsInhibitState::new::<Self>(&display_handle);
let shortcut_inhibit_enabled =
@@ -893,6 +899,7 @@ impl Ferese {
idle_inhibit_state,
idle_notifier_state,
input_method_manager_state,
virtual_keyboard_manager_state,
keyboard_shortcuts_inhibit_state,
output_manager_state,
pointer_constraints_state,
+16
View File
@@ -132,6 +132,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
pointer = ?state.seat.get_pointer().map(|pointer| pointer.current_location()),
"host input in nested output");
}
WinitEvent::Focus(false) => state.release_nested_keyboard(),
WinitEvent::Redraw => {
if !drawable.get() || !usable_size(backend.window_size()) {
drawable.set(false);
@@ -251,6 +252,21 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
Ok(())
}
impl Ferese {
pub(crate) fn release_nested_keyboard(&mut self) {
// Host shortcuts can consume releases while this window is unfocused.
// Release only the physical/nested source, including modifiers, without
// executing compositor shortcuts. Auxiliary keyboards retain ownership.
if let Some(keyboard) = self.seat.get_keyboard() {
keyboard.release_source(self, smithay::input::keyboard::KeyboardSource::MAIN);
}
self.binding_mode_pressed.clear();
self.binding_mode_repeats.clear();
self.intercepted_keys.clear();
self.cancel_focus_cycle();
}
}
fn configure_nested_protocols(state: &mut Ferese) {
// Winit's swap has no host presentation timestamp or retrace counter.
// Do not advertise timing we cannot report accurately.