sysd-manager 2.19.4

Application to empower user to manage their <b>systemd units</b> via Graphical User Interface. Not only are you able to make changes to the enablement and running status of each of the units, but you will also be able to view and modify their unit files and check the journal logs.
use super::SignalsWindow;
use crate::{
    systemd_gui::new_settings,
    widget::{app_window::AppWindow, close_window_shortcut, 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();

        // Create an empty `TaskRow` during setup
        factory.connect_setup(move |_, list_item| {
            // Create `TaskRow`
            let time_cell = gtk::Inscription::builder().build();

            list_item
                .downcast_ref::<gtk::ListItem>()
                .expect("Needs to be ListItem")
                .set_child(Some(&time_cell));
        });

        // Tell factory how to bind `TaskRow` to a `TaskObject`
        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| {
            // Create `TaskRow`
            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| {
            // Create `TaskRow`
            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");
    }
}
// The central trait for subclassing a GObject
#[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) {
        // The layout manager determines how child widgets are laid out.
        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();
        close_window_shortcut(self.obj().as_ref());
        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;
            //To handle the first
            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();

        // Allow to invoke other event handlers
        glib::Propagation::Proceed
    }
}
impl AdwWindowImpl for SignalsWindowImp {}