Add isolated native Rime service and MiSans candidate UI
This commit is contained in:
1 parent
f21167e1a2
commit
8e9a5838ab
28 files changed
+2381
-17
No files matched your search
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -100,7 +100,7 @@ render_elements! {
|
||||
Surface=SurfaceRenderElement,
|
||||
Memory=MemoryRenderElement,
|
||||
Solid=SolidColorRenderElement,
|
||||
LockSurface=WaylandSurfaceRenderElement<GlesRenderer>,
|
||||
RawSurface=WaylandSurfaceRenderElement<GlesRenderer>,
|
||||
Border=SharedPixelShaderElement,
|
||||
Blur=BlurRenderElement,
|
||||
Effect=PhysicalShaderElement,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user