Add sinks command
This commit is contained in:
18
src/eww.rs
18
src/eww.rs
@ -1,23 +1,11 @@
|
|||||||
use eyre::{bail, Context};
|
use crate::util::CommandExt;
|
||||||
|
|
||||||
/// Update eww bar variable
|
/// Update eww bar variable
|
||||||
pub fn update_var(key: &str, value: &str) -> eyre::Result<()> {
|
pub fn update_var(key: &str, value: &str) -> eyre::Result<()> {
|
||||||
println!("eww update {key}={value}");
|
std::process::Command::new("eww")
|
||||||
let output = std::process::Command::new("eww")
|
|
||||||
.arg("update")
|
.arg("update")
|
||||||
.arg(format!("{key}={value}"))
|
.arg(format!("{key}={value}"))
|
||||||
.output()
|
.just_exec()?;
|
||||||
.wrap_err("failed to execute 'eww update'")?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
let stdout = std::str::from_utf8(&output.stdout).unwrap_or("Invalid UTF-8");
|
|
||||||
let stderr = std::str::from_utf8(&output.stderr).unwrap_or("Invalid UTF-8");
|
|
||||||
|
|
||||||
eprintln!("'eww update' stdout: {stdout}");
|
|
||||||
eprintln!("'eww update' stderr: {stderr}");
|
|
||||||
|
|
||||||
bail!("'eww update' failed. See logs.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::{eww, output, Command};
|
use crate::{eww, output, pulse, util::CommandExt, Command};
|
||||||
use eyre::{bail, eyre, Context};
|
use eyre::{bail, Context};
|
||||||
use serde::{de::DeserializeOwned, Deserialize};
|
use serde::{de::DeserializeOwned, Deserialize};
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
@ -22,34 +22,26 @@ pub fn handle(command: Command) -> eyre::Result<()> {
|
|||||||
std::process::Command::new("hyprctl")
|
std::process::Command::new("hyprctl")
|
||||||
.args(["dispatch", "workspace"])
|
.args(["dispatch", "workspace"])
|
||||||
.arg(format!("{to}"))
|
.arg(format!("{to}"))
|
||||||
.status()
|
.just_exec()?;
|
||||||
.map_err(|e| eyre!("hyprctl error: {e}"))?;
|
|
||||||
|
|
||||||
eww::update_var("workspaces", &get_workspaces()?)?;
|
eww::update_var("workspaces", &get_workspaces()?)?;
|
||||||
}
|
}
|
||||||
Command::KeyboardLayout { .. } => {
|
Command::KeyboardLayout { .. } => {
|
||||||
bail!("not supported on Hyprland");
|
bail!("not supported on Hyprland");
|
||||||
}
|
}
|
||||||
|
Command::Sinks => {
|
||||||
|
println!("{}", serde_json::to_string(&pulse::get_sinks()?)?);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hyprctl<T: DeserializeOwned>(args: &[&str]) -> eyre::Result<T> {
|
fn hyprctl<T: DeserializeOwned>(args: &[&str]) -> eyre::Result<T> {
|
||||||
let workspaces_output = std::process::Command::new("hyprctl")
|
std::process::Command::new("hyprctl")
|
||||||
.args(args)
|
.args(args)
|
||||||
.arg("-j") // JSON output
|
.arg("-j") // JSON output
|
||||||
.output()
|
.just_exec_json()
|
||||||
.map_err(|e| eyre!("hyprctl error: {e}"))?;
|
|
||||||
|
|
||||||
let workspaces_stdout = str::from_utf8(&workspaces_output.stdout)?;
|
|
||||||
//let workspaces_stderr = str::from_utf8(&workspaces_output.stderr)?;
|
|
||||||
|
|
||||||
if !workspaces_output.status.success() {
|
|
||||||
bail!("hyprctl error, non-zero exit code");
|
|
||||||
}
|
|
||||||
|
|
||||||
serde_json::from_str(workspaces_stdout).wrap_err("Failed to deserialize output from hyprctl")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a JSON-string containing info about workspaces
|
/// Get a JSON-string containing info about workspaces
|
||||||
|
|||||||
@ -7,6 +7,8 @@ mod eww;
|
|||||||
mod hyprland;
|
mod hyprland;
|
||||||
mod niri;
|
mod niri;
|
||||||
mod output;
|
mod output;
|
||||||
|
mod pulse;
|
||||||
|
mod util;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
struct Opt {
|
struct Opt {
|
||||||
@ -28,6 +30,9 @@ enum Command {
|
|||||||
SwitchWorkspace {
|
SwitchWorkspace {
|
||||||
to: u8,
|
to: u8,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Get the list out audio sinks
|
||||||
|
Sinks,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum WindowManager {
|
enum WindowManager {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{eww, Command, Workspace};
|
use crate::{eww, pulse, Command, Workspace};
|
||||||
use eyre::{bail, eyre, Context};
|
use eyre::{bail, eyre, Context};
|
||||||
use niri_ipc::{Socket, WorkspaceReferenceArg};
|
use niri_ipc::{Socket, WorkspaceReferenceArg};
|
||||||
|
|
||||||
@ -21,7 +21,10 @@ pub fn handle(command: Command) -> eyre::Result<()> {
|
|||||||
|
|
||||||
eww::update_var("workspaces", &get_workspaces()?)?;
|
eww::update_var("workspaces", &get_workspaces()?)?;
|
||||||
}
|
}
|
||||||
Command::KeyboardLayout { next: _ } => todo!(),
|
Command::KeyboardLayout { next: _ } => bail!("Not implemented"),
|
||||||
|
Command::Sinks => {
|
||||||
|
println!("{}", serde_json::to_string(&pulse::get_sinks()?)?);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@ -8,3 +8,18 @@ pub struct Workspace {
|
|||||||
pub monitor: u32,
|
pub monitor: u32,
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug, Serialize)]
|
||||||
|
pub struct SinkList {
|
||||||
|
pub default: Option<Sink>,
|
||||||
|
pub all: Vec<Sink>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
pub struct Sink {
|
||||||
|
pub name: String,
|
||||||
|
pub pretty_name: String,
|
||||||
|
pub muted: bool,
|
||||||
|
pub default: bool,
|
||||||
|
pub volume: u8,
|
||||||
|
}
|
||||||
|
|||||||
62
src/pulse.rs
Normal file
62
src/pulse.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
output::{self, SinkList},
|
||||||
|
util::CommandExt,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Sink {
|
||||||
|
pub state: String,
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub mute: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sinks() -> eyre::Result<output::SinkList> {
|
||||||
|
let default_sink = pactl_get_default_sink_name()?;
|
||||||
|
let sinks = pactl_get_sinks()?;
|
||||||
|
|
||||||
|
let mut output = SinkList::default();
|
||||||
|
|
||||||
|
for sink in sinks {
|
||||||
|
let default = dbg!(&sink.name) == dbg!(&default_sink);
|
||||||
|
|
||||||
|
let sink = output::Sink {
|
||||||
|
name: sink.name,
|
||||||
|
pretty_name: sink.description,
|
||||||
|
muted: sink.mute,
|
||||||
|
default,
|
||||||
|
volume: 0, // TODO
|
||||||
|
};
|
||||||
|
|
||||||
|
if default {
|
||||||
|
output.default = Some(sink.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
output.all.push(sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.all.sort_by_key(|s| s.pretty_name.clone());
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pactl_get_sinks() -> eyre::Result<Vec<Sink>> {
|
||||||
|
std::process::Command::new("pactl")
|
||||||
|
.args(["--format", "json"])
|
||||||
|
.args(["list", "sinks"])
|
||||||
|
.just_exec_json()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pactl_get_default_sink_name() -> eyre::Result<String> {
|
||||||
|
let mut default_sink = std::process::Command::new("pactl")
|
||||||
|
.arg("get-default-sink")
|
||||||
|
.just_exec()?;
|
||||||
|
|
||||||
|
while default_sink.ends_with(char::is_whitespace) {
|
||||||
|
default_sink.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(default_sink)
|
||||||
|
}
|
||||||
48
src/util.rs
Normal file
48
src/util.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
use eyre::{eyre, Context};
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use std::{fmt::Write, str};
|
||||||
|
|
||||||
|
pub trait CommandExt: Sized {
|
||||||
|
fn just_exec(&mut self) -> eyre::Result<String>;
|
||||||
|
|
||||||
|
fn error(&self) -> String;
|
||||||
|
|
||||||
|
fn just_exec_json<T: DeserializeOwned>(&mut self) -> eyre::Result<T> {
|
||||||
|
let error = self.error();
|
||||||
|
let output = self.just_exec()?;
|
||||||
|
serde_json::from_str(&output)
|
||||||
|
.wrap_err("Failed to deserialize output as JSON")
|
||||||
|
.wrap_err(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandExt for std::process::Command {
|
||||||
|
fn just_exec(&mut self) -> eyre::Result<String> {
|
||||||
|
let error = self.error();
|
||||||
|
let output = self
|
||||||
|
.output()
|
||||||
|
.wrap_err("Failed to exec command")
|
||||||
|
.wrap_err_with(|| error.clone())?;
|
||||||
|
|
||||||
|
let stdout = str::from_utf8(&output.stdout)
|
||||||
|
.wrap_err("Output wasn't valid UTF-8")
|
||||||
|
.wrap_err_with(|| error.clone())?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(eyre!("Non-zero exit code ({})", output.status))
|
||||||
|
.wrap_err_with(|| error.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(stdout.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error(&self) -> String {
|
||||||
|
let mut command_string = self.get_program().to_string_lossy().to_string();
|
||||||
|
|
||||||
|
for arg in self.get_args() {
|
||||||
|
let _ = write!(&mut command_string, " {arg:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
format!("Command failed: {command_string}")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user