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-desktop",
|
||||||
"plugin-audio",
|
"plugin-audio",
|
||||||
"plugin-network",
|
"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/run ${DEST}/rmenu-run
|
||||||
cp -vf ./target/release/audio ${DEST}/rmenu-audio
|
cp -vf ./target/release/audio ${DEST}/rmenu-audio
|
||||||
cp -vf ./target/release/network ${DEST}/rmenu-network
|
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
|
cp -vf ./rmenu/public/config.yaml ${DEST}/config.yaml
|
||||||
|
|
||||||
build: build-rmenu build-plugins
|
build: build-rmenu build-plugins
|
||||||
@ -34,3 +35,4 @@ build-plugins:
|
|||||||
${CARGO} build -p desktop ${FLAGS}
|
${CARGO} build -p desktop ${FLAGS}
|
||||||
${CARGO} build -p audio ${FLAGS}
|
${CARGO} build -p audio ${FLAGS}
|
||||||
${CARGO} build -p network ${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"]
|
exec: ["~/.config/rmenu/rmenu-network"]
|
||||||
cache: false
|
cache: false
|
||||||
placeholder: "Connect to the Specified Wi-Fi"
|
placeholder: "Connect to the Specified Wi-Fi"
|
||||||
|
window:
|
||||||
|
exec: ["~/.config/rmenu/rmenu-window"]
|
||||||
|
cache: false
|
||||||
|
placeholder: "Jump to the Specified Window"
|
||||||
|
|
||||||
# custom keybindings
|
# custom keybindings
|
||||||
keybinds:
|
keybinds:
|
||||||
|
Loading…
Reference in New Issue
Block a user