feat: implemented simple window-switcher for sway

This commit is contained in:
imgurbot12 2023-08-14 22:11:14 -07:00
parent ff88a5c042
commit 63a87c3873
6 changed files with 169 additions and 0 deletions

View File

@ -7,4 +7,5 @@ members = [
"plugin-desktop",
"plugin-audio",
"plugin-network",
"plugin-window",
]

View File

@ -22,6 +22,7 @@ deploy:
cp -vf ./target/release/run ${DEST}/rmenu-run
cp -vf ./target/release/audio ${DEST}/rmenu-audio
cp -vf ./target/release/network ${DEST}/rmenu-network
cp -vf ./target/release/window ${DEST}/rmenu-window
cp -vf ./rmenu/public/config.yaml ${DEST}/config.yaml
build: build-rmenu build-plugins
@ -34,3 +35,4 @@ build-plugins:
${CARGO} build -p desktop ${FLAGS}
${CARGO} build -p audio ${FLAGS}
${CARGO} build -p network ${FLAGS}
${CARGO} build -p window ${FLAGS}

17
plugin-window/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "window"
version = "0.0.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["sway"]
sway = []
[dependencies]
anyhow = "1.0.72"
clap = { version = "4.3.21", features = ["derive"] }
rmenu-plugin = { version = "0.0.1", path = "../rmenu-plugin" }
serde = { version = "1.0.183", features = ["derive"] }
serde_json = "1.0.104"

52
plugin-window/src/main.rs Normal file
View File

@ -0,0 +1,52 @@
use std::fmt::Debug;
use anyhow::Result;
use clap::{Parser, Subcommand};
use rmenu_plugin::Entry;
#[cfg(feature = "sway")]
mod sway;
/// Trait To Implement for Window Focus
pub trait WindowManager: Debug {
fn focus(&self, id: &str) -> Result<()>;
fn entries(&self) -> Result<Vec<Entry>>;
}
#[derive(Debug, Subcommand)]
pub enum Commands {
ListWindow,
Focus { id: String },
}
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
pub struct Cli {
#[clap(subcommand)]
command: Option<Commands>,
}
/// Retrieve WindowManager Implementation
#[allow(unreachable_code)]
fn get_impl() -> impl WindowManager {
#[cfg(feature = "sway")]
return sway::SwayManager {};
// if no features are enabled for some reason?
panic!("No Implementations Available")
}
fn main() -> Result<()> {
let cli = Cli::parse();
let windows = get_impl();
let command = cli.command.unwrap_or(Commands::ListWindow);
match command {
Commands::Focus { id } => windows.focus(&id)?,
Commands::ListWindow => {
for entry in windows.entries()? {
println!("{}", serde_json::to_string(&entry).unwrap());
}
}
}
Ok(())
}

93
plugin-window/src/sway.rs Normal file
View File

@ -0,0 +1,93 @@
//! Sway WindowMangager Window Selector
use std::process::{Command, Stdio};
use anyhow::{anyhow, Context, Result};
use rmenu_plugin::Entry;
use serde::Deserialize;
use serde_json::Value;
use crate::WindowManager;
static SWAY_TYPE_KEY: &'static str = "type";
static SWAY_NODES_KEY: &'static str = "nodes";
static SWAY_WINDOW_TYPE: &'static str = "con";
static SWAY_WINDOW_NAME: &'static str = "name";
#[derive(Debug, Deserialize)]
pub struct SwayWindow {
pub name: String,
pub pid: u64,
pub focused: bool,
}
#[derive(Debug)]
pub struct SwayManager {}
pub fn get_windows() -> Result<Vec<SwayWindow>> {
// retrieve output of swaymsg tree
let out = Command::new("swaymsg")
.args(["-t", "get_tree"])
.stdout(Stdio::piped())
.output()
.context("SwayMsg Failed to Execute")?;
if !out.status.success() {
return Err(anyhow!("Invalid SwayMsg Status: {:?}", out.status));
}
// read output as string
let result: Value =
serde_json::from_slice(&out.stdout).context("Failed to Parse SwayMsg Output")?;
// recursively parse object for window definitions
let mut nodes = vec![result];
let mut windows = vec![];
while let Some(item) = nodes.pop() {
if !item.is_object() {
return Err(anyhow!("Unexpected Node Value: {:?}", item));
}
// pass additional nodes if not a valid window object
let Some(ntype) = item.get(SWAY_TYPE_KEY) else { continue };
let is_nulled = item
.get(SWAY_WINDOW_NAME)
.map(|v| v.is_null())
.unwrap_or(false);
if ntype != SWAY_WINDOW_TYPE || is_nulled {
let Some(snodes) = item.get(SWAY_NODES_KEY) else { continue };
match snodes {
Value::Array(array) => nodes.extend(array.clone().into_iter()),
_ => return Err(anyhow!("Unexpected NodeList Value: {:?}", snodes)),
}
continue;
}
let window: SwayWindow =
serde_json::from_value(item.clone()).context("Failed to Parse Window Object")?;
windows.push(window);
}
windows.sort_by_key(|w| w.focused);
Ok(windows)
}
impl WindowManager for SwayManager {
/// Focus on Specified Window
fn focus(&self, id: &str) -> Result<()> {
let out = Command::new("swaymsg")
.arg(format!("[pid={}] focus", id))
.output()
.context("Failed SwayMsg To Focus Window: {id:?}")?;
if !out.status.success() {
return Err(anyhow!("SwayMsg Exited with Error: {:?}", out.status));
}
Ok(())
}
/// Generate RMenu Entries
fn entries(&self) -> Result<Vec<Entry>> {
let exe = std::env::current_exe()?.to_str().unwrap().to_string();
let windows = get_windows()?;
let entries = windows
.into_iter()
.map(|w| {
let exec = format!("{exe} focus {:?}", w.pid);
Entry::new(&w.name, &exec, None)
})
.collect();
Ok(entries)
}
}

View File

@ -33,6 +33,10 @@ plugins:
exec: ["~/.config/rmenu/rmenu-network"]
cache: false
placeholder: "Connect to the Specified Wi-Fi"
window:
exec: ["~/.config/rmenu/rmenu-window"]
cache: false
placeholder: "Jump to the Specified Window"
# custom keybindings
keybinds: