You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
265 lines
8.4 KiB
265 lines
8.4 KiB
// Copyright (C) 2021 Tassilo Horn <tsdh@gnu.org> |
|
// |
|
// This program is free software: you can redistribute it and/or modify it |
|
// under the terms of the GNU General Public License as published by the Free |
|
// Software Foundation, either version 3 of the License, or (at your option) |
|
// any later version. |
|
// |
|
// This program is distributed in the hope that it will be useful, but WITHOUT |
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
|
// more details. |
|
// |
|
// You should have received a copy of the GNU General Public License along with |
|
// this program. If not, see <https://www.gnu.org/licenses/>. |
|
|
|
//! Functions and data structures of the swayrd demon. |
|
|
|
use crate::cmds; |
|
use crate::con; |
|
use crate::config; |
|
use crate::layout; |
|
use crate::util; |
|
use std::collections::HashMap; |
|
use std::io::Read; |
|
use std::os::unix::net::{UnixListener, UnixStream}; |
|
use std::sync::Arc; |
|
use std::sync::RwLock; |
|
use std::thread; |
|
use std::time::{SystemTime, UNIX_EPOCH}; |
|
use swayipc as s; |
|
|
|
pub fn run_demon() { |
|
let extra_props: Arc<RwLock<HashMap<i64, con::ExtraProps>>> = |
|
Arc::new(RwLock::new(HashMap::new())); |
|
let extra_props_for_ev_handler = extra_props.clone(); |
|
|
|
thread::spawn(move || { |
|
monitor_sway_events(extra_props_for_ev_handler); |
|
}); |
|
|
|
serve_client_requests(extra_props); |
|
} |
|
|
|
fn connect_and_subscribe() -> s::Fallible<s::EventStream> { |
|
s::Connection::new()? |
|
.subscribe(&[s::EventType::Window, s::EventType::Workspace]) |
|
} |
|
|
|
pub fn monitor_sway_events( |
|
extra_props: Arc<RwLock<HashMap<i64, con::ExtraProps>>>, |
|
) { |
|
let config = config::load_config(); |
|
let layout = config.layout.unwrap_or_else(config::Layout::default); |
|
|
|
'reset: loop { |
|
println!("Connecting to sway for subscribing to events..."); |
|
match connect_and_subscribe() { |
|
Err(err) => { |
|
eprintln!("Could not connect and subscribe: {}", err); |
|
std::thread::sleep(std::time::Duration::from_secs(3)); |
|
break 'reset; |
|
} |
|
Ok(iter) => { |
|
for ev_result in iter { |
|
let handled; |
|
match ev_result { |
|
Ok(ev) => match ev { |
|
s::Event::Window(win_ev) => { |
|
let extra_props_clone = extra_props.clone(); |
|
handled = handle_window_event( |
|
win_ev, |
|
extra_props_clone, |
|
&layout, |
|
); |
|
} |
|
s::Event::Workspace(ws_ev) => { |
|
let extra_props_clone = extra_props.clone(); |
|
handled = handle_workspace_event( |
|
ws_ev, |
|
extra_props_clone, |
|
); |
|
} |
|
_ => handled = false, |
|
}, |
|
Err(e) => { |
|
eprintln!("Error while receiving events: {}", e); |
|
std::thread::sleep(std::time::Duration::from_secs( |
|
3, |
|
)); |
|
eprintln!("Resetting!"); |
|
break 'reset; |
|
} |
|
} |
|
if handled { |
|
println!( |
|
"New extra_props state:\n{:#?}", |
|
*extra_props.read().unwrap() |
|
); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
fn maybe_auto_tile(layout: &config::Layout) { |
|
if layout.auto_tile == Some(true) { |
|
println!("\nauto_tile: start"); |
|
layout::auto_tile(layout); |
|
println!("auto_tile: end\n"); |
|
} |
|
} |
|
|
|
fn handle_window_event( |
|
ev: Box<s::WindowEvent>, |
|
extra_props: Arc<RwLock<HashMap<i64, con::ExtraProps>>>, |
|
layout: &config::Layout, |
|
) -> bool { |
|
let s::WindowEvent { |
|
change, container, .. |
|
} = *ev; |
|
match change { |
|
s::WindowChange::Focus => { |
|
maybe_auto_tile(layout); |
|
update_last_focus_time(container.id, extra_props); |
|
println!("Handled window event type {:?}", change); |
|
true |
|
} |
|
s::WindowChange::New => { |
|
maybe_auto_tile(layout); |
|
update_last_focus_time(container.id, extra_props); |
|
println!("Handled window event type {:?}", change); |
|
true |
|
} |
|
s::WindowChange::Close => { |
|
remove_extra_props(container.id, extra_props); |
|
maybe_auto_tile(layout); |
|
println!("Handled window event type {:?}", change); |
|
true |
|
} |
|
s::WindowChange::Move | s::WindowChange::Floating => { |
|
maybe_auto_tile(layout); |
|
println!("Handled window event type {:?}", change); |
|
false // We don't affect the extra_props state here. |
|
} |
|
_ => { |
|
println!("Unhandled window event type {:?}", change); |
|
false |
|
} |
|
} |
|
} |
|
|
|
fn handle_workspace_event( |
|
ev: Box<s::WorkspaceEvent>, |
|
extra_props: Arc<RwLock<HashMap<i64, con::ExtraProps>>>, |
|
) -> bool { |
|
let s::WorkspaceEvent { |
|
change, |
|
current, |
|
old: _, |
|
.. |
|
} = *ev; |
|
match change { |
|
s::WorkspaceChange::Init | s::WorkspaceChange::Focus => { |
|
update_last_focus_time( |
|
current |
|
.expect("No current in Init or Focus workspace event") |
|
.id, |
|
extra_props, |
|
); |
|
println!("Handled workspace event type {:?}", change); |
|
true |
|
} |
|
s::WorkspaceChange::Empty => { |
|
remove_extra_props( |
|
current.expect("No current in Empty workspace event").id, |
|
extra_props, |
|
); |
|
println!("Handled workspace event type {:?}", change); |
|
true |
|
} |
|
_ => false, |
|
} |
|
} |
|
|
|
fn update_last_focus_time( |
|
id: i64, |
|
extra_props: Arc<RwLock<HashMap<i64, con::ExtraProps>>>, |
|
) { |
|
let mut write_lock = extra_props.write().unwrap(); |
|
if let Some(wp) = write_lock.get_mut(&id) { |
|
wp.last_focus_time = get_epoch_time_as_millis(); |
|
} else { |
|
write_lock.insert( |
|
id, |
|
con::ExtraProps { |
|
last_focus_time: get_epoch_time_as_millis(), |
|
}, |
|
); |
|
} |
|
} |
|
|
|
fn remove_extra_props( |
|
id: i64, |
|
extra_props: Arc<RwLock<HashMap<i64, con::ExtraProps>>>, |
|
) { |
|
extra_props.write().unwrap().remove(&id); |
|
} |
|
|
|
fn get_epoch_time_as_millis() -> u128 { |
|
SystemTime::now() |
|
.duration_since(UNIX_EPOCH) |
|
.expect("Couldn't get epoch time!") |
|
.as_millis() |
|
} |
|
|
|
pub fn serve_client_requests( |
|
extra_props: Arc<RwLock<HashMap<i64, con::ExtraProps>>>, |
|
) { |
|
match std::fs::remove_file(util::get_swayr_socket_path()) { |
|
Ok(()) => println!("Deleted stale socket from previous run."), |
|
Err(e) => eprintln!("Could not delete socket:\n{:?}", e), |
|
} |
|
|
|
match UnixListener::bind(util::get_swayr_socket_path()) { |
|
Ok(listener) => { |
|
for stream in listener.incoming() { |
|
match stream { |
|
Ok(stream) => { |
|
handle_client_request(stream, extra_props.clone()); |
|
} |
|
Err(err) => { |
|
eprintln!("Error handling client request: {}", err); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
Err(err) => { |
|
eprintln!("Could not bind socket: {}", err) |
|
} |
|
} |
|
} |
|
|
|
fn handle_client_request( |
|
mut stream: UnixStream, |
|
extra_props: Arc<RwLock<HashMap<i64, con::ExtraProps>>>, |
|
) { |
|
let mut cmd_str = String::new(); |
|
if stream.read_to_string(&mut cmd_str).is_ok() { |
|
if let Ok(cmd) = serde_json::from_str::<cmds::SwayrCommand>(&cmd_str) { |
|
cmds::exec_swayr_cmd(cmds::ExecSwayrCmdArgs { |
|
cmd: &cmd, |
|
extra_props, |
|
}); |
|
} else { |
|
eprintln!( |
|
"Could not serialize following string to SwayrCommand.\n{}", |
|
cmd_str |
|
); |
|
} |
|
} else { |
|
eprintln!("Could not read command from client."); |
|
} |
|
}
|
|
|