use super::SignalsWindow;
use crate::{
systemd_gui::new_settings,
widget::{app_window::AppWindow, preferences::data::PREFERENCES},
};
use adw::subclass::window::AdwWindowImpl;
use base::enums::UnitDBusLevel;
use gio::{ListStore, glib::BoxedAnyObject};
use gtk::{
glib::{self},
prelude::*,
subclass::{
prelude::*,
widget::{
CompositeTemplateCallbacksClass, CompositeTemplateClass,
CompositeTemplateInitializingExt, WidgetImpl,
},
},
};
use std::cell::{Cell, OnceCell, Ref, RefCell};
use systemd::{SystemdSignalRow, init_signal_watcher};
use tracing::{debug, info, warn};
const SIGNAL_WINDOW_WIDTH: &str = "signal-window-width";
const SIGNAL_WINDOW_HEIGHT: &str = "signal-window-height";
#[derive(Default, gtk::CompositeTemplate)]
#[template(resource = "/io/github/plrigaux/sysd-manager/signals_window.ui")]
pub struct SignalsWindowImp {
#[template_child]
signals_column: TemplateChild<gtk::ColumnView>,
#[template_child]
panel_stack: TemplateChild<adw::ViewStack>,
#[template_child]
sort_list_model: TemplateChild<gtk::SortListModel>,
#[template_child]
time_column: TemplateChild<gtk::ColumnViewColumn>,
#[template_child]
type_column: TemplateChild<gtk::ColumnViewColumn>,
#[template_child]
bus_column: TemplateChild<gtk::ColumnViewColumn>,
#[template_child]
details_column: TemplateChild<gtk::ColumnViewColumn>,
signals: RefCell<Option<gio::ListStore>>,
app_window: OnceCell<AppWindow>,
receiving: Cell<bool>,
}
#[gtk::template_callbacks]
impl SignalsWindowImp {
pub(crate) fn set_app_window(&self, app_window: &AppWindow) {
self.app_window
.set(app_window.clone())
.expect("app_window set once");
}
fn setup_factory(&self) {
let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(move |_, list_item| {
let time_cell = gtk::Inscription::builder().build();
list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem")
.set_child(Some(&time_cell));
});
factory.connect_bind(move |_, list_item| {
let list_item = list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem");
let task_object = list_item
.item()
.and_downcast::<glib::BoxedAnyObject>()
.expect("The item has to be an `TaskObject`.");
let time_cell = list_item
.child()
.and_downcast::<gtk::Inscription>()
.expect("The child has to be a `SignalRow`.");
let signal_row: Ref<SystemdSignalRow> = task_object.borrow();
let timestamp_style = PREFERENCES.timestamp_style();
let formated_time = timestamp_style.usec_formated(signal_row.time_stamp);
time_cell.set_text(Some(&formated_time));
});
self.time_column.set_factory(Some(&factory));
let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(move |_, list_item| {
let time_cell = gtk::Inscription::builder().build();
list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem")
.set_child(Some(&time_cell));
});
factory.connect_bind(move |_, list_item| {
let list_item = list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem");
let task_object = list_item
.item()
.and_downcast::<glib::BoxedAnyObject>()
.expect("The item has to be an `TaskObject`.");
let time_cell = list_item
.child()
.and_downcast::<gtk::Inscription>()
.expect("The child has to be a `SignalRow`.");
let signal_row: Ref<SystemdSignalRow> = task_object.borrow();
time_cell.set_text(Some(signal_row.type_text()));
});
self.type_column.set_factory(Some(&factory));
let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(move |_, list_item| {
let time_cell = gtk::Inscription::builder().build();
list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem")
.set_child(Some(&time_cell));
});
factory.connect_bind(move |_, list_item| {
let list_item = list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem");
let task_object = list_item
.item()
.and_downcast::<glib::BoxedAnyObject>()
.expect("The item has to be an `TaskObject`.");
let time_cell = list_item
.child()
.and_downcast::<gtk::Inscription>()
.expect("The child has to be an `Inscription`.");
let signal_row: Ref<SystemdSignalRow> = task_object.borrow();
time_cell.set_text(Some(signal_row.bus_text()));
});
self.bus_column.set_factory(Some(&factory));
let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(move |_, list_item| {
let time_cell = gtk::Inscription::builder().build();
list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem")
.set_child(Some(&time_cell));
});
factory.connect_bind(move |_, list_item| {
let list_item = list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem");
let task_object = list_item
.item()
.and_downcast::<glib::BoxedAnyObject>()
.expect("The item has to be an `TaskObject`.");
let time_cell = list_item
.child()
.and_downcast::<gtk::Inscription>()
.expect("The child has to be a `SignalRow`.");
let signal_row: Ref<SystemdSignalRow> = task_object.borrow();
time_cell.set_text(Some(&signal_row.details()));
});
self.details_column.set_factory(Some(&factory));
}
fn display_signals(&self) {
self.panel_stack.set_visible_child_name("signals");
}
}
#[glib::object_subclass]
impl ObjectSubclass for SignalsWindowImp {
const NAME: &'static str = "SIGNALS_DIALOG";
type Type = SignalsWindow;
type ParentType = adw::Window;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
klass.bind_template_callbacks();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for SignalsWindowImp {
fn constructed(&self) {
self.parent_constructed();
let model = gio::ListStore::new::<glib::BoxedAnyObject>();
self.signals.replace(Some(model.clone()));
self.sort_list_model.set_model(Some(&model.clone()));
self.setup_factory();
let signal_dialog = self.obj().clone();
self.receiving.set(true);
glib::spawn_future_local(async move {
fn append(signal: SystemdSignalRow, model: &ListStore) {
debug!("Recieve {signal:?}");
let boxed = BoxedAnyObject::new(signal);
model.append(&boxed);
}
let mut systemd_signal_receiver = init_signal_watcher(UnitDBusLevel::Both).await;
if let Ok(signal) = systemd_signal_receiver
.recv()
.await
.inspect_err(|err| warn!("Watch Signal {err:?}"))
{
let signal_row = SystemdSignalRow::new(signal);
append(signal_row, &model);
signal_dialog.imp().display_signals();
}
while let Ok(signal) = systemd_signal_receiver
.recv()
.await
.inspect_err(|err| warn!("Watch Signal {err:?}"))
&& signal_dialog.imp().receiving.get()
{
let signal_row = SystemdSignalRow::new(signal);
info!("Watcher Browser {:?}", signal_row);
append(signal_row, &model);
}
info!("Window Watcher End receiving signals")
});
let settings = new_settings();
let width = settings.int(SIGNAL_WINDOW_WIDTH);
let height = settings.int(SIGNAL_WINDOW_HEIGHT);
self.obj().set_default_size(width, height);
}
}
impl WidgetImpl for SignalsWindowImp {}
impl WindowImpl for SignalsWindowImp {
fn close_request(&self) -> glib::Propagation {
debug!("Close window signals");
self.receiving.set(false);
self.app_window
.get()
.expect("Window not None")
.set_signal_window(None);
let (width, height) = self.obj().default_size();
let settings = new_settings();
let _ = settings.set_int(SIGNAL_WINDOW_WIDTH, width);
let _ = settings.set_int(SIGNAL_WINDOW_HEIGHT, height);
self.parent_close_request();
glib::Propagation::Proceed
}
}
impl AdwWindowImpl for SignalsWindowImp {}