From 33f85b576c3da46a89c5b66a5ccc46b8958f1b5a Mon Sep 17 00:00:00 2001 From: William Barsse Date: Thu, 5 May 2022 11:46:15 +0200 Subject: [PATCH] Add a "lock-in" delay to LRU window order This patch introduces the following: - a configurable duration has to go by before a focus event results in a window's timestamp being updated. In other words, the LRU order only changes when a window keeps the focus long enough. - focus timestamps for each window are moved from `extra_props` to a dedicated `FocusData` struct that lives in focus.rs - swayrd configuration has an added "focus" section in which the "lock-in" delay can be configured (with the `lockin_delay` key). All together, this allows the user to navigate through windows (with the mouse and/or keyboard) without updating the LRU history unless a window is lingered in. This avoids "polluting" the history and allows more efficient jumping back-and-forth with prev/next-window commands. --- swayr/src/cmds.rs | 202 +++++++++++++++++---------------------- swayr/src/config.rs | 26 +++++ swayr/src/daemon.rs | 228 +++++++++++++++++++++++++++++++------------- swayr/src/focus.rs | 90 +++++++++++++++++ swayr/src/lib.rs | 1 + swayr/src/tree.rs | 84 ++++++++-------- 6 files changed, 402 insertions(+), 229 deletions(-) create mode 100644 swayr/src/focus.rs diff --git a/swayr/src/cmds.rs b/swayr/src/cmds.rs index ec445c3..550763a 100644 --- a/swayr/src/cmds.rs +++ b/swayr/src/cmds.rs @@ -16,6 +16,8 @@ //! Functions and data structures of the swayr client. use crate::config as cfg; +use crate::focus::FocusData; +use crate::focus::FocusMessage; use crate::layout; use crate::shared::ipc; use crate::shared::ipc::NodeMethods; @@ -26,10 +28,6 @@ use once_cell::sync::Lazy; use rand::prelude::SliceRandom; use regex::Regex; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::sync::atomic; -use std::sync::Arc; -use std::sync::RwLock; use swayipc as s; pub fn run_sway_command_1(cmd: &str) { @@ -227,7 +225,7 @@ impl SwayrCommand { pub struct ExecSwayrCmdArgs<'a> { pub cmd: &'a SwayrCommand, - pub extra_props: Arc>>, + pub focus_data: &'a FocusData, } impl DisplayFormat for SwayrCommand { @@ -247,86 +245,62 @@ fn always_true(_x: &t::DisplayNode) -> bool { true } -static IN_NEXT_PREV_WINDOW_SEQ: atomic::AtomicBool = - atomic::AtomicBool::new(false); - pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) { - let props = args.extra_props; + let fdata = args.focus_data; if args.cmd.is_prev_next_window_variant() { - let before = - IN_NEXT_PREV_WINDOW_SEQ.swap(true, atomic::Ordering::SeqCst); - if !before { - let mut map = props.write().unwrap(); - for val in map.values_mut() { - val.last_focus_tick_for_next_prev_seq = val.last_focus_tick; - } - } + fdata.send(FocusMessage::TickUpdateInhibit); } else { - IN_NEXT_PREV_WINDOW_SEQ.store(false, atomic::Ordering::SeqCst); + fdata.send(FocusMessage::TickUpdateActivate); } match args.cmd { SwayrCommand::Nop => {} SwayrCommand::SwitchToUrgentOrLRUWindow => { - switch_to_urgent_or_lru_window(&*props.read().unwrap()) + switch_to_urgent_or_lru_window(fdata) } SwayrCommand::SwitchToAppOrUrgentOrLRUWindow { name } => { - switch_to_app_or_urgent_or_lru_window( - Some(name), - &*props.read().unwrap(), - ) + switch_to_app_or_urgent_or_lru_window(Some(name), fdata) } SwayrCommand::SwitchToMarkOrUrgentOrLRUWindow { con_mark } => { - switch_to_mark_or_urgent_or_lru_window( - Some(con_mark), - &*props.read().unwrap(), - ) - } - SwayrCommand::SwitchWindow => switch_window(&*props.read().unwrap()), - SwayrCommand::SwitchWorkspace => { - switch_workspace(&*props.read().unwrap()) + switch_to_mark_or_urgent_or_lru_window(Some(con_mark), fdata) } - SwayrCommand::SwitchOutput => switch_output(&*props.read().unwrap()), + SwayrCommand::SwitchWindow => switch_window(fdata), + SwayrCommand::SwitchWorkspace => switch_workspace(fdata), + SwayrCommand::SwitchOutput => switch_output(), SwayrCommand::SwitchWorkspaceOrWindow => { - switch_workspace_or_window(&*props.read().unwrap()) + switch_workspace_or_window(fdata) } SwayrCommand::SwitchWorkspaceContainerOrWindow => { - switch_workspace_container_or_window(&*props.read().unwrap()) - } - SwayrCommand::SwitchTo => switch_to(&*props.read().unwrap()), - SwayrCommand::QuitWindow { kill } => { - quit_window(&*props.read().unwrap(), *kill) - } - SwayrCommand::QuitWorkspaceOrWindow => { - quit_workspace_or_window(&*props.read().unwrap()) + switch_workspace_container_or_window(fdata) } + SwayrCommand::SwitchTo => switch_to(fdata), + SwayrCommand::QuitWindow { kill } => quit_window(fdata, *kill), + SwayrCommand::QuitWorkspaceOrWindow => quit_workspace_or_window(fdata), SwayrCommand::QuitWorkspaceContainerOrWindow => { - quit_workspace_container_or_window(&*props.read().unwrap()) + quit_workspace_container_or_window(fdata) } SwayrCommand::MoveFocusedToWorkspace => { - move_focused_to_workspace(&*props.read().unwrap()) - } - SwayrCommand::MoveFocusedTo => move_focused_to(&*props.read().unwrap()), - SwayrCommand::SwapFocusedWith => { - swap_focused_with(&*props.read().unwrap()) + move_focused_to_workspace(fdata) } + SwayrCommand::MoveFocusedTo => move_focused_to(fdata), + SwayrCommand::SwapFocusedWith => swap_focused_with(fdata), SwayrCommand::NextWindow { windows } => focus_window_in_direction( Direction::Forward, windows, - &*props.read().unwrap(), + fdata, Box::new(always_true), ), SwayrCommand::PrevWindow { windows } => focus_window_in_direction( Direction::Backward, windows, - &*props.read().unwrap(), + fdata, Box::new(always_true), ), SwayrCommand::NextTiledWindow { windows } => focus_window_in_direction( Direction::Forward, windows, - &*props.read().unwrap(), + fdata, Box::new(|dn: &t::DisplayNode| { !dn.node.is_floating() && dn.tree.is_child_of_tiled_container(dn.node.id) @@ -335,7 +309,7 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) { SwayrCommand::PrevTiledWindow { windows } => focus_window_in_direction( Direction::Backward, windows, - &*props.read().unwrap(), + fdata, Box::new(|dn: &t::DisplayNode| { !dn.node.is_floating() && dn.tree.is_child_of_tiled_container(dn.node.id) @@ -345,7 +319,7 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) { focus_window_in_direction( Direction::Forward, windows, - &*props.read().unwrap(), + fdata, Box::new(|dn: &t::DisplayNode| { !dn.node.is_floating() && dn @@ -358,7 +332,7 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) { focus_window_in_direction( Direction::Backward, windows, - &*props.read().unwrap(), + fdata, Box::new(|dn: &t::DisplayNode| { !dn.node.is_floating() && dn @@ -371,7 +345,7 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) { focus_window_in_direction( Direction::Forward, windows, - &*props.read().unwrap(), + fdata, Box::new(|dn: &t::DisplayNode| dn.node.is_floating()), ) } @@ -379,7 +353,7 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) { focus_window_in_direction( Direction::Backward, windows, - &*props.read().unwrap(), + fdata, Box::new(|dn: &t::DisplayNode| dn.node.is_floating()), ) } @@ -387,14 +361,14 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) { focus_window_of_same_layout_in_direction( Direction::Forward, windows, - &*props.read().unwrap(), + fdata, ) } SwayrCommand::PrevWindowOfSameLayout { windows } => { focus_window_of_same_layout_in_direction( Direction::Backward, windows, - &*props.read().unwrap(), + fdata, ) } SwayrCommand::TileWorkspace { floating } => { @@ -473,7 +447,7 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) { { exec_swayr_cmd(ExecSwayrCmdArgs { cmd: c, - extra_props: props, + focus_data: args.focus_data, }); } } @@ -495,19 +469,17 @@ pub fn get_outputs() -> Vec { } } -pub fn switch_to_urgent_or_lru_window( - extra_props: &HashMap, -) { - switch_to_app_or_urgent_or_lru_window(None, extra_props) +pub fn switch_to_urgent_or_lru_window(fdata: &FocusData) { + switch_to_app_or_urgent_or_lru_window(None, fdata) } pub fn switch_to_app_or_urgent_or_lru_window( name: Option<&str>, - extra_props: &HashMap, + fdata: &FocusData, ) { let root = ipc::get_root_node(false); - let tree = t::get_tree(&root, extra_props); - let wins = tree.get_windows(); + let tree = t::get_tree(&root); + let wins = tree.get_windows(fdata); let app_win = name.and_then(|n| wins.iter().find(|w| w.node.get_app_name() == n)); focus_win_if_not_focused(app_win, wins.get(0)) @@ -515,11 +487,11 @@ pub fn switch_to_app_or_urgent_or_lru_window( pub fn switch_to_mark_or_urgent_or_lru_window( con_mark: Option<&str>, - extra_props: &HashMap, + fdata: &FocusData, ) { let root = ipc::get_root_node(false); - let tree = t::get_tree(&root, extra_props); - let wins = tree.get_windows(); + let tree = t::get_tree(&root); + let wins = tree.get_windows(fdata); let marked_win = con_mark.and_then(|mark| { wins.iter() .find(|w| w.node.marks.contains(&mark.to_owned())) @@ -612,50 +584,48 @@ fn select_and_focus(prompt: &str, choices: &[t::DisplayNode]) { } } -pub fn switch_window(extra_props: &HashMap) { +pub fn switch_window(fdata: &FocusData) { let root = ipc::get_root_node(true); - let tree = t::get_tree(&root, extra_props); - select_and_focus("Select window", &tree.get_windows()); + let tree = t::get_tree(&root); + select_and_focus("Select window", &tree.get_windows(fdata)); } -pub fn switch_workspace(extra_props: &HashMap) { +pub fn switch_workspace(fdata: &FocusData) { let root = ipc::get_root_node(false); - let tree = t::get_tree(&root, extra_props); - select_and_focus("Select workspace", &tree.get_workspaces()); + let tree = t::get_tree(&root); + select_and_focus("Select workspace", &tree.get_workspaces(fdata)); } -pub fn switch_output(extra_props: &HashMap) { +pub fn switch_output() { let root = ipc::get_root_node(false); - let tree = t::get_tree(&root, extra_props); + let tree = t::get_tree(&root); select_and_focus("Select output", &tree.get_outputs()); } -pub fn switch_workspace_or_window(extra_props: &HashMap) { +pub fn switch_workspace_or_window(fdata: &FocusData) { let root = ipc::get_root_node(true); - let tree = t::get_tree(&root, extra_props); + let tree = t::get_tree(&root); select_and_focus( "Select workspace or window", - &tree.get_workspaces_and_windows(), + &tree.get_workspaces_and_windows(fdata), ); } -pub fn switch_workspace_container_or_window( - extra_props: &HashMap, -) { +pub fn switch_workspace_container_or_window(fdata: &FocusData) { let root = ipc::get_root_node(true); - let tree = t::get_tree(&root, extra_props); + let tree = t::get_tree(&root); select_and_focus( "Select workspace, container or window", - &tree.get_workspaces_containers_and_windows(), + &tree.get_workspaces_containers_and_windows(fdata), ); } -pub fn switch_to(extra_props: &HashMap) { +pub fn switch_to(fdata: &FocusData) { let root = ipc::get_root_node(true); - let tree = t::get_tree(&root, extra_props); + let tree = t::get_tree(&root); select_and_focus( "Select output, workspace, container or window", - &tree.get_outputs_workspaces_containers_and_windows(), + &tree.get_outputs_workspaces_containers_and_windows(fdata), ); } @@ -697,30 +667,28 @@ fn select_and_quit(prompt: &str, choices: &[t::DisplayNode], kill: bool) { } } -pub fn quit_window(extra_props: &HashMap, kill: bool) { +pub fn quit_window(fdata: &FocusData, kill: bool) { let root = ipc::get_root_node(true); - let tree = t::get_tree(&root, extra_props); - select_and_quit("Quit window", &tree.get_windows(), kill); + let tree = t::get_tree(&root); + select_and_quit("Quit window", &tree.get_windows(fdata), kill); } -pub fn quit_workspace_or_window(extra_props: &HashMap) { +pub fn quit_workspace_or_window(fdata: &FocusData) { let root = ipc::get_root_node(true); - let tree = t::get_tree(&root, extra_props); + let tree = t::get_tree(&root); select_and_quit( "Quit workspace or window", - &tree.get_workspaces_and_windows(), + &tree.get_workspaces_and_windows(fdata), false, ); } -pub fn quit_workspace_container_or_window( - extra_props: &HashMap, -) { +pub fn quit_workspace_container_or_window(fdata: &FocusData) { let root = ipc::get_root_node(true); - let tree = t::get_tree(&root, extra_props); + let tree = t::get_tree(&root); select_and_quit( "Quit workspace, container or window", - &tree.get_workspaces_containers_and_windows(), + &tree.get_workspaces_containers_and_windows(fdata), false, ); } @@ -783,30 +751,30 @@ fn select_and_move_focused_to(prompt: &str, choices: &[t::DisplayNode]) { } } -pub fn move_focused_to_workspace(extra_props: &HashMap) { +pub fn move_focused_to_workspace(fdata: &FocusData) { let root = ipc::get_root_node(true); - let tree = t::get_tree(&root, extra_props); - select_and_move_focused_to( + let tree = t::get_tree(&root); +select_and_move_focused_to( "Move focused container to workspace", - &tree.get_workspaces(), + &tree.get_workspaces(fdata), ); } -pub fn move_focused_to(extra_props: &HashMap) { +pub fn move_focused_to(fdata: &FocusData) { let root = ipc::get_root_node(true); - let tree = t::get_tree(&root, extra_props); + let tree = t::get_tree(&root); select_and_move_focused_to( "Move focused container to workspace or container", - &tree.get_outputs_workspaces_containers_and_windows(), + &tree.get_outputs_workspaces_containers_and_windows(fdata), ); } -pub fn swap_focused_with(extra_props: &HashMap) { +pub fn swap_focused_with(fdata: &FocusData) { let root = ipc::get_root_node(true); - let tree = t::get_tree(&root, extra_props); + let tree = t::get_tree(&root); match util::select_from_menu( "Swap focused with", - &tree.get_workspaces_containers_and_windows(), + &tree.get_workspaces_containers_and_windows(fdata), ) { Ok(tn) => match tn.node.get_type() { ipc::Type::Workspace | ipc::Type::Container | ipc::Type::Window => { @@ -835,12 +803,12 @@ pub enum Direction { pub fn focus_window_in_direction( dir: Direction, consider_wins: &ConsiderWindows, - extra_props: &HashMap, + fdata: &FocusData, pred: Box bool>, ) { let root = ipc::get_root_node(false); - let tree = t::get_tree(&root, extra_props); - let mut wins = tree.get_windows(); + let tree = t::get_tree(&root); + let mut wins = tree.get_windows(fdata); if consider_wins == &ConsiderWindows::CurrentWorkspace { let cur_ws = tree.get_current_workspace(); @@ -859,8 +827,8 @@ pub fn focus_window_in_direction( } wins.sort_by(|a, b| { - let lru_a = tree.last_focus_tick_for_next_prev_seq(a.node.id); - let lru_b = tree.last_focus_tick_for_next_prev_seq(b.node.id); + let lru_a = fdata.last_focus_tick(a.node.id); + let lru_b = fdata.last_focus_tick(b.node.id); lru_a.cmp(&lru_b).reverse() }); @@ -890,18 +858,18 @@ pub fn focus_window_in_direction( pub fn focus_window_of_same_layout_in_direction( dir: Direction, consider_wins: &ConsiderWindows, - extra_props: &HashMap, + fdata: &FocusData, ) { let root = ipc::get_root_node(false); - let tree = t::get_tree(&root, extra_props); - let wins = tree.get_windows(); + let tree = t::get_tree(&root); + let wins = tree.get_windows(fdata); let cur_win = wins.iter().find(|w| w.node.focused); if let Some(cur_win) = cur_win { focus_window_in_direction( dir, consider_wins, - extra_props, + fdata, if cur_win.node.is_floating() { Box::new(|dn| dn.node.is_floating()) } else if !cur_win.node.is_floating() diff --git a/swayr/src/config.rs b/swayr/src/config.rs index c095c8c..bc6ec98 100644 --- a/swayr/src/config.rs +++ b/swayr/src/config.rs @@ -18,12 +18,14 @@ use crate::shared::cfg; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use std::time::Duration; #[derive(Debug, Serialize, Deserialize)] pub struct Config { menu: Option, format: Option, layout: Option, + focus: Option, } fn tilde_expand_file_names(file_names: Vec) -> Vec { @@ -155,6 +157,16 @@ impl Config { .or_else(|| Layout::default().auto_tile_min_window_width_per_output_width_as_map()) .expect("No layout.auto_tile_min_window_width_per_output_width defined.") } + + pub fn get_focus_lockin_delay(&self) -> Duration { + Duration::from_millis( + self.focus + .as_ref() + .and_then(|f| f.lockin_delay) + .or_else(|| Focus::default().lockin_delay) + .expect("No focus.lockin_delay defined."), + ) + } } #[derive(Debug, Serialize, Deserialize)] @@ -199,6 +211,11 @@ impl Layout { } } +#[derive(Debug, Serialize, Deserialize)] +pub struct Focus { + lockin_delay: Option, +} + impl Default for Menu { fn default() -> Self { Menu { @@ -290,12 +307,21 @@ impl Default for Layout { } } +impl Default for Focus { + fn default() -> Self { + Self { + lockin_delay: Some(750), + } + } +} + impl Default for Config { fn default() -> Self { Config { menu: Some(Menu::default()), format: Some(Format::default()), layout: Some(Layout::default()), + focus: Some(Focus::default()), } } } diff --git a/swayr/src/daemon.rs b/swayr/src/daemon.rs index 8a6ce75..353f575 100644 --- a/swayr/src/daemon.rs +++ b/swayr/src/daemon.rs @@ -16,28 +16,47 @@ //! Functions and data structures of the swayrd daemon. use crate::cmds; -use crate::config; +use crate::config::{self, Config}; +use crate::focus::FocusData; +use crate::focus::FocusEvent; +use crate::focus::FocusMessage; use crate::layout; -use crate::tree as t; use crate::util; use std::collections::HashMap; use std::io::Read; use std::os::unix::net::{UnixListener, UnixStream}; +use std::sync::mpsc; use std::sync::Arc; use std::sync::RwLock; use std::thread; +use std::time::Duration; use swayipc as s; pub fn run_daemon() { - let extra_props: Arc>> = - Arc::new(RwLock::new(HashMap::new())); - let extra_props_for_ev_handler = extra_props.clone(); + let (focus_tx, focus_rx) = mpsc::channel(); + let fdata = FocusData { + focus_tick_by_id: Arc::new(RwLock::new(HashMap::new())), + focus_chan: focus_tx, + }; - thread::spawn(move || { - monitor_sway_events(extra_props_for_ev_handler); - }); + let config = config::load_config(); + let lockin_delay = config.get_focus_lockin_delay(); + + { + let fdata = fdata.clone(); + thread::spawn(move || { + monitor_sway_events(fdata, &config); + }); + } - serve_client_requests(extra_props); + { + let fdata = fdata.clone(); + thread::spawn(move || { + focus_lock_in_handler(focus_rx, fdata, lockin_delay); + }); + } + + serve_client_requests(fdata); } fn connect_and_subscribe() -> s::Fallible { @@ -48,10 +67,7 @@ fn connect_and_subscribe() -> s::Fallible { ]) } -pub fn monitor_sway_events( - extra_props: Arc>>, -) { - let config = config::load_config(); +pub fn monitor_sway_events(fdata: FocusData, config: &Config) { let mut focus_counter = 0; let mut resets = 0; let max_resets = 10; @@ -75,21 +91,19 @@ pub fn monitor_sway_events( match ev_result { Ok(ev) => match ev { s::Event::Window(win_ev) => { - let extra_props_clone = extra_props.clone(); focus_counter += 1; show_extra_props_state = handle_window_event( win_ev, - extra_props_clone, - &config, + &fdata, + config, focus_counter, ); } s::Event::Workspace(ws_ev) => { - let extra_props_clone = extra_props.clone(); focus_counter += 1; show_extra_props_state = handle_workspace_event( ws_ev, - extra_props_clone, + &fdata, focus_counter, ); } @@ -114,7 +128,7 @@ pub fn monitor_sway_events( if show_extra_props_state { log::debug!( "New extra_props state:\n{:#?}", - *extra_props.read().unwrap() + *fdata.focus_tick_by_id.read().unwrap() ); } } @@ -126,7 +140,7 @@ pub fn monitor_sway_events( fn handle_window_event( ev: Box, - extra_props: Arc>>, + fdata: &FocusData, config: &config::Config, focus_val: u64, ) -> bool { @@ -136,18 +150,21 @@ fn handle_window_event( match change { s::WindowChange::Focus => { layout::maybe_auto_tile(config); - update_last_focus_tick(container.id, extra_props, focus_val); + fdata.send(FocusMessage::FocusEvent(FocusEvent { + node_id: container.id, + ev_focus_ctr: focus_val, + })); log::debug!("Handled window event type {:?}", change); true } s::WindowChange::New => { layout::maybe_auto_tile(config); - update_last_focus_tick(container.id, extra_props, focus_val); + fdata.ensure_id(container.id); log::debug!("Handled window event type {:?}", change); true } s::WindowChange::Close => { - remove_extra_props(container.id, extra_props); + fdata.remove_focus_data(container.id); layout::maybe_auto_tile(config); log::debug!("Handled window event type {:?}", change); true @@ -166,7 +183,7 @@ fn handle_window_event( fn handle_workspace_event( ev: Box, - extra_props: Arc>>, + fdata: &FocusData, focus_val: u64, ) -> bool { let s::WorkspaceEvent { @@ -177,20 +194,19 @@ fn handle_workspace_event( } = *ev; match change { s::WorkspaceChange::Init | s::WorkspaceChange::Focus => { - update_last_focus_tick( - current - .expect("No current in Init or Focus workspace event") - .id, - extra_props, - focus_val, - ); + let id = current + .expect("No current in Init or Focus workspace event") + .id; + fdata.send(FocusMessage::FocusEvent(FocusEvent { + node_id: id, + ev_focus_ctr: focus_val, + })); log::debug!("Handled workspace event type {:?}", change); true } s::WorkspaceChange::Empty => { - remove_extra_props( + fdata.remove_focus_data( current.expect("No current in Empty workspace event").id, - extra_props, ); log::debug!("Handled workspace event type {:?}", change); true @@ -199,35 +215,7 @@ fn handle_workspace_event( } } -fn update_last_focus_tick( - id: i64, - extra_props: Arc>>, - focus_val: u64, -) { - let mut write_lock = extra_props.write().unwrap(); - if let Some(wp) = write_lock.get_mut(&id) { - wp.last_focus_tick = focus_val; - } else { - write_lock.insert( - id, - t::ExtraProps { - last_focus_tick: focus_val, - last_focus_tick_for_next_prev_seq: focus_val, - }, - ); - } -} - -fn remove_extra_props( - id: i64, - extra_props: Arc>>, -) { - extra_props.write().unwrap().remove(&id); -} - -pub fn serve_client_requests( - extra_props: Arc>>, -) { +pub fn serve_client_requests(fdata: FocusData) { match std::fs::remove_file(util::get_swayr_socket_path()) { Ok(()) => log::debug!("Deleted stale socket from previous run."), Err(e) => log::error!("Could not delete socket:\n{:?}", e), @@ -238,7 +226,7 @@ pub fn serve_client_requests( for stream in listener.incoming() { match stream { Ok(stream) => { - handle_client_request(stream, extra_props.clone()); + handle_client_request(stream, &fdata); } Err(err) => { log::error!("Error handling client request: {}", err); @@ -253,16 +241,13 @@ pub fn serve_client_requests( } } -fn handle_client_request( - mut stream: UnixStream, - extra_props: Arc>>, -) { +fn handle_client_request(mut stream: UnixStream, fdata: &FocusData) { let mut cmd_str = String::new(); if stream.read_to_string(&mut cmd_str).is_ok() { if let Ok(cmd) = serde_json::from_str::(&cmd_str) { cmds::exec_swayr_cmd(cmds::ExecSwayrCmdArgs { cmd: &cmd, - extra_props, + focus_data: fdata, }); } else { log::error!( @@ -274,3 +259,110 @@ fn handle_client_request( log::error!("Could not read command from client."); } } + +#[derive(Debug)] +enum InhibitState { + FocusInhibit, + FocusActive, +} + +impl InhibitState { + pub fn set(&mut self) { + if let InhibitState::FocusActive = self { + log::debug!("Inhibiting tick focus updates"); + *self = InhibitState::FocusInhibit; + } + } + + pub fn clear(&mut self) { + if let InhibitState::FocusInhibit = self { + log::debug!("Activating tick focus updates"); + *self = InhibitState::FocusActive; + } + } +} + +fn focus_lock_in_handler( + focus_chan: mpsc::Receiver, + fdata: FocusData, + lockin_delay: Duration, +) { + // Focus event that has not yet been locked-in to the LRU order + let mut pending_fev: Option = None; + + // Toggle to inhibit LRU focus updates + let mut inhibit = InhibitState::FocusActive; + + let update_focus = |fev: Option| { + if let Some(fev) = fev { + log::debug!("Locking-in focus on {}", fev.node_id); + fdata.update_last_focus_tick( + fev.node_id, + fev.ev_focus_ctr, + ) + } + }; + + // outer loop, waiting for focus events + loop { + let fmsg = match focus_chan.recv() { + Ok(fmsg) => fmsg, + Err(mpsc::RecvError) => return, + }; + + let mut fev = match fmsg { + FocusMessage::TickUpdateInhibit => { + inhibit.set(); + continue; + } + FocusMessage::TickUpdateActivate => { + inhibit.clear(); + update_focus(pending_fev.take()); + continue + } + FocusMessage::FocusEvent(fev) => { + if let InhibitState::FocusInhibit = inhibit { + // update the pending event but take no further action + pending_fev = Some(fev); + continue; + } + fev + } + }; + + // Inner loop, waiting for the lock-in delay to expire + loop { + let fmsg = match focus_chan.recv_timeout(lockin_delay) { + Ok(fmsg) => fmsg, + Err(mpsc::RecvTimeoutError::Timeout) => { + update_focus(Some(fev)); + break; // return to outer loop + } + Err(mpsc::RecvTimeoutError::Disconnected) => return, + }; + + match fmsg { + FocusMessage::TickUpdateInhibit => { + // inhibit requested before currently focused container + // was locked-in, set it as pending in case no other + // focus changes are made while updates remain inhibited + inhibit.set(); + pending_fev = Some(fev); + break; // return to outer loop with a preset pending_fev + } + FocusMessage::TickUpdateActivate => { + // updates reactivated while we were waiting to lockin + // Immediately lockin fev + inhibit.clear(); + update_focus(Some(fev)); + break; + } + FocusMessage::FocusEvent(new_fev) => { + // start a new wait (inner) loop with the most recent + // focus event + fev = new_fev; + } + } + } + } +} diff --git a/swayr/src/focus.rs b/swayr/src/focus.rs new file mode 100644 index 0000000..5340094 --- /dev/null +++ b/swayr/src/focus.rs @@ -0,0 +1,90 @@ +// Copyright (C) 2021-2022 Tassilo Horn +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + +//! Structure to hold window focus timestamps used by swayrd + +use std::sync::mpsc; +use std::sync::Arc; +use std::sync::RwLock; +use std::collections::HashMap; + +/// Data tracking most recent focus events for Sway windows/containers +#[derive(Clone)] +pub struct FocusData { + pub focus_tick_by_id: Arc>>, + pub focus_chan: mpsc::Sender, +} + +impl FocusData { + pub fn last_focus_tick(&self, id: i64) -> u64 { + *self.focus_tick_by_id + .read() + .unwrap() + .get(&id) + .unwrap_or(&0) + } + + pub fn update_last_focus_tick( + &self, + id: i64, + focus_val: u64, + ) { + let mut write_lock = self.focus_tick_by_id.write().unwrap(); + if let Some(tick) = write_lock.get_mut(&id) { + *tick = focus_val; + } + // else the node has since been closed before this focus event got locked in + } + + pub fn remove_focus_data( + &self, + id: i64 + ) { + self.focus_tick_by_id.write().unwrap().remove(&id); + } + + /// Ensures that a given node_id is present in the ExtraProps map, this + /// later used to distinguish between the case where a container was + /// closed (it will no longer be in the map) or + pub fn ensure_id(&self, id: i64) { + let mut write_lock = self.focus_tick_by_id.write().unwrap(); + if write_lock.get(&id).is_none() { + write_lock.insert( + id, + 0, + ); + } + } + + pub fn send(&self, fmsg: FocusMessage) { + // todo can this be removed? + if let FocusMessage::FocusEvent(ref fev) = fmsg { + self.ensure_id(fev.node_id); + } + self.focus_chan.send(fmsg) + .expect("Failed to send focus event over channel"); + } +} + +pub struct FocusEvent { + pub node_id: i64, // node receiving the focus + pub ev_focus_ctr: u64, // Counter for this specific focus event +} + +pub enum FocusMessage { + TickUpdateInhibit, + TickUpdateActivate, + FocusEvent(FocusEvent), +} diff --git a/swayr/src/lib.rs b/swayr/src/lib.rs index 1d4c535..c3b6e3b 100644 --- a/swayr/src/lib.rs +++ b/swayr/src/lib.rs @@ -26,3 +26,4 @@ pub mod layout; pub mod shared; pub mod tree; pub mod util; +pub mod focus; diff --git a/swayr/src/tree.rs b/swayr/src/tree.rs index 5362bdd..877780d 100644 --- a/swayr/src/tree.rs +++ b/swayr/src/tree.rs @@ -16,6 +16,7 @@ //! Convenience data structures built from the IPC structs. use crate::config; +use crate::focus::FocusData; use crate::shared::fmt::subst_placeholders; use crate::shared::ipc; use crate::shared::ipc::NodeMethods; @@ -23,25 +24,16 @@ use crate::util; use crate::util::DisplayFormat; use once_cell::sync::Lazy; use regex::Regex; -use serde::{Deserialize, Serialize}; use std::cell::RefCell; use std::cmp; use std::collections::HashMap; use std::rc::Rc; use swayipc as s; -/// Extra properties gathered by swayrd for windows and workspaces. -#[derive(Copy, Clone, Debug, Deserialize, Serialize)] -pub struct ExtraProps { - pub last_focus_tick: u64, - pub last_focus_tick_for_next_prev_seq: u64, -} - pub struct Tree<'a> { root: &'a s::Node, id_node: HashMap, id_parent: HashMap, - extra_props: &'a HashMap, } #[derive(Copy, Clone, PartialEq, Eq)] @@ -83,28 +75,23 @@ impl<'a> Tree<'a> { } } - pub fn last_focus_tick(&self, id: i64) -> u64 { - self.extra_props.get(&id).map_or(0, |wp| wp.last_focus_tick) - } - - pub fn last_focus_tick_for_next_prev_seq(&self, id: i64) -> u64 { - self.extra_props - .get(&id) - .map_or(0, |wp| wp.last_focus_tick_for_next_prev_seq) - } - fn sorted_nodes_of_type_1( &self, node: &'a s::Node, t: ipc::Type, + fdata: &FocusData, ) -> Vec<&s::Node> { let mut v: Vec<&s::Node> = node.nodes_of_type(t); - self.sort_by_urgency_and_lru_time_1(&mut v); + self.sort_by_urgency_and_lru_time_1(&mut v, fdata); v } - fn sorted_nodes_of_type(&self, t: ipc::Type) -> Vec<&s::Node> { - self.sorted_nodes_of_type_1(self.root, t) + fn sorted_nodes_of_type( + &self, + t: ipc::Type, + fdata: &FocusData, + ) -> Vec<&s::Node> { + self.sorted_nodes_of_type_1(self.root, t, fdata) } fn as_display_nodes( @@ -137,16 +124,16 @@ impl<'a> Tree<'a> { self.as_display_nodes(&outputs, IndentLevel::Fixed(0)) } - pub fn get_workspaces(&self) -> Vec { - let mut v = self.sorted_nodes_of_type(ipc::Type::Workspace); + pub fn get_workspaces(&self, fdata: &FocusData) -> Vec { + let mut v = self.sorted_nodes_of_type(ipc::Type::Workspace, fdata); if !v.is_empty() { v.rotate_left(1); } self.as_display_nodes(&v, IndentLevel::Fixed(0)) } - pub fn get_windows(&self) -> Vec { - let mut v = self.sorted_nodes_of_type(ipc::Type::Window); + pub fn get_windows(&self, fdata: &FocusData) -> Vec { + let mut v = self.sorted_nodes_of_type(ipc::Type::Window, fdata); // Rotate, but only non-urgent windows. Those should stay at the front // as they are the most likely switch candidates. let mut x; @@ -169,13 +156,17 @@ impl<'a> Tree<'a> { self.as_display_nodes(&x, IndentLevel::Fixed(0)) } - pub fn get_workspaces_and_windows(&self) -> Vec { - let workspaces = self.sorted_nodes_of_type(ipc::Type::Workspace); + pub fn get_workspaces_and_windows( + &self, + fdata: &FocusData, + ) -> Vec { + let workspaces = self.sorted_nodes_of_type(ipc::Type::Workspace, fdata); let mut first = true; let mut v = vec![]; for ws in workspaces { v.push(ws); - let mut wins = self.sorted_nodes_of_type_1(ws, ipc::Type::Window); + let mut wins = + self.sorted_nodes_of_type_1(ws, ipc::Type::Window, fdata); if first && !wins.is_empty() { wins.rotate_left(1); first = false; @@ -186,15 +177,19 @@ impl<'a> Tree<'a> { self.as_display_nodes(&v, IndentLevel::WorkspacesZeroWindowsOne) } - fn sort_by_urgency_and_lru_time_1(&self, v: &mut [&s::Node]) { + fn sort_by_urgency_and_lru_time_1( + &self, + v: &mut [&s::Node], + fdata: &FocusData, + ) { v.sort_by(|a, b| { if a.urgent && !b.urgent { cmp::Ordering::Less } else if !a.urgent && b.urgent { cmp::Ordering::Greater } else { - let lru_a = self.last_focus_tick(a.id); - let lru_b = self.last_focus_tick(b.id); + let lru_a = fdata.last_focus_tick(a.id); + let lru_b = fdata.last_focus_tick(b.id); lru_a.cmp(&lru_b).reverse() } }); @@ -204,36 +199,41 @@ impl<'a> Tree<'a> { &self, n: &'a s::Node, v: Rc>>, + fdata: &FocusData, ) { v.borrow_mut().push(n); let mut children: Vec<&s::Node> = n.nodes.iter().collect(); children.append(&mut n.floating_nodes.iter().collect()); - self.sort_by_urgency_and_lru_time_1(&mut children); + self.sort_by_urgency_and_lru_time_1(&mut children, fdata); for c in children { - self.push_subtree_sorted(c, Rc::clone(&v)); + self.push_subtree_sorted(c, Rc::clone(&v), fdata); } } pub fn get_outputs_workspaces_containers_and_windows( &self, + fdata: &FocusData, ) -> Vec { - let outputs = self.sorted_nodes_of_type(ipc::Type::Output); + let outputs = self.sorted_nodes_of_type(ipc::Type::Output, fdata); let v: Rc>> = Rc::new(RefCell::new(vec![])); for o in outputs { - self.push_subtree_sorted(o, Rc::clone(&v)); + self.push_subtree_sorted(o, Rc::clone(&v), fdata); } let x = self.as_display_nodes(&*v.borrow(), IndentLevel::TreeDepth(1)); x } - pub fn get_workspaces_containers_and_windows(&self) -> Vec { - let workspaces = self.sorted_nodes_of_type(ipc::Type::Workspace); + pub fn get_workspaces_containers_and_windows( + &self, + fdata: &FocusData, + ) -> Vec { + let workspaces = self.sorted_nodes_of_type(ipc::Type::Workspace, fdata); let v: Rc>> = Rc::new(RefCell::new(vec![])); for ws in workspaces { - self.push_subtree_sorted(ws, Rc::clone(&v)); + self.push_subtree_sorted(ws, Rc::clone(&v), fdata); } let x = self.as_display_nodes(&*v.borrow(), IndentLevel::TreeDepth(2)); @@ -281,10 +281,7 @@ fn init_id_parent<'a>( } } -pub fn get_tree<'a>( - root: &'a s::Node, - extra_props: &'a HashMap, -) -> Tree<'a> { +pub fn get_tree(root: &s::Node) -> Tree { let mut id_node: HashMap = HashMap::new(); let mut id_parent: HashMap = HashMap::new(); init_id_parent(root, None, &mut id_node, &mut id_parent); @@ -293,7 +290,6 @@ pub fn get_tree<'a>( root, id_node, id_parent, - extra_props, } }