0.6.0 #1
4 changed files with 87 additions and 27 deletions
|
@ -20,6 +20,7 @@ All notable changes to eww will be listed here, starting at changes since versio
|
||||||
- Fix wayland monitor names support (By: dragonnn)
|
- Fix wayland monitor names support (By: dragonnn)
|
||||||
- Load systray items that are registered without a path (By: Kage-Yami)
|
- Load systray items that are registered without a path (By: Kage-Yami)
|
||||||
- `get_locale` now follows POSIX standard for locale selection (By: mirhahn, w-lfchen)
|
- `get_locale` now follows POSIX standard for locale selection (By: mirhahn, w-lfchen)
|
||||||
|
- Improve multi-monitor handling under wayland (By: bkueng)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
- Add warning and docs for incompatible `:anchor` and `:exclusive` options
|
- Add warning and docs for incompatible `:anchor` and `:exclusive` options
|
||||||
|
|
|
@ -68,6 +68,7 @@ pub enum DaemonCommand {
|
||||||
},
|
},
|
||||||
CloseWindows {
|
CloseWindows {
|
||||||
windows: Vec<String>,
|
windows: Vec<String>,
|
||||||
|
auto_reopen: bool,
|
||||||
sender: DaemonResponseSender,
|
sender: DaemonResponseSender,
|
||||||
},
|
},
|
||||||
KillServer,
|
KillServer,
|
||||||
|
@ -147,16 +148,34 @@ impl<B: DisplayBackend> std::fmt::Debug for App<B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wait until the .model() is available for all monitors (or there is a timeout)
|
||||||
|
async fn wait_for_monitor_model() {
|
||||||
|
let display = gdk::Display::default().expect("could not get default display");
|
||||||
|
let start = std::time::Instant::now();
|
||||||
|
loop {
|
||||||
|
let all_monitors_set =
|
||||||
|
(0..display.n_monitors()).all(|i| display.monitor(i).and_then(|monitor| monitor.model()).is_some());
|
||||||
|
if all_monitors_set {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||||
|
if std::time::Instant::now() - start > Duration::from_millis(500) {
|
||||||
|
log::warn!("Timed out waiting for monitor model to be set");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<B: DisplayBackend> App<B> {
|
impl<B: DisplayBackend> App<B> {
|
||||||
/// Handle a [`DaemonCommand`] event, logging any errors that occur.
|
/// Handle a [`DaemonCommand`] event, logging any errors that occur.
|
||||||
pub fn handle_command(&mut self, event: DaemonCommand) {
|
pub async fn handle_command(&mut self, event: DaemonCommand) {
|
||||||
if let Err(err) = self.try_handle_command(event) {
|
if let Err(err) = self.try_handle_command(event).await {
|
||||||
error_handling_ctx::print_error(err);
|
error_handling_ctx::print_error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to handle a [`DaemonCommand`] event.
|
/// Try to handle a [`DaemonCommand`] event.
|
||||||
fn try_handle_command(&mut self, event: DaemonCommand) -> Result<()> {
|
async fn try_handle_command(&mut self, event: DaemonCommand) -> Result<()> {
|
||||||
log::debug!("Handling event: {:?}", &event);
|
log::debug!("Handling event: {:?}", &event);
|
||||||
match event {
|
match event {
|
||||||
DaemonCommand::NoOp => {}
|
DaemonCommand::NoOp => {}
|
||||||
|
@ -174,6 +193,10 @@ impl<B: DisplayBackend> App<B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DaemonCommand::ReloadConfigAndCss(sender) => {
|
DaemonCommand::ReloadConfigAndCss(sender) => {
|
||||||
|
// Wait for all monitor models to be set. When a new monitor gets added, this
|
||||||
|
// might not immediately be the case. And if we were to wait inside the
|
||||||
|
// connect_monitor_added callback, model() never gets set. So instead we wait here.
|
||||||
|
wait_for_monitor_model().await;
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
let config_result = config::read_from_eww_paths(&self.paths);
|
let config_result = config::read_from_eww_paths(&self.paths);
|
||||||
|
@ -200,7 +223,7 @@ impl<B: DisplayBackend> App<B> {
|
||||||
DaemonCommand::CloseAll => {
|
DaemonCommand::CloseAll => {
|
||||||
log::info!("Received close command, closing all windows");
|
log::info!("Received close command, closing all windows");
|
||||||
for window_name in self.open_windows.keys().cloned().collect::<Vec<String>>() {
|
for window_name in self.open_windows.keys().cloned().collect::<Vec<String>>() {
|
||||||
self.close_window(&window_name)?;
|
self.close_window(&window_name, false)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DaemonCommand::OpenMany { windows, args, should_toggle, sender } => {
|
DaemonCommand::OpenMany { windows, args, should_toggle, sender } => {
|
||||||
|
@ -209,7 +232,7 @@ impl<B: DisplayBackend> App<B> {
|
||||||
.map(|w| {
|
.map(|w| {
|
||||||
let (config_name, id) = w;
|
let (config_name, id) = w;
|
||||||
if should_toggle && self.open_windows.contains_key(id) {
|
if should_toggle && self.open_windows.contains_key(id) {
|
||||||
self.close_window(id)
|
self.close_window(id, false)
|
||||||
} else {
|
} else {
|
||||||
log::debug!("Config: {}, id: {}", config_name, id);
|
log::debug!("Config: {}, id: {}", config_name, id);
|
||||||
let window_args = args
|
let window_args = args
|
||||||
|
@ -240,7 +263,7 @@ impl<B: DisplayBackend> App<B> {
|
||||||
let is_open = self.open_windows.contains_key(&instance_id);
|
let is_open = self.open_windows.contains_key(&instance_id);
|
||||||
|
|
||||||
let result = if should_toggle && is_open {
|
let result = if should_toggle && is_open {
|
||||||
self.close_window(&instance_id)
|
self.close_window(&instance_id, false)
|
||||||
} else {
|
} else {
|
||||||
self.open_window(&WindowArguments {
|
self.open_window(&WindowArguments {
|
||||||
instance_id,
|
instance_id,
|
||||||
|
@ -256,9 +279,10 @@ impl<B: DisplayBackend> App<B> {
|
||||||
|
|
||||||
sender.respond_with_result(result)?;
|
sender.respond_with_result(result)?;
|
||||||
}
|
}
|
||||||
DaemonCommand::CloseWindows { windows, sender } => {
|
DaemonCommand::CloseWindows { windows, auto_reopen, sender } => {
|
||||||
let errors = windows.iter().map(|window| self.close_window(window)).filter_map(Result::err);
|
let errors = windows.iter().map(|window| self.close_window(window, auto_reopen)).filter_map(Result::err);
|
||||||
sender.respond_with_error_list(errors)?;
|
// Ignore sending errors, as the channel might already be closed
|
||||||
|
let _ = sender.respond_with_error_list(errors);
|
||||||
}
|
}
|
||||||
DaemonCommand::PrintState { all, sender } => {
|
DaemonCommand::PrintState { all, sender } => {
|
||||||
let scope_graph = self.scope_graph.borrow();
|
let scope_graph = self.scope_graph.borrow();
|
||||||
|
@ -360,7 +384,7 @@ impl<B: DisplayBackend> App<B> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close a window and do all the required cleanups in the scope_graph and script_var_handler
|
/// Close a window and do all the required cleanups in the scope_graph and script_var_handler
|
||||||
fn close_window(&mut self, instance_id: &str) -> Result<()> {
|
fn close_window(&mut self, instance_id: &str, auto_reopen: bool) -> Result<()> {
|
||||||
if let Some(old_abort_send) = self.window_close_timer_abort_senders.remove(instance_id) {
|
if let Some(old_abort_send) = self.window_close_timer_abort_senders.remove(instance_id) {
|
||||||
_ = old_abort_send.send(());
|
_ = old_abort_send.send(());
|
||||||
}
|
}
|
||||||
|
@ -380,7 +404,17 @@ impl<B: DisplayBackend> App<B> {
|
||||||
self.script_var_handler.stop_for_variable(unused_var.clone());
|
self.script_var_handler.stop_for_variable(unused_var.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if auto_reopen {
|
||||||
|
self.failed_windows.insert(instance_id.to_string());
|
||||||
|
// There might be an alternative monitor available already, so try to re-open it immediately.
|
||||||
|
// This can happen for example when a monitor gets disconnected and another connected,
|
||||||
|
// and the connection event happens before the disconnect.
|
||||||
|
if let Some(window_arguments) = self.instance_id_to_args.get(instance_id) {
|
||||||
|
let _ = self.open_window(&window_arguments.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
self.instance_id_to_args.remove(instance_id);
|
self.instance_id_to_args.remove(instance_id);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -392,7 +426,7 @@ impl<B: DisplayBackend> App<B> {
|
||||||
|
|
||||||
// if an instance of this is already running, close it
|
// if an instance of this is already running, close it
|
||||||
if self.open_windows.contains_key(instance_id) {
|
if self.open_windows.contains_key(instance_id) {
|
||||||
self.close_window(instance_id)?;
|
self.close_window(instance_id, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.instance_id_to_args.insert(instance_id.to_string(), window_args.clone());
|
self.instance_id_to_args.insert(instance_id.to_string(), window_args.clone());
|
||||||
|
@ -445,9 +479,17 @@ impl<B: DisplayBackend> App<B> {
|
||||||
move |_| {
|
move |_| {
|
||||||
// we don't care about the actual error response from the daemon as this is mostly just a fallback.
|
// we don't care about the actual error response from the daemon as this is mostly just a fallback.
|
||||||
// Generally, this should get disconnected before the gtk window gets destroyed.
|
// Generally, this should get disconnected before the gtk window gets destroyed.
|
||||||
// It serves as a fallback for when the window is closed manually.
|
// This callback is triggered in 2 cases:
|
||||||
|
// - When the monitor of this window gets disconnected
|
||||||
|
// - When the window is closed manually.
|
||||||
|
// We don't distinguish here and assume the window should be reopened once a monitor
|
||||||
|
// becomes available again
|
||||||
let (response_sender, _) = daemon_response::create_pair();
|
let (response_sender, _) = daemon_response::create_pair();
|
||||||
let command = DaemonCommand::CloseWindows { windows: vec![instance_id.clone()], sender: response_sender };
|
let command = DaemonCommand::CloseWindows {
|
||||||
|
windows: vec![instance_id.clone()],
|
||||||
|
auto_reopen: true,
|
||||||
|
sender: response_sender,
|
||||||
|
};
|
||||||
if let Err(err) = app_evt_sender.send(command) {
|
if let Err(err) = app_evt_sender.send(command) {
|
||||||
log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err);
|
log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err);
|
||||||
}
|
}
|
||||||
|
@ -466,7 +508,7 @@ impl<B: DisplayBackend> App<B> {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = glib::timeout_future(duration) => {
|
_ = glib::timeout_future(duration) => {
|
||||||
let (response_sender, mut response_recv) = daemon_response::create_pair();
|
let (response_sender, mut response_recv) = daemon_response::create_pair();
|
||||||
let command = DaemonCommand::CloseWindows { windows: vec![instance_id.clone()], sender: response_sender };
|
let command = DaemonCommand::CloseWindows { windows: vec![instance_id.clone()], auto_reopen: false, sender: response_sender };
|
||||||
if let Err(err) = app_evt_sender.send(command) {
|
if let Err(err) = app_evt_sender.send(command) {
|
||||||
log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err);
|
log::error!("Error sending close window command to daemon after gtk window destroy event: {}", err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,7 +292,7 @@ impl ActionWithServer {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ActionWithServer::CloseWindows { windows } => {
|
ActionWithServer::CloseWindows { windows } => {
|
||||||
return with_response_channel(|sender| app::DaemonCommand::CloseWindows { windows, sender });
|
return with_response_channel(|sender| app::DaemonCommand::CloseWindows { windows, auto_reopen: false, sender });
|
||||||
}
|
}
|
||||||
ActionWithServer::Reload => return with_response_channel(app::DaemonCommand::ReloadConfigAndCss),
|
ActionWithServer::Reload => return with_response_channel(app::DaemonCommand::ReloadConfigAndCss),
|
||||||
ActionWithServer::ListWindows => return with_response_channel(app::DaemonCommand::ListWindows),
|
ActionWithServer::ListWindows => return with_response_channel(app::DaemonCommand::ListWindows),
|
||||||
|
|
|
@ -105,13 +105,15 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connect_monitor_added(ui_send.clone());
|
||||||
|
|
||||||
// initialize all the handlers and tasks running asyncronously
|
// initialize all the handlers and tasks running asyncronously
|
||||||
let tokio_handle = init_async_part(app.paths.clone(), ui_send);
|
let tokio_handle = init_async_part(app.paths.clone(), ui_send);
|
||||||
|
|
||||||
gtk::glib::MainContext::default().spawn_local(async move {
|
gtk::glib::MainContext::default().spawn_local(async move {
|
||||||
// if an action was given to the daemon initially, execute it first.
|
// if an action was given to the daemon initially, execute it first.
|
||||||
if let Some(action) = action {
|
if let Some(action) = action {
|
||||||
app.handle_command(action);
|
app.handle_command(action).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -120,7 +122,7 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||||
app.scope_graph.borrow_mut().handle_scope_graph_event(scope_graph_evt);
|
app.scope_graph.borrow_mut().handle_scope_graph_event(scope_graph_evt);
|
||||||
},
|
},
|
||||||
Some(ui_event) = ui_recv.recv() => {
|
Some(ui_event) = ui_recv.recv() => {
|
||||||
app.handle_command(ui_event);
|
app.handle_command(ui_event).await;
|
||||||
}
|
}
|
||||||
else => break,
|
else => break,
|
||||||
}
|
}
|
||||||
|
@ -136,6 +138,29 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||||
Ok(ForkResult::Child)
|
Ok(ForkResult::Child)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn connect_monitor_added(ui_send: UnboundedSender<DaemonCommand>) {
|
||||||
|
let display = gtk::gdk::Display::default().expect("could not get default display");
|
||||||
|
display.connect_monitor_added({
|
||||||
|
move |_display: >k::gdk::Display, _monitor: >k::gdk::Monitor| {
|
||||||
|
log::info!("New monitor connected, reloading configuration");
|
||||||
|
let _ = reload_config_and_css(&ui_send);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload_config_and_css(ui_send: &UnboundedSender<DaemonCommand>) -> Result<()> {
|
||||||
|
let (daemon_resp_sender, mut daemon_resp_response) = daemon_response::create_pair();
|
||||||
|
ui_send.send(DaemonCommand::ReloadConfigAndCss(daemon_resp_sender))?;
|
||||||
|
tokio::spawn(async move {
|
||||||
|
match daemon_resp_response.recv().await {
|
||||||
|
Some(daemon_response::DaemonResponse::Success(_)) => log::info!("Reloaded config successfully"),
|
||||||
|
Some(daemon_response::DaemonResponse::Failure(e)) => eprintln!("{}", e),
|
||||||
|
None => log::error!("No response to reload configuration-reload request"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender<app::DaemonCommand>) -> tokio::runtime::Handle {
|
fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender<app::DaemonCommand>) -> tokio::runtime::Handle {
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.thread_name("main-async-runtime")
|
.thread_name("main-async-runtime")
|
||||||
|
@ -216,20 +241,12 @@ async fn run_filewatch<P: AsRef<Path>>(config_dir: P, evt_send: UnboundedSender<
|
||||||
debounce_done.store(true, Ordering::SeqCst);
|
debounce_done.store(true, Ordering::SeqCst);
|
||||||
});
|
});
|
||||||
|
|
||||||
let (daemon_resp_sender, mut daemon_resp_response) = daemon_response::create_pair();
|
|
||||||
// without this sleep, reading the config file sometimes gives an empty file.
|
// without this sleep, reading the config file sometimes gives an empty file.
|
||||||
// This is probably a result of editors not locking the file correctly,
|
// This is probably a result of editors not locking the file correctly,
|
||||||
// and eww being too fast, thus reading the file while it's empty.
|
// and eww being too fast, thus reading the file while it's empty.
|
||||||
// There should be some cleaner solution for this, but this will do for now.
|
// There should be some cleaner solution for this, but this will do for now.
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||||
evt_send.send(app::DaemonCommand::ReloadConfigAndCss(daemon_resp_sender))?;
|
reload_config_and_css(&evt_send)?;
|
||||||
tokio::spawn(async move {
|
|
||||||
match daemon_resp_response.recv().await {
|
|
||||||
Some(daemon_response::DaemonResponse::Success(_)) => log::info!("Reloaded config successfully"),
|
|
||||||
Some(daemon_response::DaemonResponse::Failure(e)) => eprintln!("{}", e),
|
|
||||||
None => log::error!("No response to reload configuration-reload request"),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
else => break
|
else => break
|
||||||
|
|
Loading…
Add table
Reference in a new issue