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.
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<RwLock<HashMap<i64, t::ExtraProps>>>,
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<s::Output> {
}
}
pub fn switch_to_urgent_or_lru_window(
extra_props: &HashMap<i64, t::ExtraProps>,
) {
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<i64, t::ExtraProps>,
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<i64, t::ExtraProps>,
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<i64, t::ExtraProps>) {
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<i64, t::ExtraProps>) {
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<i64, t::ExtraProps>) {
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<i64, t::ExtraProps>) {
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<i64, t::ExtraProps>,
) {
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<i64, t::ExtraProps>) {
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<i64, t::ExtraProps>, 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<i64, t::ExtraProps>) {
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<i64, t::ExtraProps>,
) {
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<i64, t::ExtraProps>) {
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<i64, t::ExtraProps>) {
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<i64, t::ExtraProps>) {
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<i64, t::ExtraProps>,
fdata: &FocusData,
pred: Box<dyn Fn(&t::DisplayNode) -> 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<i64, t::ExtraProps>,
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()

@ -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<Menu>,
format: Option<Format>,
layout: Option<Layout>,
focus: Option<Focus>,
}
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())
.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<u64>,
}
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()),
}
}
}

@ -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<RwLock<HashMap<i64, t::ExtraProps>>> =
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,
};
let config = config::load_config();
let lockin_delay = config.get_focus_lockin_delay();
{
let fdata = fdata.clone();
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> {
@ -48,10 +67,7 @@ fn connect_and_subscribe() -> s::Fallible<s::EventStream> {
])
}
pub fn monitor_sway_events(
extra_props: Arc<RwLock<HashMap<i64, t::ExtraProps>>>,
) {
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<s::WindowEvent>,
extra_props: Arc<RwLock<HashMap<i64, t::ExtraProps>>>,
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<s::WorkspaceEvent>,
extra_props: Arc<RwLock<HashMap<i64, t::ExtraProps>>>,
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
let id = current
.expect("No current in Init or Focus workspace event")
.id,
extra_props,
focus_val,
);
.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<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>>>,
) {
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<RwLock<HashMap<i64, t::ExtraProps>>>,
) {
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::<cmds::SwayrCommand>(&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<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 tree;
pub mod util;
pub mod focus;

@ -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<i64, &'a s::Node>,
id_parent: HashMap<i64, i64>,
extra_props: &'a HashMap<i64, ExtraProps>,
}
#[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<DisplayNode> {
let mut v = self.sorted_nodes_of_type(ipc::Type::Workspace);
pub fn get_workspaces(&self, fdata: &FocusData) -> Vec<DisplayNode> {
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<DisplayNode> {
let mut v = self.sorted_nodes_of_type(ipc::Type::Window);
pub fn get_windows(&self, fdata: &FocusData) -> Vec<DisplayNode> {
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<DisplayNode> {
let workspaces = self.sorted_nodes_of_type(ipc::Type::Workspace);
pub fn get_workspaces_and_windows(
&self,
fdata: &FocusData,
) -> Vec<DisplayNode> {
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<RefCell<Vec<&'a s::Node>>>,
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<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![]));
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<DisplayNode> {
let workspaces = self.sorted_nodes_of_type(ipc::Type::Workspace);
pub fn get_workspaces_containers_and_windows(
&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![]));
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<i64, ExtraProps>,
) -> Tree<'a> {
pub fn get_tree(root: &s::Node) -> Tree {
let mut id_node: HashMap<i64, &s::Node> = HashMap::new();
let mut id_parent: HashMap<i64, i64> = 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,
}
}

Loading…
Cancel
Save