use super::*;
use crate::compositor::focus::read;
use crate::compositor::interaction::state::ViewportPanAnim;
use crate::compositor::surface::stack_focus_target_for_node;
use eventline::debug;
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
use smithay::reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface};
use smithay::utils::SERIAL_COUNTER;
use smithay::wayland::selection::data_device::set_data_device_focus;
use smithay::wayland::selection::primary_selection::set_primary_focus;
use crate::compositor::ctx::FocusCtx;
use smithay::input::Seat;
use std::ops::{Deref, DerefMut};
use std::time::{Duration, Instant};
pub(crate) fn on_seat_focus_changed(
ctx: &FocusCtx<'_>,
seat: &Seat<Halley>,
focused: Option<&WlSurface>,
) {
debug!(
"seat focus_changed -> {:?}",
focused.map(|wl| format!("{:?}", wl.id()))
);
let client = focused.and_then(|wl| wl.client());
set_data_device_focus(ctx.display_handle, seat, client.clone());
set_primary_focus(ctx.display_handle, seat, client);
}
pub(crate) struct FocusSystemController<T> {
st: T,
}
pub(crate) fn focus_system_controller<T>(st: T) -> FocusSystemController<T> {
FocusSystemController { st }
}
impl<T: Deref<Target = Halley>> Deref for FocusSystemController<T> {
type Target = Halley;
fn deref(&self) -> &Self::Target {
self.st.deref()
}
}
impl<T: DerefMut<Target = Halley>> DerefMut for FocusSystemController<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.st.deref_mut()
}
}
pub fn wl_surface_for_node(st: &Halley, id: NodeId) -> Option<WlSurface> {
for top in st.platform.xdg_shell_state.toplevel_surfaces() {
let wl = top.wl_surface().clone();
if st.model.surface_to_node.get(&wl.id()).copied() == Some(id) {
return Some(wl);
}
}
None
}
pub(crate) fn update_selection_focus_from_surface(st: &Halley, surface: Option<&WlSurface>) {
let client = surface.and_then(|wl| wl.client());
set_data_device_focus(
&st.platform.display_handle,
&st.platform.seat,
client.clone(),
);
set_primary_focus(&st.platform.display_handle, &st.platform.seat, client);
}
pub(crate) fn focus_pointer_target(
st: &mut Halley,
node_id: NodeId,
hold_ms: u64,
now: Instant,
) -> NodeId {
let focus_target = stack_focus_target_for_node(st, node_id).unwrap_or(node_id);
st.set_recent_top_node(focus_target, now + Duration::from_millis(1200));
st.set_interaction_focus(Some(focus_target), hold_ms, now);
focus_target
}
pub(crate) fn surface_is_fully_visible_on_monitor(st: &Halley, monitor: &str, id: NodeId) -> bool {
read::surface_is_fully_visible_on_monitor(st, monitor, id)
}
pub(crate) fn minimal_reveal_center_for_surface_on_monitor(
st: &Halley,
monitor: &str,
id: NodeId,
) -> Option<Vec2> {
read::minimal_reveal_center_for_surface_on_monitor(st, monitor, id)
}
#[cfg(test)]
pub(crate) fn fullscreen_focus_override(st: &Halley, requested: Option<NodeId>) -> Option<NodeId> {
read::fullscreen_focus_override(st, requested)
}
pub fn last_focused_surface_node(st: &Halley) -> Option<NodeId> {
read::last_focused_surface_node(st)
}
pub fn last_focused_surface_node_for_monitor(st: &Halley, monitor: &str) -> Option<NodeId> {
read::last_focused_surface_node_for_monitor(st, monitor)
}
pub fn last_input_surface_node(st: &Halley) -> Option<NodeId> {
read::last_input_surface_node(st)
}
pub fn last_input_surface_node_for_monitor(st: &Halley, monitor: &str) -> Option<NodeId> {
read::last_input_surface_node_for_monitor(st, monitor)
}
impl<T: DerefMut<Target = Halley>> FocusSystemController<T> {
pub fn set_app_focused(&mut self, focused: bool) {
self.model.focus_state.app_focused = focused;
}
pub(crate) fn clear_keyboard_focus(&mut self) {
let Some(keyboard) = self.platform.seat.get_keyboard() else {
return;
};
keyboard.set_focus(self, None, SERIAL_COUNTER.next_serial());
self.update_selection_focus_from_surface(None);
}
}
impl<T: DerefMut<Target = Halley>> FocusSystemController<T> {
pub(crate) const VIEWPORT_PAN_DURATION_MS: u64 = 260;
const SPAWN_VIEW_HANDOFF_PAN_RATIO: f32 = 0.35;
const SPAWN_VIEW_HANDOFF_FOCUS_RATIO: f32 = 0.25;
pub(crate) fn fullscreen_focus_override(&self, requested: Option<NodeId>) -> Option<NodeId> {
read::fullscreen_focus_override(self, requested)
}
pub fn apply_wayland_focus_state(&mut self, id: Option<NodeId>) {
if crate::protocol::wayland::session_lock::session_lock_active(self) {
crate::protocol::wayland::session_lock::reassert_keyboard_focus_if_drifted(self);
return;
}
let focus_id = self.fullscreen_focus_override(id).or(id);
if let Some(fid) = focus_id
&& self
.model
.fullscreen_state
.fullscreen_suspended_node
.values()
.any(|&nid| nid == fid)
{
if let Some(entry) = self
.model
.fullscreen_state
.fullscreen_restore
.get(&fid)
.copied()
{
let target_monitor = self.monitor_for_node_or_current(fid);
if let Some(space) = self.model.monitor_state.monitors.get_mut(&target_monitor) {
space.viewport.center = entry.viewport_center;
space.camera_target_center = entry.viewport_center;
}
if self.model.monitor_state.current_monitor == target_monitor {
self.model.viewport.center = entry.viewport_center;
self.model.camera_target_center = self.model.viewport.center;
self.runtime.tuning.viewport_center = self.model.viewport.center;
self.input.interaction_state.viewport_pan_anim = None;
}
}
self.enter_xdg_fullscreen(fid, None, Instant::now());
}
self.model.monitor_state.layer_keyboard_focus = None;
let requested_focus_surface = focus_id.and_then(|fid| self.wl_surface_for_node(fid));
let active_constrained_surface =
crate::compositor::interaction::pointer::active_constrained_pointer_surface(self)
.map(|(surface, _)| surface);
let locked_surface_node = active_constrained_surface
.as_ref()
.and_then(|surface| self.model.surface_to_node.get(&surface.id()).copied());
let keep_locked_focus =
locked_surface_node.is_some_and(|nid| self.is_fullscreen_active(nid));
let focus_surface = if keep_locked_focus {
active_constrained_surface
.clone()
.or(requested_focus_surface.clone())
} else {
requested_focus_surface.clone()
};
if !keep_locked_focus
&& active_constrained_surface.as_ref().is_some_and(|surface| {
Some(surface.id()) != focus_surface.as_ref().map(|wl| wl.id())
})
{
crate::compositor::interaction::pointer::release_active_pointer_constraint(self);
}
if let Some(keyboard) = self.platform.seat.get_keyboard() {
keyboard.set_focus(self, focus_surface.clone(), SERIAL_COUNTER.next_serial());
}
self.update_selection_focus_from_surface(focus_surface.as_ref());
for top in self.platform.xdg_shell_state.toplevel_surfaces() {
let key = top.wl_surface().id();
let node_id = self.model.surface_to_node.get(&key).copied();
let activated = node_id.is_some_and(|nid| {
self.model
.monitor_state
.node_monitor
.get(&nid)
.and_then(|monitor| self.model.focus_state.monitor_focus.get(monitor))
.copied()
== Some(nid)
|| Some(nid) == focus_id
});
let state_changed = top.with_pending_state(|s| {
let was_active = s.states.contains(xdg_toplevel::State::Activated);
if activated {
s.states.set(xdg_toplevel::State::Activated);
} else {
s.states.unset(xdg_toplevel::State::Activated);
}
self.apply_toplevel_tiled_hint(s);
was_active != activated
});
if state_changed {
top.send_configure();
}
}
}
pub fn update_focus_tracking_for_surface(&mut self, fid: NodeId, now_ms: u64) {
let Some(node_state) = self
.model
.field
.node(fid)
.map(|n| (n.kind.clone(), n.state.clone()))
else {
return;
};
if node_state.0 != halley_core::field::NodeKind::Surface
|| !self.model.field.participates_in_field_activity(fid)
|| !self.model.field.is_visible(fid)
{
return;
}
self.model
.focus_state
.last_surface_focus_ms
.insert(fid, now_ms);
self.model
.focus_state
.outside_focus_ring_since_ms
.remove(&fid);
if self.model.focus_state.suppress_trail_record_once {
self.model.focus_state.suppress_trail_record_once = false;
} else {
self.record_focus_trail_visit(fid);
}
if node_state.1 == halley_core::field::NodeState::Active {
let _ = self.model.field.touch(fid, now_ms);
let _ = self.model.field.set_decay_level(fid, DecayLevel::Hot);
if self.runtime.tuning.restore_last_active_on_pan_return {
self.model.focus_state.pan_restore_active_focus = Some(fid);
}
}
}
pub fn note_pan_activity(&mut self, now: Instant) {
self.input.interaction_state.viewport_pan_anim = None;
let now_ms = self.now_ms(now);
self.input.interaction_state.pan_dominant_until_ms = now_ms.saturating_add(220);
let current_monitor = self.model.monitor_state.current_monitor.clone();
let viewport_center = self.model.viewport.center;
let spawn = self.spawn_monitor_state_mut(current_monitor.as_str());
spawn.spawn_last_pan_ms = now_ms;
spawn.spawn_pan_start_center.get_or_insert(viewport_center);
if self.runtime.tuning.restore_last_active_on_pan_return
&& self.model.focus_state.pan_restore_active_focus.is_none()
{
self.model.focus_state.pan_restore_active_focus =
read::last_focused_active_surface_node(self);
}
self.input.interaction_state.suspend_overlap_resolve = false;
self.input.interaction_state.suspend_state_checks = false;
self.request_maintenance();
}
fn spawn_view_handoff_pan_distance(&self) -> f32 {
self.model.viewport.size.x.min(self.model.viewport.size.y)
* Self::SPAWN_VIEW_HANDOFF_PAN_RATIO
}
fn spawn_view_handoff_focus_distance(&self) -> f32 {
self.model.viewport.size.x.hypot(self.model.viewport.size.y)
* Self::SPAWN_VIEW_HANDOFF_FOCUS_RATIO
}
pub(crate) fn note_pan_viewport_change(&mut self, _now: Instant) {
let current_monitor = self.model.monitor_state.current_monitor.clone();
if self
.spawn_monitor_state(current_monitor.as_str())
.spawn_anchor_mode
== crate::compositor::spawn::state::SpawnAnchorMode::View
{
self.spawn_monitor_state_mut(current_monitor.as_str())
.spawn_view_anchor = self.model.viewport.center;
}
self.request_maintenance();
let Some(start_center) = self
.spawn_monitor_state(current_monitor.as_str())
.spawn_pan_start_center
else {
return;
};
let moved = ((self.model.viewport.center.x - start_center.x).powi(2)
+ (self.model.viewport.center.y - start_center.y).powi(2))
.sqrt();
if moved < self.spawn_view_handoff_pan_distance() {
return;
}
let focus_far = self
.last_input_surface_node()
.and_then(|id| self.model.field.node(id))
.map(|node| {
let dx = self.model.viewport.center.x - node.pos.x;
let dy = self.model.viewport.center.y - node.pos.y;
dx.hypot(dy) >= self.spawn_view_handoff_focus_distance()
})
.unwrap_or(true);
if !focus_far {
return;
}
let viewport_center = self.model.viewport.center;
let spawn = self.spawn_monitor_state_mut(current_monitor.as_str());
spawn.spawn_anchor_mode = crate::compositor::spawn::state::SpawnAnchorMode::View;
spawn.spawn_view_anchor = viewport_center;
spawn.spawn_patch = None;
spawn.spawn_pan_start_center = Some(viewport_center);
self.model.focus_state.pan_restore_active_focus = None;
}
pub fn set_pan_restore_focus_target(&mut self, id: NodeId) {
self.model.focus_state.pan_restore_active_focus = Some(id);
}
pub fn animate_viewport_center_to(&mut self, target_center: Vec2, now: Instant) -> bool {
self.animate_viewport_center_to_delayed(target_center, now, 0)
}
pub fn animate_viewport_center_to_delayed(
&mut self,
target_center: Vec2,
now: Instant,
delay_ms: u64,
) -> bool {
let from = self.model.viewport.center;
let dx = target_center.x - from.x;
let dy = target_center.y - from.y;
if dx.abs() < 0.25 && dy.abs() < 0.25 {
return false;
}
self.input.interaction_state.viewport_pan_anim = Some(ViewportPanAnim {
start_ms: self.now_ms(now),
delay_ms,
duration_ms: Self::VIEWPORT_PAN_DURATION_MS,
from_center: from,
to_center: target_center,
});
true
}
pub(crate) fn tick_viewport_pan_animation(&mut self, now_ms: u64) {
let Some(anim) = &self.input.interaction_state.viewport_pan_anim else {
return;
};
if now_ms <= anim.start_ms.saturating_add(anim.delay_ms) {
self.model.viewport.center = anim.from_center;
self.model.camera_target_center = self.model.viewport.center;
self.runtime.tuning.viewport_center = self.model.viewport.center;
return;
}
let dur = anim.duration_ms.max(1);
let elapsed_ms = now_ms.saturating_sub(anim.start_ms.saturating_add(anim.delay_ms));
let t = (elapsed_ms as f32 / dur as f32).clamp(0.0, 1.0);
let e = if t < 0.5 {
4.0 * t * t * t
} else {
1.0 - (-2.0 * t + 2.0).powf(3.0) * 0.5
};
self.model.viewport.center = Vec2 {
x: anim.from_center.x + (anim.to_center.x - anim.from_center.x) * e,
y: anim.from_center.y + (anim.to_center.y - anim.from_center.y) * e,
};
self.model.camera_target_center = self.model.viewport.center;
self.runtime.tuning.viewport_center = self.model.viewport.center;
if t >= 1.0 {
self.input.interaction_state.viewport_pan_anim = None;
}
}
pub(crate) fn maybe_pan_to_restored_focus_on_close(
&mut self,
monitor: &str,
id: NodeId,
now: Instant,
) -> bool {
read::maybe_pan_to_restored_focus_on_close(self, monitor, id, now)
}
pub fn begin_resize_interaction(&mut self, id: NodeId, now: Instant) {
self.input.interaction_state.resize_active = Some(id);
self.input.interaction_state.resize_static_node = Some(id);
self.input.interaction_state.resize_static_lock_pos = None;
self.input.interaction_state.resize_static_until_ms =
self.now_ms(now).saturating_add(60_000);
self.input.interaction_state.suspend_overlap_resolve = true;
self.input.interaction_state.suspend_state_checks = true;
self.set_interaction_focus(Some(id), 60_000, now);
let now_ms = self.now_ms(now);
if self.model.field.participates_in_field_activity(id) {
let _ = self.model.field.touch(id, now_ms);
let _ = self.model.field.set_decay_level(id, DecayLevel::Hot);
}
self.model
.workspace_state
.manual_collapsed_nodes
.remove(&id);
self.request_maintenance();
}
pub fn end_resize_interaction(&mut self, now: Instant) {
let ended = self.input.interaction_state.resize_active.take();
if let Some(id) = ended {
self.input.interaction_state.resize_static_node = Some(id);
self.input.interaction_state.resize_static_lock_pos =
self.model.field.node(id).map(|n| n.pos);
self.input.interaction_state.resize_static_until_ms =
self.now_ms(now).saturating_add(120);
self.set_interaction_focus(Some(id), 30_000, now);
} else {
self.input.interaction_state.resize_static_lock_pos = None;
self.set_interaction_focus(None, 0, now);
}
self.input.interaction_state.suspend_state_checks = false;
self.input.interaction_state.suspend_overlap_resolve = false;
self.resolve_surface_overlap();
self.request_maintenance();
}
pub fn resolve_overlap_now(&mut self) {
let saved_suspend = self.input.interaction_state.suspend_overlap_resolve;
self.input.interaction_state.suspend_overlap_resolve = false;
self.resolve_surface_overlap();
self.input.interaction_state.suspend_overlap_resolve = saved_suspend;
}
pub fn set_last_active_size_now(&mut self, id: NodeId, size: Vec2) {
self.model.workspace_state.last_active_size.insert(id, size);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn last_input_surface_prefers_current_monitor_local_focus() {
let tuning = halley_config::RuntimeTuning::default();
let dh = smithay::reexports::wayland_server::Display::<Halley>::new()
.expect("display")
.handle();
let mut state = Halley::new_for_test(&dh, tuning);
let left = state.model.field.spawn_surface(
"left",
Vec2 { x: -200.0, y: 0.0 },
Vec2 { x: 100.0, y: 80.0 },
);
let right = state.model.field.spawn_surface(
"right",
Vec2 { x: 200.0, y: 0.0 },
Vec2 { x: 100.0, y: 80.0 },
);
state.assign_node_to_monitor(left, "default");
state.assign_node_to_monitor(right, "other");
state.model.focus_state.primary_interaction_focus = Some(right);
state
.model
.focus_state
.last_surface_focus_ms
.insert(left, 1);
state
.model
.focus_state
.last_surface_focus_ms
.insert(right, 2);
state
.model
.focus_state
.monitor_focus
.insert("default".to_string(), left);
state
.model
.focus_state
.monitor_focus
.insert("other".to_string(), right);
assert_eq!(
state.last_input_surface_node_for_monitor("default"),
Some(left)
);
assert_eq!(
state.last_input_surface_node_for_monitor("other"),
Some(right)
);
}
#[test]
fn fullscreen_focus_override_stays_on_requested_monitor() {
let mut tuning = halley_config::RuntimeTuning::default();
tuning.tty_viewports = vec![
halley_config::ViewportOutputConfig {
connector: "left".to_string(),
enabled: true,
offset_x: 0,
offset_y: 0,
width: 800,
height: 600,
refresh_rate: None,
transform_degrees: 0,
vrr: halley_config::ViewportVrrMode::Off,
focus_ring: None,
},
halley_config::ViewportOutputConfig {
connector: "right".to_string(),
enabled: true,
offset_x: 800,
offset_y: 0,
width: 800,
height: 600,
refresh_rate: None,
transform_degrees: 0,
vrr: halley_config::ViewportVrrMode::Off,
focus_ring: None,
},
];
let dh = smithay::reexports::wayland_server::Display::<Halley>::new()
.expect("display")
.handle();
let mut state = Halley::new_for_test(&dh, tuning);
let fullscreen_left = state.model.field.spawn_surface(
"fullscreen-left",
Vec2 { x: 400.0, y: 300.0 },
Vec2 { x: 200.0, y: 140.0 },
);
let right = state.model.field.spawn_surface(
"right",
Vec2 {
x: 1200.0,
y: 300.0,
},
Vec2 { x: 200.0, y: 140.0 },
);
state.assign_node_to_monitor(fullscreen_left, "left");
state.assign_node_to_monitor(right, "right");
state
.model
.fullscreen_state
.fullscreen_active_node
.insert("left".to_string(), fullscreen_left);
state.set_interaction_monitor("right");
state.set_focused_monitor("right");
assert_eq!(state.fullscreen_focus_override(Some(right)), Some(right));
assert_eq!(state.fullscreen_focus_override(None), None);
}
#[test]
fn fullscreen_focus_override_keeps_same_monitor_fullscreen() {
let mut tuning = halley_config::RuntimeTuning::default();
tuning.tty_viewports = vec![
halley_config::ViewportOutputConfig {
connector: "left".to_string(),
enabled: true,
offset_x: 0,
offset_y: 0,
width: 800,
height: 600,
refresh_rate: None,
transform_degrees: 0,
vrr: halley_config::ViewportVrrMode::Off,
focus_ring: None,
},
halley_config::ViewportOutputConfig {
connector: "right".to_string(),
enabled: true,
offset_x: 800,
offset_y: 0,
width: 800,
height: 600,
refresh_rate: None,
transform_degrees: 0,
vrr: halley_config::ViewportVrrMode::Off,
focus_ring: None,
},
];
let dh = smithay::reexports::wayland_server::Display::<Halley>::new()
.expect("display")
.handle();
let mut state = Halley::new_for_test(&dh, tuning);
let fullscreen_left = state.model.field.spawn_surface(
"fullscreen-left",
Vec2 { x: 400.0, y: 300.0 },
Vec2 { x: 200.0, y: 140.0 },
);
let other_left = state.model.field.spawn_surface(
"other-left",
Vec2 { x: 500.0, y: 300.0 },
Vec2 { x: 200.0, y: 140.0 },
);
state.assign_node_to_monitor(fullscreen_left, "left");
state.assign_node_to_monitor(other_left, "left");
state
.model
.fullscreen_state
.fullscreen_active_node
.insert("left".to_string(), fullscreen_left);
state.set_interaction_monitor("left");
state.set_focused_monitor("left");
assert_eq!(
state.fullscreen_focus_override(Some(other_left)),
Some(fullscreen_left)
);
assert_eq!(state.fullscreen_focus_override(None), Some(fullscreen_left));
}
#[test]
fn fullscreen_focus_override_allows_same_monitor_overlap_policy_window() {
let mut tuning = halley_config::RuntimeTuning::default();
tuning.cluster_default_layout = halley_config::ClusterDefaultLayout::Tiling;
let dh = smithay::reexports::wayland_server::Display::<Halley>::new()
.expect("display")
.handle();
let mut state = Halley::new_for_test(&dh, tuning);
let monitor = state.model.monitor_state.current_monitor.clone();
let fullscreen = state.model.field.spawn_surface(
"fullscreen",
Vec2 { x: 400.0, y: 300.0 },
Vec2 { x: 800.0, y: 600.0 },
);
let overlap = state.model.field.spawn_surface(
"overlap",
Vec2 { x: 400.0, y: 300.0 },
Vec2 { x: 240.0, y: 180.0 },
);
state.assign_node_to_monitor(fullscreen, monitor.as_str());
state.assign_node_to_monitor(overlap, monitor.as_str());
let _ = state
.model
.field
.set_state(fullscreen, halley_core::field::NodeState::Active);
let _ = state
.model
.field
.set_state(overlap, halley_core::field::NodeState::Active);
state
.model
.fullscreen_state
.fullscreen_active_node
.insert(monitor, fullscreen);
state.model.spawn_state.applied_window_rules.insert(
overlap,
crate::compositor::spawn::state::AppliedInitialWindowRule {
overlap_policy: halley_config::InitialWindowOverlapPolicy::All,
spawn_placement: halley_config::InitialWindowSpawnPlacement::Adjacent,
cluster_participation: halley_config::InitialWindowClusterParticipation::Float,
parent_node: None,
suppress_reveal_pan: true,
},
);
assert_eq!(
state.fullscreen_focus_override(Some(overlap)),
Some(overlap)
);
}
#[test]
fn setting_interaction_focus_switches_current_monitor_to_focused_node_monitor() {
let mut tuning = halley_config::RuntimeTuning::default();
tuning.tty_viewports = vec![
halley_config::ViewportOutputConfig {
connector: "left".to_string(),
enabled: true,
offset_x: 0,
offset_y: 0,
width: 800,
height: 600,
refresh_rate: None,
transform_degrees: 0,
vrr: halley_config::ViewportVrrMode::Off,
focus_ring: None,
},
halley_config::ViewportOutputConfig {
connector: "right".to_string(),
enabled: true,
offset_x: 800,
offset_y: 0,
width: 800,
height: 600,
refresh_rate: None,
transform_degrees: 0,
vrr: halley_config::ViewportVrrMode::Off,
focus_ring: None,
},
];
let dh = smithay::reexports::wayland_server::Display::<Halley>::new()
.expect("display")
.handle();
let mut state = Halley::new_for_test(&dh, tuning);
let _ = state.activate_monitor("left");
let right = state.model.field.spawn_surface(
"right",
Vec2 {
x: 1200.0,
y: 300.0,
},
Vec2 { x: 200.0, y: 140.0 },
);
state.assign_node_to_monitor(right, "right");
state.set_interaction_focus(Some(right), 30_000, Instant::now());
assert_eq!(state.focused_monitor(), "right");
assert_eq!(state.interaction_monitor(), "right");
assert_eq!(state.model.monitor_state.current_monitor, "right");
}
#[test]
fn focus_monitor_view_restores_last_focused_surface_on_monitor() {
let mut tuning = halley_config::RuntimeTuning::default();
tuning.tty_viewports = vec![
halley_config::ViewportOutputConfig {
connector: "left".to_string(),
enabled: true,
offset_x: 0,
offset_y: 0,
width: 800,
height: 600,
refresh_rate: None,
transform_degrees: 0,
vrr: halley_config::ViewportVrrMode::Off,
focus_ring: None,
},
halley_config::ViewportOutputConfig {
connector: "right".to_string(),
enabled: true,
offset_x: 800,
offset_y: 0,
width: 800,
height: 600,
refresh_rate: None,
transform_degrees: 0,
vrr: halley_config::ViewportVrrMode::Off,
focus_ring: None,
},
];
let dh = smithay::reexports::wayland_server::Display::<Halley>::new()
.expect("display")
.handle();
let mut state = Halley::new_for_test(&dh, tuning);
let right = state.model.field.spawn_surface(
"right",
Vec2 {
x: 1200.0,
y: 300.0,
},
Vec2 { x: 200.0, y: 140.0 },
);
state.assign_node_to_monitor(right, "right");
state.set_interaction_focus(Some(right), 30_000, Instant::now());
state.focus_monitor_view("left", Instant::now());
assert_eq!(state.model.focus_state.primary_interaction_focus, None);
state.focus_monitor_view("right", Instant::now());
assert_eq!(state.focused_monitor(), "right");
assert_eq!(state.interaction_monitor(), "right");
assert_eq!(state.model.monitor_state.current_monitor, "right");
assert_eq!(
state.model.focus_state.primary_interaction_focus,
Some(right)
);
}
#[test]
fn focus_monitor_view_uses_bare_monitor_view_when_no_surface_exists() {
let mut tuning = halley_config::RuntimeTuning::default();
tuning.tty_viewports = vec![
halley_config::ViewportOutputConfig {
connector: "left".to_string(),
enabled: true,
offset_x: 0,
offset_y: 0,
width: 800,
height: 600,
refresh_rate: None,
transform_degrees: 0,
vrr: halley_config::ViewportVrrMode::Off,
focus_ring: None,
},
halley_config::ViewportOutputConfig {
connector: "right".to_string(),
enabled: true,
offset_x: 800,
offset_y: 0,
width: 800,
height: 600,
refresh_rate: None,
transform_degrees: 0,
vrr: halley_config::ViewportVrrMode::Off,
focus_ring: None,
},
];
let dh = smithay::reexports::wayland_server::Display::<Halley>::new()
.expect("display")
.handle();
let mut state = Halley::new_for_test(&dh, tuning);
state.focus_monitor_view("right", Instant::now());
assert_eq!(state.focused_monitor(), "right");
assert_eq!(state.interaction_monitor(), "right");
assert_eq!(state.model.monitor_state.current_monitor, "right");
assert_eq!(state.model.focus_state.primary_interaction_focus, None);
}
#[test]
fn focus_monitor_view_does_not_restore_blocked_monitor_focus() {
let mut tuning = halley_config::RuntimeTuning::default();
tuning.close_restore_focus = false;
tuning.tty_viewports = vec![halley_config::ViewportOutputConfig {
connector: "right".to_string(),
enabled: true,
offset_x: 0,
offset_y: 0,
width: 800,
height: 600,
refresh_rate: None,
transform_degrees: 0,
vrr: halley_config::ViewportVrrMode::Off,
focus_ring: None,
}];
let dh = smithay::reexports::wayland_server::Display::<Halley>::new()
.expect("display")
.handle();
let mut state = Halley::new_for_test(&dh, tuning);
let right = state.model.field.spawn_surface(
"right",
Vec2 { x: 200.0, y: 0.0 },
Vec2 { x: 100.0, y: 80.0 },
);
state.assign_node_to_monitor(right, "right");
state
.model
.focus_state
.monitor_focus
.insert("right".to_string(), right);
state
.model
.focus_state
.blocked_monitor_focus_restore
.insert("right".to_string());
state.focus_monitor_view("right", Instant::now());
assert_eq!(state.model.focus_state.primary_interaction_focus, None);
}
}