aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/handlers/mod.rs3
-rw-r--r--src/input/mod.rs37
-rw-r--r--src/layout/mod.rs100
-rw-r--r--src/layout/tests.rs19
4 files changed, 137 insertions, 22 deletions
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs
index 334ab762..587d44ea 100644
--- a/src/handlers/mod.rs
+++ b/src/handlers/mod.rs
@@ -344,6 +344,9 @@ impl ClientDndGrabHandler for State {
fn dropped(&mut self, target: Option<WlSurface>, validated: bool, _seat: Seat<Self>) {
trace!("client dropped, target: {target:?}, validated: {validated}");
+ // End DnD before activating a specific window below so that it takes precedence.
+ self.niri.layout.dnd_end();
+
// Activate the target output, since that's how Firefox drag-tab-into-new-window works for
// example. On successful drop, additionally activate the target window.
let mut activate_output = true;
diff --git a/src/input/mod.rs b/src/input/mod.rs
index a2931b85..6c3ec812 100644
--- a/src/input/mod.rs
+++ b/src/input/mod.rs
@@ -32,6 +32,7 @@ use smithay::output::Output;
use smithay::utils::{Logical, Point, Rectangle, Transform, SERIAL_COUNTER};
use smithay::wayland::keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitor;
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraint};
+use smithay::wayland::selection::data_device::DnDGrab;
use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait};
use touch_move_grab::TouchMoveGrab;
@@ -1888,6 +1889,18 @@ impl State {
// Activate a new confinement if necessary.
self.niri.maybe_activate_pointer_constraint();
+ // Inform the layout of an ongoing DnD operation.
+ let mut is_dnd_grab = false;
+ pointer.with_grab(|_, grab| {
+ is_dnd_grab = grab.as_any().downcast_ref::<DnDGrab<Self>>().is_some();
+ });
+ if is_dnd_grab {
+ if let Some((output, pos_within_output)) = self.niri.output_under(new_pos) {
+ let output = output.clone();
+ self.niri.layout.dnd_update(output, pos_within_output);
+ }
+ }
+
// Redraw to update the cursor position.
// FIXME: redraw only outputs overlapping the cursor.
self.niri.queue_redraw_all();
@@ -1950,6 +1963,18 @@ impl State {
// We moved the regular pointer, so show it now.
self.niri.tablet_cursor_location = None;
+ // Inform the layout of an ongoing DnD operation.
+ let mut is_dnd_grab = false;
+ pointer.with_grab(|_, grab| {
+ is_dnd_grab = grab.as_any().downcast_ref::<DnDGrab<Self>>().is_some();
+ });
+ if is_dnd_grab {
+ if let Some((output, pos_within_output)) = self.niri.output_under(pos) {
+ let output = output.clone();
+ self.niri.layout.dnd_update(output, pos_within_output);
+ }
+ }
+
// Redraw to update the cursor position.
// FIXME: redraw only outputs overlapping the cursor.
self.niri.queue_redraw_all();
@@ -2912,6 +2937,18 @@ impl State {
time: evt.time_msec(),
},
);
+
+ // Inform the layout of an ongoing DnD operation.
+ let mut is_dnd_grab = false;
+ handle.with_grab(|_, grab| {
+ is_dnd_grab = grab.as_any().downcast_ref::<DnDGrab<Self>>().is_some();
+ });
+ if is_dnd_grab {
+ if let Some((output, pos_within_output)) = self.niri.output_under(touch_location) {
+ let output = output.clone();
+ self.niri.layout.dnd_update(output, pos_within_output);
+ }
+ }
}
fn on_touch_frame<I: InputBackend>(&mut self, _evt: I::TouchFrameEvent) {
let Some(handle) = self.niri.seat.get_touch() else {
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 739709f4..248a5c4f 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -278,6 +278,8 @@ pub struct Layout<W: LayoutElement> {
last_active_workspace_id: HashMap<String, WorkspaceId>,
/// Ongoing interactive move.
interactive_move: Option<InteractiveMoveState<W>>,
+ /// Ongoing drag-and-drop operation.
+ dnd: Option<DndData>,
/// Clock for driving animations.
clock: Clock,
/// Time that we last updated render elements for.
@@ -400,6 +402,14 @@ struct InteractiveMoveData<W: LayoutElement> {
pub(self) pointer_ratio_within_window: (f64, f64),
}
+#[derive(Debug)]
+pub struct DndData {
+ /// Output where the pointer is currently located.
+ output: Output,
+ /// Current pointer position within output.
+ pointer_pos_within_output: Point<f64, Logical>,
+}
+
#[derive(Debug, Clone, Copy)]
pub struct InteractiveResizeData {
pub(self) edges: ResizeEdge,
@@ -600,6 +610,7 @@ impl<W: LayoutElement> Layout<W> {
is_active: true,
last_active_workspace_id: HashMap::new(),
interactive_move: None,
+ dnd: None,
clock,
update_render_elements_time: Duration::ZERO,
options: Rc::new(options),
@@ -622,6 +633,7 @@ impl<W: LayoutElement> Layout<W> {
is_active: true,
last_active_workspace_id: HashMap::new(),
interactive_move: None,
+ dnd: None,
clock,
update_render_elements_time: Duration::ZERO,
options: opts,
@@ -2518,7 +2530,7 @@ impl<W: LayoutElement> Layout<W> {
workspace.verify_invariants(move_win_id.as_ref());
let has_view_offset_gesture = workspace.scrolling().view_offset().is_gesture();
- if self.interactive_move.is_some() {
+ if self.dnd.is_some() || self.interactive_move.is_some() {
// We'd like to check that all workspaces have the gesture here, furthermore we
// want to check that they have the gesture only if the interactive move
// targets the scrolling layout. However, we cannot do that because we start
@@ -2545,23 +2557,30 @@ impl<W: LayoutElement> Layout<W> {
pub fn advance_animations(&mut self) {
let _span = tracy_client::span!("Layout::advance_animations");
+ let mut dnd_scroll = None;
+ if let Some(dnd) = &self.dnd {
+ dnd_scroll = Some((dnd.output.clone(), dnd.pointer_pos_within_output));
+ }
+
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
move_.tile.advance_animations();
- // Scroll the view if needed.
- if !move_.is_floating {
- let output = move_.output.clone();
- let pos_within_output = move_.pointer_pos_within_output;
- if let Some(mon) = self.monitor_for_output_mut(&output) {
- if let Some((ws, offset)) = mon.workspace_under(pos_within_output) {
- let ws_id = ws.id();
- let ws = mon
- .workspaces
- .iter_mut()
- .find(|ws| ws.id() == ws_id)
- .unwrap();
- ws.dnd_scroll_gesture_scroll(pos_within_output - offset);
- }
+ if !move_.is_floating && dnd_scroll.is_none() {
+ dnd_scroll = Some((move_.output.clone(), move_.pointer_pos_within_output));
+ }
+ }
+
+ // Scroll the view if needed.
+ if let Some((output, pos_within_output)) = dnd_scroll {
+ if let Some(mon) = self.monitor_for_output_mut(&output) {
+ if let Some((ws, offset)) = mon.workspace_under(pos_within_output) {
+ let ws_id = ws.id();
+ let ws = mon
+ .workspaces
+ .iter_mut()
+ .find(|ws| ws.id() == ws_id)
+ .unwrap();
+ ws.dnd_scroll_gesture_scroll(pos_within_output - offset);
}
}
}
@@ -2581,6 +2600,13 @@ impl<W: LayoutElement> Layout<W> {
}
pub fn are_animations_ongoing(&self, output: Option<&Output>) -> bool {
+ // Keep advancing animations if we might need to scroll the view.
+ if let Some(dnd) = &self.dnd {
+ if output.map_or(true, |output| *output == dnd.output) {
+ return true;
+ }
+ }
+
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
if output.map_or(true, |output| *output == move_.output) {
if move_.tile.are_animations_ongoing() {
@@ -3991,6 +4017,33 @@ impl<W: LayoutElement> Layout<W> {
move_.output == *output
}
+ pub fn dnd_update(&mut self, output: Output, pointer_pos_within_output: Point<f64, Logical>) {
+ let begin_gesture = self.dnd.is_none();
+
+ self.dnd = Some(DndData {
+ output,
+ pointer_pos_within_output,
+ });
+
+ if begin_gesture {
+ for ws in self.workspaces_mut() {
+ ws.dnd_scroll_gesture_begin();
+ }
+ }
+ }
+
+ pub fn dnd_end(&mut self) {
+ if self.dnd.is_none() {
+ return;
+ }
+
+ self.dnd = None;
+
+ for ws in self.workspaces_mut() {
+ ws.view_offset_gesture_end(false, None);
+ }
+ }
+
pub fn interactive_resize_begin(&mut self, window: W::Id, edges: ResizeEdge) -> bool {
match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => {
@@ -4349,7 +4402,8 @@ impl<W: LayoutElement> Layout<W> {
self.is_active = is_active;
- let mut ongoing_scrolling_dnd = None;
+ let mut ongoing_scrolling_dnd = self.dnd.is_some().then_some(true);
+
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
let win = move_.tile.window_mut();
@@ -4364,15 +4418,17 @@ impl<W: LayoutElement> Layout<W> {
win.send_pending_configure();
win.refresh();
- ongoing_scrolling_dnd = Some(!move_.is_floating);
+ ongoing_scrolling_dnd.get_or_insert(!move_.is_floating);
} else if let Some(InteractiveMoveState::Starting { window_id, .. }) =
&self.interactive_move
{
- let (_, _, ws) = self
- .workspaces()
- .find(|(_, _, ws)| ws.has_window(window_id))
- .unwrap();
- ongoing_scrolling_dnd = Some(!ws.is_floating(window_id));
+ ongoing_scrolling_dnd.get_or_insert_with(|| {
+ let (_, _, ws) = self
+ .workspaces()
+ .find(|(_, _, ws)| ws.has_window(window_id))
+ .unwrap();
+ !ws.is_floating(window_id)
+ });
}
match &mut self.monitor_set {
diff --git a/src/layout/tests.rs b/src/layout/tests.rs
index ecb9d683..813f4765 100644
--- a/src/layout/tests.rs
+++ b/src/layout/tests.rs
@@ -589,6 +589,15 @@ enum Op {
#[proptest(strategy = "1..=5usize")]
window: usize,
},
+ DndUpdate {
+ #[proptest(strategy = "1..=5usize")]
+ output_idx: usize,
+ #[proptest(strategy = "-20000f64..20000f64")]
+ px: f64,
+ #[proptest(strategy = "-20000f64..20000f64")]
+ py: f64,
+ },
+ DndEnd,
InteractiveResizeBegin {
#[proptest(strategy = "1..=5usize")]
window: usize,
@@ -1353,6 +1362,16 @@ impl Op {
Op::InteractiveMoveEnd { window } => {
layout.interactive_move_end(&window);
}
+ Op::DndUpdate { output_idx, px, py } => {
+ let name = format!("output{output_idx}");
+ let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
+ return;
+ };
+ layout.dnd_update(output, Point::from((px, py)));
+ }
+ Op::DndEnd => {
+ layout.dnd_end();
+ }
Op::InteractiveResizeBegin { window, edges } => {
layout.interactive_resize_begin(window, edges);
}