use std::io::ErrorKind;
use std::os::unix::net::UnixListener;
use std::os::unix::net::UnixStream;
use niri_ipc::Request;
use niri_ipc::Response;
use niri_ipc::WorkspaceReferenceArg;
use crate::cmds;
use crate::ipc;
use crate::state::STATE;
use crate::util;
pub fn run_daemon() {
std::thread::spawn(init_then_process_events);
serve_client_requests();
}
fn init_then_process_events() -> std::io::Result<()> {
match ipc::query_niri(Request::Windows) {
Ok(response) => match response {
Response::Windows(wins) => {
let mut state =
STATE.write().expect("Could not write() STATE.");
log::info!("Initializing state with {} windows.", wins.len());
for win in wins {
let msg = state.register_window(win.clone()).unwrap();
log::info!("{}", msg);
}
}
x => panic!("Received unexpected reply {x:?}"),
},
Err(err) => panic!("Could not query niri for windows: {err}"),
}
process_events()
}
fn process_events() -> std::io::Result<()> {
let mut socket = niri_ipc::socket::Socket::connect()?;
match socket.send(Request::EventStream) {
Ok(response) => match response {
Ok(Response::Handled) => {
let mut read_event = socket.read_events();
loop {
match read_event() {
Ok(event) => match handle_event(&event) {
Ok(msg) => {
log::info!(
"Handled event successfully: {event:?} => {msg}"
)
}
Err(e) => {
log::error!(
"Error during event-handling: {e:?}"
)
}
},
Err(err) => {
if err.kind() == ErrorKind::UnexpectedEof {
log::error!(
"Received EOF, niri has quit and so do I. Goodbye!"
);
std::process::exit(0)
}
log::error!("Could not read event: {err:?}")
}
}
}
}
Ok(other) => {
let msg = format!(
"Unexpected response for Request::EventStream: {other:?}"
);
log::error!("{msg}");
panic!("{msg}")
}
Err(e) => {
let msg = format!("Error when requesting EventStream: {e:?}");
log::error!("{msg}");
panic!("{msg}")
}
},
Err(e) => {
let msg = format!("Could not send Request::EventStream: {e:?}");
log::error!("{msg}");
panic!("{msg}")
}
}
}
fn handle_event(event: &niri_ipc::Event) -> Result<String, String> {
match event {
niri_ipc::Event::WorkspaceActivated { id, focused } if *focused => {
{
let mut state =
STATE.write().expect("Could not write() STATE.");
state.workspace_focused(*id);
}
let state = STATE.read().expect("Could not read() STATE.");
let mut str = String::new();
if state.is_bottom_workspace_focused() {
str += &cmds::scratchpad_move()?;
}
let mut i = 0;
for w in &state.follow_mode_win_ids {
cmds::move_window_to_workspace(
*w,
WorkspaceReferenceArg::Id(*id),
false,
)?;
i += 1;
}
if i > 0 {
str += &format!("Moved {i} follow-mode windows");
}
if str.is_empty() {
Ok("Nothing needed to be done.".to_owned())
} else {
Ok(str)
}
}
niri_ipc::Event::WindowOpenedOrChanged { window } => {
let mut state = STATE.write().expect("Could not write() STATE.");
if !window.is_floating {
state.scratchpad_win_ids.retain(|w| *w != window.id)
}
state.register_window(window.clone())
}
niri_ipc::Event::WindowClosed { id } => {
let mut state = STATE.write().expect("Could not write() STATE.");
state.remove_window(id)
}
niri_ipc::Event::WindowFocusChanged { id } => {
let mut state = STATE.write().expect("Could not write() STATE.");
state.window_focus_changed(*id)
}
niri_ipc::Event::WorkspacesChanged { workspaces } => {
let mut state = STATE.write().expect("Could not write() STATE.");
state.workspaces_changed(workspaces.clone())
}
_other => Ok("Nothing to do.".to_owned()),
}
}
fn serve_client_requests() {
let socket_path = util::get_nirius_socket_path();
match std::fs::exists(&socket_path) {
Ok(true) => match std::fs::remove_file(&socket_path) {
Ok(()) => log::debug!(
"Deleted stale socket {socket_path} from previous run."
),
Err(e) => {
panic!("Could not delete stale socket {socket_path}.\n{e:?}");
}
},
Err(err) => {
panic!("Error when trying to access {socket_path}.\n{err:?}")
}
_ => (),
};
log::debug!("niriusd starts listening on {socket_path}.");
match UnixListener::bind(socket_path) {
Ok(listener) => {
for stream in listener.incoming() {
match stream {
Ok(stream) => {
handle_client_request(stream);
}
Err(err) => {
log::error!("Error handling client request: {err}");
}
}
}
}
Err(err) => {
log::error!("Could not bind socket: {err}")
}
}
}
fn handle_client_request(stream: UnixStream) {
match serde_json::from_reader::<_, cmds::NiriusCmd>(&stream) {
Ok(cmd) => {
log::debug!("Received command: {cmd:?}");
if let Err(err) = stream.shutdown(std::net::Shutdown::Read) {
log::error!("Could not shutdown stream for read: {err}")
}
let result = cmds::exec_nirius_cmd(cmd);
log::debug!("Executed command, returning result {result:?}");
if let Err(err) = serde_json::to_writer(&stream, &result) {
log::error!("Couldn't send result back to client: {err}");
}
if let Err(err) = stream.shutdown(std::net::Shutdown::Write) {
log::error!("Could not shutdown stream for read: {err}");
}
}
Err(err) => {
log::error!("Could not read command from client: {err}");
}
}
}