Compare commits
No commits in common. "0997b124a2e0a70074c1f79a6620c4efb1b4b0e5" and "1318c51a4414431e07bce4ca376e74ee295f7140" have entirely different histories.
0997b124a2
...
1318c51a44
@ -56,7 +56,7 @@ niri-tag = {
|
||||
````
|
||||
it is assumed you use niri-flake, or else will use the `stable` package output; this is important for the niri IPC definitions.
|
||||
|
||||
next,
|
||||
next,\
|
||||
- add `inputs.niri-tag.nixosModules.niri-tag` to your module imports
|
||||
- add `services.niri-tag.enable = true;` somewhere in your config
|
||||
- if you wish to use a stable niri instead of unstable from niri-flake (default), set `services.niri-tag.package = inputs.niri-tag.packages.${pkgs.system}.stable;`
|
||||
@ -87,7 +87,7 @@ set up:
|
||||
|
||||
alternatively,\
|
||||
you may bind as you see fit the tagctl commands `add` `remove` `toggle` for window tagging,
|
||||
and `enable-tag` `disable-tag` `toggle-tag` `exclusive-tag` for managing tags.
|
||||
and `enable-tag` `disable-tag` `toggle-tag` for managing tags.
|
||||
|
||||
all commands (except `remove`) take a tag number from 1-255 after their command.
|
||||
|
||||
|
14
cli/main.rs
14
cli/main.rs
@ -1,9 +1,8 @@
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use clap::{Parser, Subcommand};
|
||||
use microxdg::Xdg;
|
||||
use niri_tag::TagCmd;
|
||||
use nix::unistd::geteuid;
|
||||
use std::{io::Write, os::unix::net::UnixStream, path::PathBuf, str::FromStr};
|
||||
use std::{io::Write, os::unix::net::UnixStream};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "tagctl")]
|
||||
@ -21,7 +20,6 @@ enum Commands {
|
||||
EnableTag { tag: u8 },
|
||||
DisableTag { tag: u8 },
|
||||
ToggleTag { tag: u8 },
|
||||
ExclusiveTag { tag: u8 },
|
||||
}
|
||||
|
||||
impl From<Commands> for niri_tag::TagCmd {
|
||||
@ -33,7 +31,6 @@ impl From<Commands> for niri_tag::TagCmd {
|
||||
Commands::EnableTag { tag } => TagCmd::EnableTag(tag),
|
||||
Commands::DisableTag { tag } => TagCmd::DisableTag(tag),
|
||||
Commands::ToggleTag { tag } => TagCmd::ToggleTag(tag),
|
||||
Commands::ExclusiveTag { tag } => TagCmd::ExclusiveTag(tag),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -49,18 +46,13 @@ fn main() -> Result<()> {
|
||||
EnableTag { tag } if tag > 0 => (),
|
||||
DisableTag { tag } if tag > 0 => (),
|
||||
ToggleTag { tag } if tag > 0 => (),
|
||||
ExclusiveTag { .. } => (),
|
||||
_ => return Err(anyhow!("Can't change tag 0!")),
|
||||
};
|
||||
|
||||
let cmd = TagCmd::from(cli.cmd);
|
||||
|
||||
let xdg = Xdg::new()?;
|
||||
let mut path = xdg
|
||||
.runtime()?
|
||||
.unwrap_or(PathBuf::from_str(&format!("/run/user/{}", geteuid()))?);
|
||||
path.push("niri-tag.sock");
|
||||
let mut ipc = UnixStream::connect(path).context("Connecting to niri-tag ipc socket")?;
|
||||
let mut ipc = UnixStream::connect(format!("/run/user/{}/niri-tag.sock", geteuid()))
|
||||
.context("Connecting to niri-tag ipc socket")?;
|
||||
ipc.write_all(serde_json::to_string(&cmd)?.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2,13 +2,10 @@ use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
net::SocketAddr,
|
||||
os::linux::net::SocketAddrExt,
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use crate::socket::{create_niri_socket, tell};
|
||||
use anyhow::{Error, Result, anyhow};
|
||||
use microxdg::Xdg;
|
||||
use niri_ipc::{Event, Request};
|
||||
use niri_tag::{TagCmd, TagEvent, TagState};
|
||||
use nix::unistd::geteuid;
|
||||
@ -38,16 +35,8 @@ pub async fn event_consumer(tx: channel::Sender<Event>) -> Result<()> {
|
||||
unreachable!("Listener loop ended");
|
||||
}
|
||||
|
||||
fn get_run_path() -> Result<PathBuf> {
|
||||
let xdg = Xdg::new()?;
|
||||
Ok(xdg
|
||||
.runtime()?
|
||||
.unwrap_or(PathBuf::from_str(&format!("/run/user/{}", geteuid()))?))
|
||||
}
|
||||
|
||||
async fn create_provider_socket(name: &'static str, socket: &'static str) -> Result<UnixListener> {
|
||||
let mut sock_path = get_run_path()?;
|
||||
sock_path.push(format!("{}.sock", socket));
|
||||
let sock_path = format!("/run/user/{}/{}.sock", geteuid(), socket);
|
||||
if smol::fs::metadata(&sock_path).await.is_ok() {
|
||||
tracing::debug!("removing old {} socket", name);
|
||||
smol::fs::remove_file(&sock_path).await?;
|
||||
|
@ -24,9 +24,8 @@ pub struct NiriTag {
|
||||
}
|
||||
|
||||
enum TagAction {
|
||||
Window(u64),
|
||||
Tag(u8),
|
||||
TagExclusive(u8),
|
||||
ChangeWindow(u64),
|
||||
ChangeTag(u8),
|
||||
}
|
||||
|
||||
impl NiriTag {
|
||||
@ -42,79 +41,39 @@ impl NiriTag {
|
||||
})
|
||||
}
|
||||
|
||||
fn same_output(&self, wsid: u64, candidates: &HashMap<u64, Workspace>) -> Result<Workspace> {
|
||||
candidates
|
||||
.values()
|
||||
.filter_map(|ws| {
|
||||
let output = ws.output.clone()?;
|
||||
let win_output = self
|
||||
.state
|
||||
.workspaces
|
||||
.workspaces
|
||||
.get(&wsid)?
|
||||
.output
|
||||
.clone()?;
|
||||
(win_output == output).then_some(ws)
|
||||
})
|
||||
.last()
|
||||
.context(anyhow!(
|
||||
"No inactive workspaces on output of workspace {} found",
|
||||
wsid
|
||||
))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
async fn move_windows(
|
||||
&mut self,
|
||||
candidates: &HashMap<u64, Workspace>,
|
||||
affected_windows: Vec<u64>,
|
||||
) {
|
||||
for wid in affected_windows {
|
||||
tracing::debug!("Changing affected window {}", wid);
|
||||
if let Some(win) = self.state.windows.windows.get(&wid) {
|
||||
let wsid = win.workspace_id.unwrap();
|
||||
match self.same_output(wsid, candidates) {
|
||||
Ok(status_same_output) => {
|
||||
if let Err(e) = tell(
|
||||
&mut self.socket,
|
||||
Request::Action(Action::MoveWindowToWorkspace {
|
||||
window_id: Some(wid),
|
||||
reference: WorkspaceReferenceArg::Id(status_same_output.id),
|
||||
focus: false,
|
||||
}),
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to move window {} to workspace {}: {}",
|
||||
wid,
|
||||
status_same_output.id,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to get workspace on same output as {}: {}", wsid, e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::warn!("Failed to get wid {} from niri state", wid);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_action(&mut self, action: TagAction) -> Result<()> {
|
||||
use TagAction::*;
|
||||
let same_output =
|
||||
|wsid: u64, candidates: &HashMap<&u64, &Workspace>| -> Result<Workspace> {
|
||||
candidates
|
||||
.values()
|
||||
.filter_map(|ws| {
|
||||
let output = ws.output.clone()?;
|
||||
let win_output = self
|
||||
.state
|
||||
.workspaces
|
||||
.workspaces
|
||||
.get(&wsid)?
|
||||
.output
|
||||
.clone()?;
|
||||
(win_output == output).then_some(ws)
|
||||
})
|
||||
.last()
|
||||
.context(anyhow!(
|
||||
"No inactive workspaces on output of workspace {} found",
|
||||
wsid
|
||||
))
|
||||
.copied()
|
||||
.cloned()
|
||||
};
|
||||
let (active, inactive): (HashMap<_, _>, HashMap<_, _>) = self
|
||||
.state
|
||||
.workspaces
|
||||
.workspaces
|
||||
.clone()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.partition(|(_, ws)| ws.is_active);
|
||||
match action {
|
||||
Window(wid) => {
|
||||
ChangeWindow(wid) => {
|
||||
let current_tag = *self.windows.entry(wid).or_insert(0);
|
||||
let tag_visible = *self.tags.entry(current_tag).or_insert(true);
|
||||
let win = self
|
||||
@ -129,7 +88,7 @@ impl NiriTag {
|
||||
let win_visible = active.contains_key(&wsid);
|
||||
match (win_visible, tag_visible) {
|
||||
(true, false) => {
|
||||
let inactive_same_output = self.same_output(wsid, &inactive)?;
|
||||
let inactive_same_output = same_output(wsid, &inactive)?;
|
||||
tell(
|
||||
&mut self.socket,
|
||||
Request::Action(Action::MoveWindowToWorkspace {
|
||||
@ -141,7 +100,7 @@ impl NiriTag {
|
||||
.await
|
||||
}
|
||||
(false, true) => {
|
||||
let active_same_output = self.same_output(wsid, &active)?;
|
||||
let active_same_output = same_output(wsid, &active)?;
|
||||
tell(
|
||||
&mut self.socket,
|
||||
Request::Action(Action::MoveWindowToWorkspace {
|
||||
@ -155,7 +114,7 @@ impl NiriTag {
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
Tag(tag) => {
|
||||
ChangeTag(tag) => {
|
||||
tracing::debug!("Changing tag {}", tag);
|
||||
let tag_visible = *self.tags.entry(tag).or_insert(true);
|
||||
tracing::debug!("Windows: {:?}", self.windows);
|
||||
@ -172,11 +131,41 @@ impl NiriTag {
|
||||
affected_windows
|
||||
);
|
||||
let focus = affected_windows.last().cloned();
|
||||
self.move_windows(
|
||||
if tag_visible { &active } else { &inactive },
|
||||
affected_windows,
|
||||
)
|
||||
.await;
|
||||
for wid in affected_windows {
|
||||
tracing::debug!("Changing affected window {}", wid);
|
||||
if let Some(win) = self.state.windows.windows.get(&wid) {
|
||||
let wsid = win.workspace_id.unwrap();
|
||||
match same_output(wsid, if tag_visible { &active } else { &inactive }) {
|
||||
Ok(status_same_output) => {
|
||||
if let Err(e) = tell(
|
||||
&mut self.socket,
|
||||
Request::Action(Action::MoveWindowToWorkspace {
|
||||
window_id: Some(wid),
|
||||
reference: WorkspaceReferenceArg::Id(status_same_output.id),
|
||||
focus: false,
|
||||
}),
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to move window {} to workspace {}: {}",
|
||||
wid,
|
||||
status_same_output.id,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => tracing::error!(
|
||||
"Failed to get workspace on same output as {}: {}",
|
||||
wsid,
|
||||
e
|
||||
),
|
||||
}
|
||||
} else {
|
||||
tracing::warn!("Failed to get wid {} from niri state", wid);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(focus) = focus {
|
||||
if tag_visible {
|
||||
tell(
|
||||
@ -188,24 +177,6 @@ impl NiriTag {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
TagExclusive(t) => {
|
||||
tracing::debug!("Changing all tags");
|
||||
let (active_wid, inactive_wid): (HashMap<u64, u8>, HashMap<u64, u8>) =
|
||||
self.windows.iter().partition(|(_, it)| **it == t);
|
||||
let focus = active_wid.keys().last();
|
||||
self.move_windows(&inactive, inactive_wid.keys().cloned().collect())
|
||||
.await;
|
||||
self.move_windows(&active, active_wid.keys().cloned().collect())
|
||||
.await;
|
||||
if let Some(f) = focus {
|
||||
tell(
|
||||
&mut self.socket,
|
||||
Request::Action(Action::FocusWindow { id: *f }),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,7 +266,7 @@ impl NiriTag {
|
||||
tracing::debug!("adding tag {} to {}", t, wid);
|
||||
let tx = self.ev_tx.clone();
|
||||
add_tag(tx, &self.windows, t).await;
|
||||
Window(wid)
|
||||
ChangeWindow(wid)
|
||||
}
|
||||
TagCmd::RemoveTagFromWin(_) => {
|
||||
let win = self.get_focused_window().await?;
|
||||
@ -304,7 +275,7 @@ impl NiriTag {
|
||||
tracing::debug!("resetting tag on {}", wid);
|
||||
let tx = self.ev_tx.clone();
|
||||
rm_tag(tx, &self.windows, wid, old_tag).await;
|
||||
Window(wid)
|
||||
ChangeWindow(wid)
|
||||
}
|
||||
TagCmd::ToggleTagOnWin(t) => {
|
||||
let win = self.get_focused_window().await?;
|
||||
@ -320,18 +291,18 @@ impl NiriTag {
|
||||
}
|
||||
tracing::debug!("toggling {} to tag {}", wid, toggle);
|
||||
self.windows.insert(wid, toggle);
|
||||
Window(wid)
|
||||
ChangeWindow(wid)
|
||||
}
|
||||
|
||||
TagCmd::EnableTag(t) => {
|
||||
self.tags.insert(t, true);
|
||||
send_event(self.ev_tx.clone(), TagEvent::TagEnabled(t)).await;
|
||||
Tag(t)
|
||||
ChangeTag(t)
|
||||
}
|
||||
TagCmd::DisableTag(t) => {
|
||||
self.tags.insert(t, false);
|
||||
send_event(self.ev_tx.clone(), TagEvent::TagDisabled(t)).await;
|
||||
Tag(t)
|
||||
ChangeTag(t)
|
||||
}
|
||||
TagCmd::ToggleTag(t) => {
|
||||
let new_state = !*self.tags.entry(t).or_insert(false);
|
||||
@ -342,13 +313,7 @@ impl NiriTag {
|
||||
}
|
||||
tracing::debug!("toggling tag {} to {}", t, new_state);
|
||||
self.tags.insert(t, new_state);
|
||||
Tag(t)
|
||||
}
|
||||
TagCmd::ExclusiveTag(t) => {
|
||||
self.tags.entry(t).insert_entry(true);
|
||||
self.tags.iter_mut().for_each(|(it, en)| *en = *it == t);
|
||||
send_event(self.ev_tx.clone(), TagEvent::TagExclusive(t)).await;
|
||||
TagExclusive(t)
|
||||
ChangeTag(t)
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -410,7 +375,7 @@ impl NiriTag {
|
||||
WindowsChanged { windows } => {
|
||||
for w in windows {
|
||||
self.windows.entry(w.id).or_insert(0);
|
||||
let action = self.do_action(TagAction::Window(w.id)).await;
|
||||
let action = self.do_action(TagAction::ChangeWindow(w.id)).await;
|
||||
if let Err(e) = action {
|
||||
tracing::warn!("Failed to ChangeWindow on {}: {}", w.id, e);
|
||||
}
|
||||
|
@ -10,7 +10,8 @@ pub enum TagCmd {
|
||||
DisableTag(u8),
|
||||
ToggleTagOnWin(u8),
|
||||
ToggleTag(u8),
|
||||
ExclusiveTag(u8),
|
||||
// TODO
|
||||
// ExclusiveTag(u8),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@ -20,7 +21,6 @@ pub enum TagEvent {
|
||||
TagUrgent(u8),
|
||||
TagEnabled(u8),
|
||||
TagDisabled(u8),
|
||||
TagExclusive(u8),
|
||||
TagFullState(HashMap<u8, TagState>),
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user