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.
main
William Barsse 3 years ago committed by Tassilo Horn
parent 559bf941e5
commit 33f85b576c
  1. 202
      swayr/src/cmds.rs
  2. 26
      swayr/src/config.rs
  3. 222
      swayr/src/daemon.rs
  4. 90
      swayr/src/focus.rs
  5. 1
      swayr/src/lib.rs
  6. 84
      swayr/src/tree.rs

@ -16,6 +16,8 @@
//! Functions and data structures of the swayr client. //! Functions and data structures of the swayr client.
use crate::config as cfg; use crate::config as cfg;
use crate::focus::FocusData;
use crate::focus::FocusMessage;
use crate::layout; use crate::layout;
use crate::shared::ipc; use crate::shared::ipc;
use crate::shared::ipc::NodeMethods; use crate::shared::ipc::NodeMethods;
@ -26,10 +28,6 @@ use once_cell::sync::Lazy;
use rand::prelude::SliceRandom; use rand::prelude::SliceRandom;
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::atomic;
use std::sync::Arc;
use std::sync::RwLock;
use swayipc as s; use swayipc as s;
pub fn run_sway_command_1(cmd: &str) { pub fn run_sway_command_1(cmd: &str) {
@ -227,7 +225,7 @@ impl SwayrCommand {
pub struct ExecSwayrCmdArgs<'a> { pub struct ExecSwayrCmdArgs<'a> {
pub cmd: &'a SwayrCommand, pub cmd: &'a SwayrCommand,
pub extra_props: Arc<RwLock<HashMap<i64, t::ExtraProps>>>, pub focus_data: &'a FocusData,
} }
impl DisplayFormat for SwayrCommand { impl DisplayFormat for SwayrCommand {
@ -247,86 +245,62 @@ fn always_true(_x: &t::DisplayNode) -> bool {
true true
} }
static IN_NEXT_PREV_WINDOW_SEQ: atomic::AtomicBool =
atomic::AtomicBool::new(false);
pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) { 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() { if args.cmd.is_prev_next_window_variant() {
let before = fdata.send(FocusMessage::TickUpdateInhibit);
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;
}
}
} else { } else {
IN_NEXT_PREV_WINDOW_SEQ.store(false, atomic::Ordering::SeqCst); fdata.send(FocusMessage::TickUpdateActivate);
} }
match args.cmd { match args.cmd {
SwayrCommand::Nop => {} SwayrCommand::Nop => {}
SwayrCommand::SwitchToUrgentOrLRUWindow => { SwayrCommand::SwitchToUrgentOrLRUWindow => {
switch_to_urgent_or_lru_window(&*props.read().unwrap()) switch_to_urgent_or_lru_window(fdata)
} }
SwayrCommand::SwitchToAppOrUrgentOrLRUWindow { name } => { SwayrCommand::SwitchToAppOrUrgentOrLRUWindow { name } => {
switch_to_app_or_urgent_or_lru_window( switch_to_app_or_urgent_or_lru_window(Some(name), fdata)
Some(name),
&*props.read().unwrap(),
)
} }
SwayrCommand::SwitchToMarkOrUrgentOrLRUWindow { con_mark } => { SwayrCommand::SwitchToMarkOrUrgentOrLRUWindow { con_mark } => {
switch_to_mark_or_urgent_or_lru_window( switch_to_mark_or_urgent_or_lru_window(Some(con_mark), fdata)
Some(con_mark),
&*props.read().unwrap(),
)
}
SwayrCommand::SwitchWindow => switch_window(&*props.read().unwrap()),
SwayrCommand::SwitchWorkspace => {
switch_workspace(&*props.read().unwrap())
} }
SwayrCommand::SwitchOutput => switch_output(&*props.read().unwrap()), SwayrCommand::SwitchWindow => switch_window(fdata),
SwayrCommand::SwitchWorkspace => switch_workspace(fdata),
SwayrCommand::SwitchOutput => switch_output(),
SwayrCommand::SwitchWorkspaceOrWindow => { SwayrCommand::SwitchWorkspaceOrWindow => {
switch_workspace_or_window(&*props.read().unwrap()) switch_workspace_or_window(fdata)
} }
SwayrCommand::SwitchWorkspaceContainerOrWindow => { SwayrCommand::SwitchWorkspaceContainerOrWindow => {
switch_workspace_container_or_window(&*props.read().unwrap()) switch_workspace_container_or_window(fdata)
}
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())
} }
SwayrCommand::SwitchTo => switch_to(fdata),
SwayrCommand::QuitWindow { kill } => quit_window(fdata, *kill),
SwayrCommand::QuitWorkspaceOrWindow => quit_workspace_or_window(fdata),
SwayrCommand::QuitWorkspaceContainerOrWindow => { SwayrCommand::QuitWorkspaceContainerOrWindow => {
quit_workspace_container_or_window(&*props.read().unwrap()) quit_workspace_container_or_window(fdata)
} }
SwayrCommand::MoveFocusedToWorkspace => { SwayrCommand::MoveFocusedToWorkspace => {
move_focused_to_workspace(&*props.read().unwrap()) move_focused_to_workspace(fdata)
}
SwayrCommand::MoveFocusedTo => move_focused_to(&*props.read().unwrap()),
SwayrCommand::SwapFocusedWith => {
swap_focused_with(&*props.read().unwrap())
} }
SwayrCommand::MoveFocusedTo => move_focused_to(fdata),
SwayrCommand::SwapFocusedWith => swap_focused_with(fdata),
SwayrCommand::NextWindow { windows } => focus_window_in_direction( SwayrCommand::NextWindow { windows } => focus_window_in_direction(
Direction::Forward, Direction::Forward,
windows, windows,
&*props.read().unwrap(), fdata,
Box::new(always_true), Box::new(always_true),
), ),
SwayrCommand::PrevWindow { windows } => focus_window_in_direction( SwayrCommand::PrevWindow { windows } => focus_window_in_direction(
Direction::Backward, Direction::Backward,
windows, windows,
&*props.read().unwrap(), fdata,
Box::new(always_true), Box::new(always_true),
), ),
SwayrCommand::NextTiledWindow { windows } => focus_window_in_direction( SwayrCommand::NextTiledWindow { windows } => focus_window_in_direction(
Direction::Forward, Direction::Forward,
windows, windows,
&*props.read().unwrap(), fdata,
Box::new(|dn: &t::DisplayNode| { Box::new(|dn: &t::DisplayNode| {
!dn.node.is_floating() !dn.node.is_floating()
&& dn.tree.is_child_of_tiled_container(dn.node.id) && 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( SwayrCommand::PrevTiledWindow { windows } => focus_window_in_direction(
Direction::Backward, Direction::Backward,
windows, windows,
&*props.read().unwrap(), fdata,
Box::new(|dn: &t::DisplayNode| { Box::new(|dn: &t::DisplayNode| {
!dn.node.is_floating() !dn.node.is_floating()
&& dn.tree.is_child_of_tiled_container(dn.node.id) && 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( focus_window_in_direction(
Direction::Forward, Direction::Forward,
windows, windows,
&*props.read().unwrap(), fdata,
Box::new(|dn: &t::DisplayNode| { Box::new(|dn: &t::DisplayNode| {
!dn.node.is_floating() !dn.node.is_floating()
&& dn && dn
@ -358,7 +332,7 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) {
focus_window_in_direction( focus_window_in_direction(
Direction::Backward, Direction::Backward,
windows, windows,
&*props.read().unwrap(), fdata,
Box::new(|dn: &t::DisplayNode| { Box::new(|dn: &t::DisplayNode| {
!dn.node.is_floating() !dn.node.is_floating()
&& dn && dn
@ -371,7 +345,7 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) {
focus_window_in_direction( focus_window_in_direction(
Direction::Forward, Direction::Forward,
windows, windows,
&*props.read().unwrap(), fdata,
Box::new(|dn: &t::DisplayNode| dn.node.is_floating()), Box::new(|dn: &t::DisplayNode| dn.node.is_floating()),
) )
} }
@ -379,7 +353,7 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) {
focus_window_in_direction( focus_window_in_direction(
Direction::Backward, Direction::Backward,
windows, windows,
&*props.read().unwrap(), fdata,
Box::new(|dn: &t::DisplayNode| dn.node.is_floating()), 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( focus_window_of_same_layout_in_direction(
Direction::Forward, Direction::Forward,
windows, windows,
&*props.read().unwrap(), fdata,
) )
} }
SwayrCommand::PrevWindowOfSameLayout { windows } => { SwayrCommand::PrevWindowOfSameLayout { windows } => {
focus_window_of_same_layout_in_direction( focus_window_of_same_layout_in_direction(
Direction::Backward, Direction::Backward,
windows, windows,
&*props.read().unwrap(), fdata,
) )
} }
SwayrCommand::TileWorkspace { floating } => { SwayrCommand::TileWorkspace { floating } => {
@ -473,7 +447,7 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) {
{ {
exec_swayr_cmd(ExecSwayrCmdArgs { exec_swayr_cmd(ExecSwayrCmdArgs {
cmd: c, cmd: c,
extra_props: props, focus_data: args.focus_data,
}); });
} }
} }
@ -495,19 +469,17 @@ pub fn get_outputs() -> Vec<s::Output> {
} }
} }
pub fn switch_to_urgent_or_lru_window( pub fn switch_to_urgent_or_lru_window(fdata: &FocusData) {
extra_props: &HashMap<i64, t::ExtraProps>, switch_to_app_or_urgent_or_lru_window(None, fdata)
) {
switch_to_app_or_urgent_or_lru_window(None, extra_props)
} }
pub fn switch_to_app_or_urgent_or_lru_window( pub fn switch_to_app_or_urgent_or_lru_window(
name: Option<&str>, name: Option<&str>,
extra_props: &HashMap<i64, t::ExtraProps>, fdata: &FocusData,
) { ) {
let root = ipc::get_root_node(false); let root = ipc::get_root_node(false);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root);
let wins = tree.get_windows(); let wins = tree.get_windows(fdata);
let app_win = let app_win =
name.and_then(|n| wins.iter().find(|w| w.node.get_app_name() == n)); name.and_then(|n| wins.iter().find(|w| w.node.get_app_name() == n));
focus_win_if_not_focused(app_win, wins.get(0)) 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( pub fn switch_to_mark_or_urgent_or_lru_window(
con_mark: Option<&str>, con_mark: Option<&str>,
extra_props: &HashMap<i64, t::ExtraProps>, fdata: &FocusData,
) { ) {
let root = ipc::get_root_node(false); let root = ipc::get_root_node(false);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root);
let wins = tree.get_windows(); let wins = tree.get_windows(fdata);
let marked_win = con_mark.and_then(|mark| { let marked_win = con_mark.and_then(|mark| {
wins.iter() wins.iter()
.find(|w| w.node.marks.contains(&mark.to_owned())) .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<i64, t::ExtraProps>) { pub fn switch_window(fdata: &FocusData) {
let root = ipc::get_root_node(true); 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 window", &tree.get_windows()); select_and_focus("Select window", &tree.get_windows(fdata));
} }
pub fn switch_workspace(extra_props: &HashMap<i64, t::ExtraProps>) { pub fn switch_workspace(fdata: &FocusData) {
let root = ipc::get_root_node(false); 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 workspace", &tree.get_workspaces()); select_and_focus("Select workspace", &tree.get_workspaces(fdata));
} }
pub fn switch_output(extra_props: &HashMap<i64, t::ExtraProps>) { pub fn switch_output() {
let root = ipc::get_root_node(false); 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()); select_and_focus("Select output", &tree.get_outputs());
} }
pub fn switch_workspace_or_window(extra_props: &HashMap<i64, t::ExtraProps>) { pub fn switch_workspace_or_window(fdata: &FocusData) {
let root = ipc::get_root_node(true); 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_and_focus(
"Select workspace or window", "Select workspace or window",
&tree.get_workspaces_and_windows(), &tree.get_workspaces_and_windows(fdata),
); );
} }
pub fn switch_workspace_container_or_window( pub fn switch_workspace_container_or_window(fdata: &FocusData) {
extra_props: &HashMap<i64, t::ExtraProps>,
) {
let root = ipc::get_root_node(true); 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_and_focus(
"Select workspace, container or window", "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<i64, t::ExtraProps>) { pub fn switch_to(fdata: &FocusData) {
let root = ipc::get_root_node(true); 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_and_focus(
"Select output, workspace, container or window", "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<i64, t::ExtraProps>, kill: bool) { pub fn quit_window(fdata: &FocusData, kill: bool) {
let root = ipc::get_root_node(true); 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 window", &tree.get_windows(), kill); select_and_quit("Quit window", &tree.get_windows(fdata), kill);
} }
pub fn quit_workspace_or_window(extra_props: &HashMap<i64, t::ExtraProps>) { pub fn quit_workspace_or_window(fdata: &FocusData) {
let root = ipc::get_root_node(true); let root = ipc::get_root_node(true);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root);
select_and_quit( select_and_quit(
"Quit workspace or window", "Quit workspace or window",
&tree.get_workspaces_and_windows(), &tree.get_workspaces_and_windows(fdata),
false, false,
); );
} }
pub fn quit_workspace_container_or_window( pub fn quit_workspace_container_or_window(fdata: &FocusData) {
extra_props: &HashMap<i64, t::ExtraProps>,
) {
let root = ipc::get_root_node(true); let root = ipc::get_root_node(true);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root);
select_and_quit( select_and_quit(
"Quit workspace, container or window", "Quit workspace, container or window",
&tree.get_workspaces_containers_and_windows(), &tree.get_workspaces_containers_and_windows(fdata),
false, 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<i64, t::ExtraProps>) { pub fn move_focused_to_workspace(fdata: &FocusData) {
let root = ipc::get_root_node(true); 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( select_and_move_focused_to(
"Move focused container to workspace", "Move focused container to workspace",
&tree.get_workspaces(), &tree.get_workspaces(fdata),
); );
} }
pub fn move_focused_to(extra_props: &HashMap<i64, t::ExtraProps>) { pub fn move_focused_to(fdata: &FocusData) {
let root = ipc::get_root_node(true); 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( select_and_move_focused_to(
"Move focused container to workspace or container", "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<i64, t::ExtraProps>) { pub fn swap_focused_with(fdata: &FocusData) {
let root = ipc::get_root_node(true); 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( match util::select_from_menu(
"Swap focused with", "Swap focused with",
&tree.get_workspaces_containers_and_windows(), &tree.get_workspaces_containers_and_windows(fdata),
) { ) {
Ok(tn) => match tn.node.get_type() { Ok(tn) => match tn.node.get_type() {
ipc::Type::Workspace | ipc::Type::Container | ipc::Type::Window => { ipc::Type::Workspace | ipc::Type::Container | ipc::Type::Window => {
@ -835,12 +803,12 @@ pub enum Direction {
pub fn focus_window_in_direction( pub fn focus_window_in_direction(
dir: Direction, dir: Direction,
consider_wins: &ConsiderWindows, consider_wins: &ConsiderWindows,
extra_props: &HashMap<i64, t::ExtraProps>, fdata: &FocusData,
pred: Box<dyn Fn(&t::DisplayNode) -> bool>, pred: Box<dyn Fn(&t::DisplayNode) -> bool>,
) { ) {
let root = ipc::get_root_node(false); let root = ipc::get_root_node(false);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root);
let mut wins = tree.get_windows(); let mut wins = tree.get_windows(fdata);
if consider_wins == &ConsiderWindows::CurrentWorkspace { if consider_wins == &ConsiderWindows::CurrentWorkspace {
let cur_ws = tree.get_current_workspace(); let cur_ws = tree.get_current_workspace();
@ -859,8 +827,8 @@ pub fn focus_window_in_direction(
} }
wins.sort_by(|a, b| { wins.sort_by(|a, b| {
let lru_a = tree.last_focus_tick_for_next_prev_seq(a.node.id); let lru_a = fdata.last_focus_tick(a.node.id);
let lru_b = tree.last_focus_tick_for_next_prev_seq(b.node.id); let lru_b = fdata.last_focus_tick(b.node.id);
lru_a.cmp(&lru_b).reverse() 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( pub fn focus_window_of_same_layout_in_direction(
dir: Direction, dir: Direction,
consider_wins: &ConsiderWindows, consider_wins: &ConsiderWindows,
extra_props: &HashMap<i64, t::ExtraProps>, fdata: &FocusData,
) { ) {
let root = ipc::get_root_node(false); let root = ipc::get_root_node(false);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root);
let wins = tree.get_windows(); let wins = tree.get_windows(fdata);
let cur_win = wins.iter().find(|w| w.node.focused); let cur_win = wins.iter().find(|w| w.node.focused);
if let Some(cur_win) = cur_win { if let Some(cur_win) = cur_win {
focus_window_in_direction( focus_window_in_direction(
dir, dir,
consider_wins, consider_wins,
extra_props, fdata,
if cur_win.node.is_floating() { if cur_win.node.is_floating() {
Box::new(|dn| dn.node.is_floating()) Box::new(|dn| dn.node.is_floating())
} else if !cur_win.node.is_floating() } else if !cur_win.node.is_floating()

@ -18,12 +18,14 @@
use crate::shared::cfg; use crate::shared::cfg;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Config { pub struct Config {
menu: Option<Menu>, menu: Option<Menu>,
format: Option<Format>, format: Option<Format>,
layout: Option<Layout>, layout: Option<Layout>,
focus: Option<Focus>,
} }
fn tilde_expand_file_names(file_names: Vec<String>) -> Vec<String> { fn tilde_expand_file_names(file_names: Vec<String>) -> Vec<String> {
@ -155,6 +157,16 @@ impl Config {
.or_else(|| Layout::default().auto_tile_min_window_width_per_output_width_as_map()) .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.") .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)] #[derive(Debug, Serialize, Deserialize)]
@ -199,6 +211,11 @@ impl Layout {
} }
} }
#[derive(Debug, Serialize, Deserialize)]
pub struct Focus {
lockin_delay: Option<u64>,
}
impl Default for Menu { impl Default for Menu {
fn default() -> Self { fn default() -> Self {
Menu { 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 { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Config { Config {
menu: Some(Menu::default()), menu: Some(Menu::default()),
format: Some(Format::default()), format: Some(Format::default()),
layout: Some(Layout::default()), layout: Some(Layout::default()),
focus: Some(Focus::default()),
} }
} }
} }

@ -16,28 +16,47 @@
//! Functions and data structures of the swayrd daemon. //! Functions and data structures of the swayrd daemon.
use crate::cmds; 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::layout;
use crate::tree as t;
use crate::util; use crate::util;
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Read; use std::io::Read;
use std::os::unix::net::{UnixListener, UnixStream}; use std::os::unix::net::{UnixListener, UnixStream};
use std::sync::mpsc;
use std::sync::Arc; use std::sync::Arc;
use std::sync::RwLock; use std::sync::RwLock;
use std::thread; use std::thread;
use std::time::Duration;
use swayipc as s; use swayipc as s;
pub fn run_daemon() { pub fn run_daemon() {
let extra_props: Arc<RwLock<HashMap<i64, t::ExtraProps>>> = let (focus_tx, focus_rx) = mpsc::channel();
Arc::new(RwLock::new(HashMap::new())); let fdata = FocusData {
let extra_props_for_ev_handler = extra_props.clone(); focus_tick_by_id: Arc::new(RwLock::new(HashMap::new())),
focus_chan: focus_tx,
};
let config = config::load_config();
let lockin_delay = config.get_focus_lockin_delay();
{
let fdata = fdata.clone();
thread::spawn(move || { thread::spawn(move || {
monitor_sway_events(extra_props_for_ev_handler); monitor_sway_events(fdata, &config);
}); });
}
{
let fdata = fdata.clone();
thread::spawn(move || {
focus_lock_in_handler(focus_rx, fdata, lockin_delay);
});
}
serve_client_requests(extra_props); serve_client_requests(fdata);
} }
fn connect_and_subscribe() -> s::Fallible<s::EventStream> { fn connect_and_subscribe() -> s::Fallible<s::EventStream> {
@ -48,10 +67,7 @@ fn connect_and_subscribe() -> s::Fallible<s::EventStream> {
]) ])
} }
pub fn monitor_sway_events( pub fn monitor_sway_events(fdata: FocusData, config: &Config) {
extra_props: Arc<RwLock<HashMap<i64, t::ExtraProps>>>,
) {
let config = config::load_config();
let mut focus_counter = 0; let mut focus_counter = 0;
let mut resets = 0; let mut resets = 0;
let max_resets = 10; let max_resets = 10;
@ -75,21 +91,19 @@ pub fn monitor_sway_events(
match ev_result { match ev_result {
Ok(ev) => match ev { Ok(ev) => match ev {
s::Event::Window(win_ev) => { s::Event::Window(win_ev) => {
let extra_props_clone = extra_props.clone();
focus_counter += 1; focus_counter += 1;
show_extra_props_state = handle_window_event( show_extra_props_state = handle_window_event(
win_ev, win_ev,
extra_props_clone, &fdata,
&config, config,
focus_counter, focus_counter,
); );
} }
s::Event::Workspace(ws_ev) => { s::Event::Workspace(ws_ev) => {
let extra_props_clone = extra_props.clone();
focus_counter += 1; focus_counter += 1;
show_extra_props_state = handle_workspace_event( show_extra_props_state = handle_workspace_event(
ws_ev, ws_ev,
extra_props_clone, &fdata,
focus_counter, focus_counter,
); );
} }
@ -114,7 +128,7 @@ pub fn monitor_sway_events(
if show_extra_props_state { if show_extra_props_state {
log::debug!( log::debug!(
"New extra_props state:\n{:#?}", "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( fn handle_window_event(
ev: Box<s::WindowEvent>, ev: Box<s::WindowEvent>,
extra_props: Arc<RwLock<HashMap<i64, t::ExtraProps>>>, fdata: &FocusData,
config: &config::Config, config: &config::Config,
focus_val: u64, focus_val: u64,
) -> bool { ) -> bool {
@ -136,18 +150,21 @@ fn handle_window_event(
match change { match change {
s::WindowChange::Focus => { s::WindowChange::Focus => {
layout::maybe_auto_tile(config); 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); log::debug!("Handled window event type {:?}", change);
true true
} }
s::WindowChange::New => { s::WindowChange::New => {
layout::maybe_auto_tile(config); 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); log::debug!("Handled window event type {:?}", change);
true true
} }
s::WindowChange::Close => { s::WindowChange::Close => {
remove_extra_props(container.id, extra_props); fdata.remove_focus_data(container.id);
layout::maybe_auto_tile(config); layout::maybe_auto_tile(config);
log::debug!("Handled window event type {:?}", change); log::debug!("Handled window event type {:?}", change);
true true
@ -166,7 +183,7 @@ fn handle_window_event(
fn handle_workspace_event( fn handle_workspace_event(
ev: Box<s::WorkspaceEvent>, ev: Box<s::WorkspaceEvent>,
extra_props: Arc<RwLock<HashMap<i64, t::ExtraProps>>>, fdata: &FocusData,
focus_val: u64, focus_val: u64,
) -> bool { ) -> bool {
let s::WorkspaceEvent { let s::WorkspaceEvent {
@ -177,20 +194,19 @@ fn handle_workspace_event(
} = *ev; } = *ev;
match change { match change {
s::WorkspaceChange::Init | s::WorkspaceChange::Focus => { s::WorkspaceChange::Init | s::WorkspaceChange::Focus => {
update_last_focus_tick( let id = current
current
.expect("No current in Init or Focus workspace event") .expect("No current in Init or Focus workspace event")
.id, .id;
extra_props, fdata.send(FocusMessage::FocusEvent(FocusEvent {
focus_val, node_id: id,
); ev_focus_ctr: focus_val,
}));
log::debug!("Handled workspace event type {:?}", change); log::debug!("Handled workspace event type {:?}", change);
true true
} }
s::WorkspaceChange::Empty => { s::WorkspaceChange::Empty => {
remove_extra_props( fdata.remove_focus_data(
current.expect("No current in Empty workspace event").id, current.expect("No current in Empty workspace event").id,
extra_props,
); );
log::debug!("Handled workspace event type {:?}", change); log::debug!("Handled workspace event type {:?}", change);
true true
@ -199,35 +215,7 @@ fn handle_workspace_event(
} }
} }
fn update_last_focus_tick( pub fn serve_client_requests(fdata: FocusData) {
id: i64,
extra_props: Arc<RwLock<HashMap<i64, t::ExtraProps>>>,
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<RwLock<HashMap<i64, t::ExtraProps>>>,
) {
extra_props.write().unwrap().remove(&id);
}
pub fn serve_client_requests(
extra_props: Arc<RwLock<HashMap<i64, t::ExtraProps>>>,
) {
match std::fs::remove_file(util::get_swayr_socket_path()) { match std::fs::remove_file(util::get_swayr_socket_path()) {
Ok(()) => log::debug!("Deleted stale socket from previous run."), Ok(()) => log::debug!("Deleted stale socket from previous run."),
Err(e) => log::error!("Could not delete socket:\n{:?}", e), Err(e) => log::error!("Could not delete socket:\n{:?}", e),
@ -238,7 +226,7 @@ pub fn serve_client_requests(
for stream in listener.incoming() { for stream in listener.incoming() {
match stream { match stream {
Ok(stream) => { Ok(stream) => {
handle_client_request(stream, extra_props.clone()); handle_client_request(stream, &fdata);
} }
Err(err) => { Err(err) => {
log::error!("Error handling client request: {}", err); log::error!("Error handling client request: {}", err);
@ -253,16 +241,13 @@ pub fn serve_client_requests(
} }
} }
fn handle_client_request( fn handle_client_request(mut stream: UnixStream, fdata: &FocusData) {
mut stream: UnixStream,
extra_props: Arc<RwLock<HashMap<i64, t::ExtraProps>>>,
) {
let mut cmd_str = String::new(); let mut cmd_str = String::new();
if stream.read_to_string(&mut cmd_str).is_ok() { if stream.read_to_string(&mut cmd_str).is_ok() {
if let Ok(cmd) = serde_json::from_str::<cmds::SwayrCommand>(&cmd_str) { if let Ok(cmd) = serde_json::from_str::<cmds::SwayrCommand>(&cmd_str) {
cmds::exec_swayr_cmd(cmds::ExecSwayrCmdArgs { cmds::exec_swayr_cmd(cmds::ExecSwayrCmdArgs {
cmd: &cmd, cmd: &cmd,
extra_props, focus_data: fdata,
}); });
} else { } else {
log::error!( log::error!(
@ -274,3 +259,110 @@ fn handle_client_request(
log::error!("Could not read command from client."); 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<FocusMessage>,
fdata: FocusData,
lockin_delay: Duration,
) {
// Focus event that has not yet been locked-in to the LRU order
let mut pending_fev: Option<FocusEvent> = None;
// Toggle to inhibit LRU focus updates
let mut inhibit = InhibitState::FocusActive;
let update_focus = |fev: Option<FocusEvent>| {
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;
}
}
}
}
}

@ -0,0 +1,90 @@
// Copyright (C) 2021-2022 Tassilo Horn <tsdh@gnu.org>
//
// 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 <https://www.gnu.org/licenses/>.
//! 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<RwLock<HashMap<i64, u64>>>,
pub focus_chan: mpsc::Sender<FocusMessage>,
}
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),
}

@ -26,3 +26,4 @@ pub mod layout;
pub mod shared; pub mod shared;
pub mod tree; pub mod tree;
pub mod util; pub mod util;
pub mod focus;

@ -16,6 +16,7 @@
//! Convenience data structures built from the IPC structs. //! Convenience data structures built from the IPC structs.
use crate::config; use crate::config;
use crate::focus::FocusData;
use crate::shared::fmt::subst_placeholders; use crate::shared::fmt::subst_placeholders;
use crate::shared::ipc; use crate::shared::ipc;
use crate::shared::ipc::NodeMethods; use crate::shared::ipc::NodeMethods;
@ -23,25 +24,16 @@ use crate::util;
use crate::util::DisplayFormat; use crate::util::DisplayFormat;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize};
use std::cell::RefCell; use std::cell::RefCell;
use std::cmp; use std::cmp;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use swayipc as s; 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> { pub struct Tree<'a> {
root: &'a s::Node, root: &'a s::Node,
id_node: HashMap<i64, &'a s::Node>, id_node: HashMap<i64, &'a s::Node>,
id_parent: HashMap<i64, i64>, id_parent: HashMap<i64, i64>,
extra_props: &'a HashMap<i64, ExtraProps>,
} }
#[derive(Copy, Clone, PartialEq, Eq)] #[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( fn sorted_nodes_of_type_1(
&self, &self,
node: &'a s::Node, node: &'a s::Node,
t: ipc::Type, t: ipc::Type,
fdata: &FocusData,
) -> Vec<&s::Node> { ) -> Vec<&s::Node> {
let mut v: Vec<&s::Node> = node.nodes_of_type(t); 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 v
} }
fn sorted_nodes_of_type(&self, t: ipc::Type) -> Vec<&s::Node> { fn sorted_nodes_of_type(
self.sorted_nodes_of_type_1(self.root, t) &self,
t: ipc::Type,
fdata: &FocusData,
) -> Vec<&s::Node> {
self.sorted_nodes_of_type_1(self.root, t, fdata)
} }
fn as_display_nodes( fn as_display_nodes(
@ -137,16 +124,16 @@ impl<'a> Tree<'a> {
self.as_display_nodes(&outputs, IndentLevel::Fixed(0)) self.as_display_nodes(&outputs, IndentLevel::Fixed(0))
} }
pub fn get_workspaces(&self) -> Vec<DisplayNode> { pub fn get_workspaces(&self, fdata: &FocusData) -> Vec<DisplayNode> {
let mut v = self.sorted_nodes_of_type(ipc::Type::Workspace); let mut v = self.sorted_nodes_of_type(ipc::Type::Workspace, fdata);
if !v.is_empty() { if !v.is_empty() {
v.rotate_left(1); v.rotate_left(1);
} }
self.as_display_nodes(&v, IndentLevel::Fixed(0)) self.as_display_nodes(&v, IndentLevel::Fixed(0))
} }
pub fn get_windows(&self) -> Vec<DisplayNode> { pub fn get_windows(&self, fdata: &FocusData) -> Vec<DisplayNode> {
let mut v = self.sorted_nodes_of_type(ipc::Type::Window); let mut v = self.sorted_nodes_of_type(ipc::Type::Window, fdata);
// Rotate, but only non-urgent windows. Those should stay at the front // Rotate, but only non-urgent windows. Those should stay at the front
// as they are the most likely switch candidates. // as they are the most likely switch candidates.
let mut x; let mut x;
@ -169,13 +156,17 @@ impl<'a> Tree<'a> {
self.as_display_nodes(&x, IndentLevel::Fixed(0)) self.as_display_nodes(&x, IndentLevel::Fixed(0))
} }
pub fn get_workspaces_and_windows(&self) -> Vec<DisplayNode> { pub fn get_workspaces_and_windows(
let workspaces = self.sorted_nodes_of_type(ipc::Type::Workspace); &self,
fdata: &FocusData,
) -> Vec<DisplayNode> {
let workspaces = self.sorted_nodes_of_type(ipc::Type::Workspace, fdata);
let mut first = true; let mut first = true;
let mut v = vec![]; let mut v = vec![];
for ws in workspaces { for ws in workspaces {
v.push(ws); 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() { if first && !wins.is_empty() {
wins.rotate_left(1); wins.rotate_left(1);
first = false; first = false;
@ -186,15 +177,19 @@ impl<'a> Tree<'a> {
self.as_display_nodes(&v, IndentLevel::WorkspacesZeroWindowsOne) 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| { v.sort_by(|a, b| {
if a.urgent && !b.urgent { if a.urgent && !b.urgent {
cmp::Ordering::Less cmp::Ordering::Less
} else if !a.urgent && b.urgent { } else if !a.urgent && b.urgent {
cmp::Ordering::Greater cmp::Ordering::Greater
} else { } else {
let lru_a = self.last_focus_tick(a.id); let lru_a = fdata.last_focus_tick(a.id);
let lru_b = self.last_focus_tick(b.id); let lru_b = fdata.last_focus_tick(b.id);
lru_a.cmp(&lru_b).reverse() lru_a.cmp(&lru_b).reverse()
} }
}); });
@ -204,36 +199,41 @@ impl<'a> Tree<'a> {
&self, &self,
n: &'a s::Node, n: &'a s::Node,
v: Rc<RefCell<Vec<&'a s::Node>>>, v: Rc<RefCell<Vec<&'a s::Node>>>,
fdata: &FocusData,
) { ) {
v.borrow_mut().push(n); v.borrow_mut().push(n);
let mut children: Vec<&s::Node> = n.nodes.iter().collect(); let mut children: Vec<&s::Node> = n.nodes.iter().collect();
children.append(&mut n.floating_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 { 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( pub fn get_outputs_workspaces_containers_and_windows(
&self, &self,
fdata: &FocusData,
) -> Vec<DisplayNode> { ) -> Vec<DisplayNode> {
let outputs = self.sorted_nodes_of_type(ipc::Type::Output); let outputs = self.sorted_nodes_of_type(ipc::Type::Output, fdata);
let v: Rc<RefCell<Vec<&s::Node>>> = Rc::new(RefCell::new(vec![])); let v: Rc<RefCell<Vec<&s::Node>>> = Rc::new(RefCell::new(vec![]));
for o in outputs { 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)); let x = self.as_display_nodes(&*v.borrow(), IndentLevel::TreeDepth(1));
x x
} }
pub fn get_workspaces_containers_and_windows(&self) -> Vec<DisplayNode> { pub fn get_workspaces_containers_and_windows(
let workspaces = self.sorted_nodes_of_type(ipc::Type::Workspace); &self,
fdata: &FocusData,
) -> Vec<DisplayNode> {
let workspaces = self.sorted_nodes_of_type(ipc::Type::Workspace, fdata);
let v: Rc<RefCell<Vec<&s::Node>>> = Rc::new(RefCell::new(vec![])); let v: Rc<RefCell<Vec<&s::Node>>> = Rc::new(RefCell::new(vec![]));
for ws in workspaces { 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)); 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>( pub fn get_tree(root: &s::Node) -> Tree {
root: &'a s::Node,
extra_props: &'a HashMap<i64, ExtraProps>,
) -> Tree<'a> {
let mut id_node: HashMap<i64, &s::Node> = HashMap::new(); let mut id_node: HashMap<i64, &s::Node> = HashMap::new();
let mut id_parent: HashMap<i64, i64> = HashMap::new(); let mut id_parent: HashMap<i64, i64> = HashMap::new();
init_id_parent(root, None, &mut id_node, &mut id_parent); init_id_parent(root, None, &mut id_node, &mut id_parent);
@ -293,7 +290,6 @@ pub fn get_tree<'a>(
root, root,
id_node, id_node,
id_parent, id_parent,
extra_props,
} }
} }

Loading…
Cancel
Save