mirror of
https://github.com/imgurbot12/rmenu.git
synced 2024-11-10 11:33:48 +01:00
feat: implemented simple window-switcher for sway
This commit is contained in:
parent
ff88a5c042
commit
63a87c3873
@ -7,4 +7,5 @@ members = [
|
||||
"plugin-desktop",
|
||||
"plugin-audio",
|
||||
"plugin-network",
|
||||
"plugin-window",
|
||||
]
|
||||
|
2
Makefile
2
Makefile
@ -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
17
plugin-window/Cargo.toml
Normal 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
52
plugin-window/src/main.rs
Normal 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
93
plugin-window/src/sway.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user