mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Implement niri msg pick-window
* feat: `niri msg pick-window` * fixes --------- Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This commit is contained in:
@@ -63,6 +63,8 @@ pub enum Request {
|
||||
FocusedOutput,
|
||||
/// Request information about the focused window.
|
||||
FocusedWindow,
|
||||
/// Request picking a window and get its information.
|
||||
PickWindow,
|
||||
/// Perform an action.
|
||||
Action(Action),
|
||||
/// Change output configuration temporarily.
|
||||
@@ -129,6 +131,8 @@ pub enum Response {
|
||||
FocusedOutput(Option<Output>),
|
||||
/// Information about the focused window.
|
||||
FocusedWindow(Option<Window>),
|
||||
/// Information about the picked window.
|
||||
PickedWindow(Option<Window>),
|
||||
/// Output configuration change result.
|
||||
OutputConfigChanged(OutputConfigChanged),
|
||||
}
|
||||
|
||||
@@ -72,6 +72,8 @@ pub enum Msg {
|
||||
FocusedOutput,
|
||||
/// Print information about the focused window.
|
||||
FocusedWindow,
|
||||
/// Pick a window with the mouse and print information about it.
|
||||
PickWindow,
|
||||
/// Perform an action.
|
||||
Action {
|
||||
#[command(subcommand)]
|
||||
|
||||
+14
-1
@@ -48,6 +48,7 @@ use crate::utils::{center, get_monotonic_time, ResizeEdge};
|
||||
|
||||
pub mod backend_ext;
|
||||
pub mod move_grab;
|
||||
pub mod pick_window_grab;
|
||||
pub mod resize_grab;
|
||||
pub mod scroll_tracker;
|
||||
pub mod spatial_movement_grab;
|
||||
@@ -367,7 +368,6 @@ impl State {
|
||||
serial,
|
||||
time,
|
||||
|this, mods, keysym| {
|
||||
let bindings = &this.niri.config.borrow().binds;
|
||||
let key_code = event.key_code();
|
||||
let modified = keysym.modified_sym();
|
||||
let raw = keysym.raw_latin_sym_or_raw_current_sym();
|
||||
@@ -383,6 +383,19 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
if pressed && raw == Some(Keysym::Escape) && this.niri.pick_window.is_some() {
|
||||
// We window picking state so the pick window grab must be active.
|
||||
// Unsetting it cancels window picking.
|
||||
this.niri
|
||||
.seat
|
||||
.get_pointer()
|
||||
.unwrap()
|
||||
.unset_grab(this, serial, time);
|
||||
this.niri.suppressed_keys.insert(key_code);
|
||||
return FilterResult::Intercept(None);
|
||||
}
|
||||
|
||||
let bindings = &this.niri.config.borrow().binds;
|
||||
should_intercept_key(
|
||||
&mut this.niri.suppressed_keys,
|
||||
bindings,
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
use smithay::backend::input::ButtonState;
|
||||
use smithay::input::pointer::{
|
||||
AxisFrame, ButtonEvent, CursorImageStatus, GestureHoldBeginEvent, GestureHoldEndEvent,
|
||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||
MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent,
|
||||
};
|
||||
use smithay::input::SeatHandler;
|
||||
use smithay::utils::{Logical, Point};
|
||||
|
||||
use crate::niri::State;
|
||||
use crate::window::Mapped;
|
||||
|
||||
pub struct PickWindowGrab {
|
||||
start_data: PointerGrabStartData<State>,
|
||||
}
|
||||
|
||||
impl PickWindowGrab {
|
||||
pub fn new(start_data: PointerGrabStartData<State>) -> Self {
|
||||
Self { start_data }
|
||||
}
|
||||
|
||||
fn on_ungrab(&mut self, state: &mut State) {
|
||||
if let Some(tx) = state.niri.pick_window.take() {
|
||||
let _ = tx.send_blocking(None);
|
||||
}
|
||||
state
|
||||
.niri
|
||||
.cursor_manager
|
||||
.set_cursor_image(CursorImageStatus::default_named());
|
||||
// Redraw to update the cursor.
|
||||
state.niri.queue_redraw_all();
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerGrab<State> for PickWindowGrab {
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
_focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
handle.motion(data, None, event);
|
||||
}
|
||||
|
||||
fn relative_motion(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
_focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>,
|
||||
event: &RelativeMotionEvent,
|
||||
) {
|
||||
handle.relative_motion(data, None, event);
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &ButtonEvent,
|
||||
) {
|
||||
if event.state == ButtonState::Pressed {
|
||||
if let Some(tx) = data.niri.pick_window.take() {
|
||||
let _ = tx.send_blocking(
|
||||
data.niri
|
||||
.window_under(handle.current_location())
|
||||
.map(Mapped::id),
|
||||
);
|
||||
}
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
details: AxisFrame,
|
||||
) {
|
||||
handle.axis(data, details);
|
||||
}
|
||||
|
||||
fn frame(&mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>) {
|
||||
handle.frame(data);
|
||||
}
|
||||
|
||||
fn gesture_swipe_begin(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &GestureSwipeBeginEvent,
|
||||
) {
|
||||
handle.gesture_swipe_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_swipe_update(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &GestureSwipeUpdateEvent,
|
||||
) {
|
||||
handle.gesture_swipe_update(data, event);
|
||||
}
|
||||
|
||||
fn gesture_swipe_end(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &GestureSwipeEndEvent,
|
||||
) {
|
||||
handle.gesture_swipe_end(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_begin(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &GesturePinchBeginEvent,
|
||||
) {
|
||||
handle.gesture_pinch_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_update(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &GesturePinchUpdateEvent,
|
||||
) {
|
||||
handle.gesture_pinch_update(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_end(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &GesturePinchEndEvent,
|
||||
) {
|
||||
handle.gesture_pinch_end(data, event);
|
||||
}
|
||||
|
||||
fn gesture_hold_begin(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &GestureHoldBeginEvent,
|
||||
) {
|
||||
handle.gesture_hold_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_hold_end(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &GestureHoldEndEvent,
|
||||
) {
|
||||
handle.gesture_hold_end(data, event);
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &PointerGrabStartData<State> {
|
||||
&self.start_data
|
||||
}
|
||||
|
||||
fn unset(&mut self, data: &mut State) {
|
||||
self.on_ungrab(data);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
|
||||
Msg::Outputs => Request::Outputs,
|
||||
Msg::FocusedWindow => Request::FocusedWindow,
|
||||
Msg::FocusedOutput => Request::FocusedOutput,
|
||||
Msg::PickWindow => Request::PickWindow,
|
||||
Msg::Action { action } => Request::Action(action.clone()),
|
||||
Msg::Output { output, action } => Request::Output {
|
||||
output: output.clone(),
|
||||
@@ -252,6 +253,23 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
|
||||
println!("No output is focused.");
|
||||
}
|
||||
}
|
||||
Msg::PickWindow => {
|
||||
let Response::PickedWindow(window) = response else {
|
||||
bail!("unexpected response: expected PickedWindow, got {response:?}");
|
||||
};
|
||||
|
||||
if json {
|
||||
let window = serde_json::to_string(&window).context("error formatting response")?;
|
||||
println!("{window}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(window) = window {
|
||||
print_window(&window);
|
||||
} else {
|
||||
println!("No window selected.");
|
||||
}
|
||||
}
|
||||
Msg::Action { .. } => {
|
||||
let Response::Handled = response else {
|
||||
bail!("unexpected response: expected Handled, got {response:?}");
|
||||
|
||||
@@ -18,12 +18,17 @@ use niri_config::OutputName;
|
||||
use niri_ipc::state::{EventStreamState, EventStreamStatePart as _};
|
||||
use niri_ipc::{Event, KeyboardLayouts, OutputConfigChanged, Reply, Request, Response, Workspace};
|
||||
use smithay::desktop::layer_map_for_output;
|
||||
use smithay::input::pointer::{
|
||||
CursorIcon, CursorImageStatus, Focus, GrabStartData as PointerGrabStartData,
|
||||
};
|
||||
use smithay::reexports::calloop::generic::Generic;
|
||||
use smithay::reexports::calloop::{Interest, LoopHandle, Mode, PostAction};
|
||||
use smithay::reexports::rustix::fs::unlink;
|
||||
use smithay::utils::SERIAL_COUNTER;
|
||||
use smithay::wayland::shell::wlr_layer::{KeyboardInteractivity, Layer};
|
||||
|
||||
use crate::backend::IpcOutputMap;
|
||||
use crate::input::pick_window_grab::PickWindowGrab;
|
||||
use crate::layout::workspace::WorkspaceId;
|
||||
use crate::niri::State;
|
||||
use crate::utils::{version, with_toplevel_role};
|
||||
@@ -322,6 +327,35 @@ async fn process(ctx: &ClientCtx, request: Request) -> Reply {
|
||||
let window = windows.values().find(|win| win.is_focused).cloned();
|
||||
Response::FocusedWindow(window)
|
||||
}
|
||||
Request::PickWindow => {
|
||||
let (tx, rx) = async_channel::bounded(1);
|
||||
ctx.event_loop.insert_idle(move |state| {
|
||||
let pointer = state.niri.seat.get_pointer().unwrap();
|
||||
let start_data = PointerGrabStartData {
|
||||
focus: None,
|
||||
button: 0,
|
||||
location: pointer.current_location(),
|
||||
};
|
||||
let grab = PickWindowGrab::new(start_data);
|
||||
// The `WindowPickGrab` ungrab handler will cancel the previous ongoing pick, if
|
||||
// any.
|
||||
pointer.set_grab(state, grab, SERIAL_COUNTER.next_serial(), Focus::Clear);
|
||||
state.niri.pick_window = Some(tx);
|
||||
state
|
||||
.niri
|
||||
.cursor_manager
|
||||
.set_cursor_image(CursorImageStatus::Named(CursorIcon::Crosshair));
|
||||
// Redraw to update the cursor.
|
||||
state.niri.queue_redraw_all();
|
||||
});
|
||||
let result = rx.recv().await;
|
||||
let id = result.map_err(|_| String::from("error getting picked window info"))?;
|
||||
let window = id.and_then(|id| {
|
||||
let state = ctx.event_stream_state.borrow();
|
||||
state.windows.windows.get(&id.get()).cloned()
|
||||
});
|
||||
Response::PickedWindow(window)
|
||||
}
|
||||
Request::Action(action) => {
|
||||
let (tx, rx) = async_channel::bounded(1);
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ use crate::utils::{
|
||||
center, center_f64, expand_home, get_monotonic_time, ipc_transform_to_smithay, logical_output,
|
||||
make_screenshot_path, output_matches_name, output_size, send_scale_transform, write_png_rgba8,
|
||||
};
|
||||
use crate::window::mapped::MappedId;
|
||||
use crate::window::{InitialConfigureState, Mapped, ResolvedWindowRules, Unmapped, WindowRef};
|
||||
|
||||
const CLEAR_COLOR_LOCKED: [f32; 4] = [0.3, 0.1, 0.1, 1.];
|
||||
@@ -356,6 +357,8 @@ pub struct Niri {
|
||||
pub hotkey_overlay: HotkeyOverlay,
|
||||
pub exit_confirm_dialog: Option<ExitConfirmDialog>,
|
||||
|
||||
pub pick_window: Option<async_channel::Sender<Option<MappedId>>>,
|
||||
|
||||
pub debug_draw_opaque_regions: bool,
|
||||
pub debug_draw_damage: bool,
|
||||
|
||||
@@ -2172,6 +2175,8 @@ impl Niri {
|
||||
hotkey_overlay,
|
||||
exit_confirm_dialog,
|
||||
|
||||
pick_window: None,
|
||||
|
||||
debug_draw_opaque_regions: false,
|
||||
debug_draw_damage: false,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user