Give touch-opened OnDemand layers keyboard focus
Remember and consume the opening interaction instead of depending on pointer focus. Revoke pending focus after another interaction, focus change, cancellation or unmapping, and retain lock and grab safeguards. Fixes #54
This commit is contained in:
1 parent
de6e66460d
commit
17df20089b
5 files changed
+122
-13
No files matched your search
@@ -65,6 +65,13 @@ impl WlrLayerShellHandler for Ferese {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn layer_destroyed(&mut self, surface: LayerSurface) {
|
fn layer_destroyed(&mut self, surface: LayerSurface) {
|
||||||
|
if self
|
||||||
|
.pending_layer_focus
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|(target, _)| target == surface.wl_surface())
|
||||||
|
{
|
||||||
|
self.pending_layer_focus = None;
|
||||||
|
}
|
||||||
let Some(layer) = self
|
let Some(layer) = self
|
||||||
.space
|
.space
|
||||||
.layer_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL)
|
.layer_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL)
|
||||||
@@ -137,6 +144,14 @@ pub fn handle_commit(state: &mut Ferese, surface: &WlSurface) {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if !state.layer_mapped_and_visible(&layer)
|
||||||
|
&& state
|
||||||
|
.pending_layer_focus
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|(target, _)| target == surface)
|
||||||
|
{
|
||||||
|
state.pending_layer_focus = None;
|
||||||
|
}
|
||||||
let policy = layer.cached_state();
|
let policy = layer.cached_state();
|
||||||
let current = LayoutState::current(&layer);
|
let current = LayoutState::current(&layer);
|
||||||
let previous = layer
|
let previous = layer
|
||||||
@@ -159,22 +174,24 @@ pub fn handle_commit(state: &mut Ferese, surface: &WlSurface) {
|
|||||||
// Configure acknowledgement and keyboard policy are independent of layout.
|
// Configure acknowledgement and keyboard policy are independent of layout.
|
||||||
layer.layer_surface().send_pending_configure();
|
layer.layer_surface().send_pending_configure();
|
||||||
|
|
||||||
// A click can enable keyboard interaction after its button event was
|
// Consume the opening interaction once. Later commits, pointer hover, or
|
||||||
// delivered. Give that layer focus without requiring a second click.
|
// another input target must not give a panel a fresh claim to focus.
|
||||||
let pointer_on_layer = requested_focus
|
let opening_interaction = if requested_focus
|
||||||
&& !state.seat.get_keyboard().is_some_and(|keyboard| keyboard.is_grabbed())
|
|
||||||
&& state
|
&& state
|
||||||
.seat
|
.pending_layer_focus
|
||||||
.get_pointer()
|
.as_ref()
|
||||||
.and_then(|pointer| pointer.current_focus())
|
.is_some_and(|(target, _)| target == surface)
|
||||||
.is_some_and(|surface| {
|
{
|
||||||
state
|
state
|
||||||
.space
|
.pending_layer_focus
|
||||||
.layer_for_surface(&surface, WindowSurfaceType::ALL)
|
.take()
|
||||||
.is_some_and(|focused| focused == layer)
|
.is_some_and(|(_, at)| at.elapsed() <= std::time::Duration::from_secs(5))
|
||||||
});
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
state.reconcile_layer_keyboard_focus();
|
state.reconcile_layer_keyboard_focus();
|
||||||
if pointer_on_layer
|
if opening_interaction
|
||||||
|
&& !state.seat.get_keyboard().is_some_and(|keyboard| keyboard.is_grabbed())
|
||||||
&& !state.session_lock.active()
|
&& !state.session_lock.active()
|
||||||
&& !state.input_capture.captures(1)
|
&& !state.input_capture.captures(1)
|
||||||
&& state.layer_keyboard_eligible(&layer)
|
&& state.layer_keyboard_eligible(&layer)
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ impl SeatHandler for Ferese {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&WlSurface>) {
|
fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&WlSurface>) {
|
||||||
|
self.pending_layer_focus = None;
|
||||||
// Smithay has already sent enter. last_enter uses a separate mutex;
|
// Smithay has already sent enter. last_enter uses a separate mutex;
|
||||||
// querying current_focus here would reenter the held keyboard lock.
|
// querying current_focus here would reenter the held keyboard lock.
|
||||||
if let Some(serial) = seat.get_keyboard().and_then(|keyboard| keyboard.last_enter()) {
|
if let Some(serial) = seat.get_keyboard().and_then(|keyboard| keyboard.last_enter()) {
|
||||||
|
|||||||
@@ -867,6 +867,7 @@ impl Ferese {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
InputEvent::TouchCancel { .. } => {
|
InputEvent::TouchCancel { .. } => {
|
||||||
|
self.pending_layer_focus = None;
|
||||||
self.consumed_touch_slots.clear();
|
self.consumed_touch_slots.clear();
|
||||||
let touch = self.seat.get_touch().expect("seat has touch capability");
|
let touch = self.seat.get_touch().expect("seat has touch capability");
|
||||||
touch.cancel(self);
|
touch.cancel(self);
|
||||||
@@ -1012,6 +1013,7 @@ impl Ferese {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn focus_window_at(&mut self, position: Point<f64, Logical>, serial: Serial, raise: bool) {
|
fn focus_window_at(&mut self, position: Point<f64, Logical>, serial: Serial, raise: bool) {
|
||||||
|
self.pending_layer_focus = None;
|
||||||
if self.input_capture.captures(1) {
|
if self.input_capture.captures(1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1044,6 +1046,14 @@ impl Ferese {
|
|||||||
// release an expanded panel's focus too.
|
// release an expanded panel's focus too.
|
||||||
self.restore_keyboard_focus();
|
self.restore_keyboard_focus();
|
||||||
}
|
}
|
||||||
|
if raise
|
||||||
|
&& layer.cached_state().keyboard_interactivity
|
||||||
|
== smithay::wayland::shell::wlr_layer::KeyboardInteractivity::None
|
||||||
|
{
|
||||||
|
// The client may enable OnDemand only after receiving this
|
||||||
|
// opening press. Touch does not establish pointer focus.
|
||||||
|
self.pending_layer_focus = Some((layer.wl_surface().clone(), std::time::Instant::now()));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -759,3 +759,82 @@ fn animated_overview_exit_revokes_top_focus_when_it_becomes_hidden() {
|
|||||||
"completion of the visibility transition revokes focus without a client commit"
|
"completion of the visibility transition revokes focus without a client commit"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn touch_opened_on_demand_layer_does_not_need_pointer_focus() {
|
||||||
|
if !crate::startup_tests::private_runtime(
|
||||||
|
"input::layer_focus_tests::touch_opened_on_demand_layer_does_not_need_pointer_focus",
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let (mut state, mut events, output) = fixture();
|
||||||
|
let (panel, mut wire) = layer(&mut state, &mut events, &output);
|
||||||
|
let pointer = state.seat.get_pointer().unwrap();
|
||||||
|
let keyboard = state.seat.get_keyboard().unwrap();
|
||||||
|
let inside = Point::from((20., 20.));
|
||||||
|
assert_eq!(pointer.current_focus(), None);
|
||||||
|
// TouchDown focuses the target before delivering the opening touch.
|
||||||
|
// Unlike the pointer regression, no pointer motion establishes focus.
|
||||||
|
state.focus_window_at(inside, SERIAL_COUNTER.next_serial(), true);
|
||||||
|
assert_eq!(keyboard.current_focus(), None);
|
||||||
|
request(&mut wire, 3, 4, &[2], None); // OnDemand.
|
||||||
|
request(&mut wire, 2, 6, &[], None);
|
||||||
|
dispatch(&mut events, &mut state);
|
||||||
|
assert_eq!(
|
||||||
|
keyboard.current_focus(),
|
||||||
|
Some(panel.wl_surface().clone()),
|
||||||
|
"opening must not require a second click"
|
||||||
|
);
|
||||||
|
state.focus_window_at(inside, SERIAL_COUNTER.next_serial(), true);
|
||||||
|
assert_eq!(
|
||||||
|
keyboard.current_focus(),
|
||||||
|
Some(panel.wl_surface().clone()),
|
||||||
|
"inside clicks keep the panel open"
|
||||||
|
);
|
||||||
|
|
||||||
|
let outside = Point::from((400., 300.));
|
||||||
|
let input_time = state.last_pointer_time;
|
||||||
|
pointer.motion(
|
||||||
|
&mut state,
|
||||||
|
None,
|
||||||
|
&MotionEvent {
|
||||||
|
location: outside,
|
||||||
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
|
time: input_time,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
state.focus_window_at(outside, SERIAL_COUNTER.next_serial(), true);
|
||||||
|
assert_eq!(keyboard.current_focus(), None, "empty-desktop clicks dismiss the panel");
|
||||||
|
request(&mut wire, 2, 6, &[], None);
|
||||||
|
dispatch(&mut events, &mut state);
|
||||||
|
assert_eq!(
|
||||||
|
keyboard.current_focus(),
|
||||||
|
None,
|
||||||
|
"later animation commits must not reclaim focus"
|
||||||
|
);
|
||||||
|
commit_policy(&mut state, &mut events, &mut wire, 0);
|
||||||
|
state.focus_window_at(inside, SERIAL_COUNTER.next_serial(), true);
|
||||||
|
state.focus_window_at(outside, SERIAL_COUNTER.next_serial(), true);
|
||||||
|
commit_policy(&mut state, &mut events, &mut wire, 2);
|
||||||
|
assert_eq!(
|
||||||
|
keyboard.current_focus(),
|
||||||
|
None,
|
||||||
|
"a later interaction cancels delayed focus"
|
||||||
|
);
|
||||||
|
commit_policy(&mut state, &mut events, &mut wire, 0);
|
||||||
|
pointer.motion(
|
||||||
|
&mut state,
|
||||||
|
Some((panel.wl_surface().clone(), (0., 0.).into())),
|
||||||
|
&MotionEvent {
|
||||||
|
location: inside,
|
||||||
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
|
time: input_time,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
commit_policy(&mut state, &mut events, &mut wire, 2);
|
||||||
|
assert_eq!(
|
||||||
|
keyboard.current_focus(),
|
||||||
|
None,
|
||||||
|
"pointer hover alone cannot open keyboard focus"
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -470,6 +470,7 @@ pub struct Ferese {
|
|||||||
pub(crate) tab_indicator_settings: crate::config::TabIndicatorSettings,
|
pub(crate) tab_indicator_settings: crate::config::TabIndicatorSettings,
|
||||||
pub(crate) tab_bars: HashMap<WorkspaceId, Vec<ferese_layout::TabBar>>,
|
pub(crate) tab_bars: HashMap<WorkspaceId, Vec<ferese_layout::TabBar>>,
|
||||||
pub(crate) consumed_pointer_buttons: HashSet<u32>,
|
pub(crate) consumed_pointer_buttons: HashSet<u32>,
|
||||||
|
pub(crate) pending_layer_focus: Option<(WlSurface, Instant)>,
|
||||||
pub(crate) consumed_touch_slots: HashSet<smithay::backend::input::TouchSlot>,
|
pub(crate) consumed_touch_slots: HashSet<smithay::backend::input::TouchSlot>,
|
||||||
pub(crate) focus_history: ferese_core::FocusHistory,
|
pub(crate) focus_history: ferese_core::FocusHistory,
|
||||||
pub(crate) focus_cycle: Option<ferese_core::FocusCycle>,
|
pub(crate) focus_cycle: Option<ferese_core::FocusCycle>,
|
||||||
@@ -785,6 +786,7 @@ impl Ferese {
|
|||||||
tab_indicator_settings: config.tab_indicator,
|
tab_indicator_settings: config.tab_indicator,
|
||||||
tab_bars: HashMap::new(),
|
tab_bars: HashMap::new(),
|
||||||
consumed_pointer_buttons: HashSet::new(),
|
consumed_pointer_buttons: HashSet::new(),
|
||||||
|
pending_layer_focus: None,
|
||||||
consumed_touch_slots: HashSet::new(),
|
consumed_touch_slots: HashSet::new(),
|
||||||
focus_history: Default::default(),
|
focus_history: Default::default(),
|
||||||
focus_cycle: None,
|
focus_cycle: None,
|
||||||
|
|||||||
Reference in new issue
Block a user