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 std::{cell::RefCell, rc::Rc};

use crate::systemd::{self};
use crate::widget::app_window::AppWindow;
use base::enums::UnitDBusLevel;
use gtk::{
    gdk, gio,
    glib::{self, Value},
    pango,
    prelude::*,
};
use tracing::{info, warn};

use super::writer::{PROP_UNDERLINE, TAG_DATA_LINK};

pub struct LinkActivator {
    app: Option<AppWindow>,
}

impl LinkActivator {
    pub fn new(app: Option<AppWindow>) -> Self {
        LinkActivator { app }
    }

    pub fn run(&self, link: &str) {
        if link.starts_with("file://") {
            let uri = gio::File::for_uri(link);
            let launcher = gtk::FileLauncher::new(Some(&uri));
            let link = link.to_owned();
            launcher.launch(
                None::<&gtk::Window>,
                None::<&gio::Cancellable>,
                move |result| {
                    if let Err(error) = result {
                        warn!("Finished launch {link} Error {error:?}")
                    }
                },
            );
        } else if link.starts_with("http://")
            || link.starts_with("https://")
            || link.starts_with("man:")
        {
            let launcher = gtk::UriLauncher::new(link);
            let link = link.to_owned();
            launcher.launch(
                None::<&gtk::Window>,
                None::<&gio::Cancellable>,
                move |result| {
                    if let Err(error) = result {
                        warn!("Finished launch {link} Error {error:?}")
                    }
                },
            );
        } else if let Some(unit_name) = link.strip_prefix("unit://") {
            let (unit_name, level) = match unit_name.split_once("?") {
                Some((prefix, suffix)) => (prefix, UnitDBusLevel::from_short(suffix)),
                None => (unit_name, UnitDBusLevel::System),
            };

            info!("open unit {:?} at level {}", unit_name, level.short());

            let unit = systemd::fetch_unit(level, unit_name)
                .inspect_err(|e| warn!("Cli unit: {e:?}"))
                .ok();

            if let Some(app_window) = &self.app {
                app_window.set_unit(unit.as_ref());
            } else {
                warn!("app_window missing");
            }
        } else {
            warn!("Not handle link {link:?}")
        }
    }
}

impl Clone for LinkActivator {
    fn clone(&self) -> LinkActivator {
        LinkActivator {
            app: self.app.clone(),
        }
    }
}

pub fn build_textview_link_platform(
    text_view_original: &gtk::TextView,
    hovering_over_link_tag: Rc<RefCell<Option<gtk::TextTag>>>,
    link_activator: LinkActivator,
) {
    text_view_original.set_has_tooltip(true);

    text_view_original.connect_query_tooltip(|text_view, mut x, mut y, keyboard_mode, tool_tip| {
        let s = format!("TT x {x} y {y} b {keyboard_mode}");
        tool_tip.set_text(Some(&s));

        //Adjust to the scrolling
        if let Some(vadj) = text_view.vadjustment() {
            y += vadj.value() as i32;
        }

        //Adjust to the scrolling
        if let Some(hadj) = text_view.hadjustment() {
            x += hadj.value() as i32;
        }

        let Some(iter) = text_view.iter_at_location(x, y) else {
            return false;
        };

        let Some(link) = retreive_tag_link_value(iter) else {
            return false;
        };

        tool_tip.set_text(Some(&link));

        true
    });

    {
        let text_view = text_view_original.clone();
        let event_controller_key = gtk::EventControllerKey::new();
        let link_activator = link_activator.clone();
        event_controller_key.connect_key_pressed(
            move |_event_controller_key, keyval: gdk::Key, _keycode, _modifiers| {
                match keyval {
                    gdk::Key::Return | gdk::Key::KP_Enter => {
                        let buffer = text_view.buffer();
                        let mark = buffer.get_insert();
                        let iter = buffer.iter_at_mark(&mark);

                        follow_if_link(iter, link_activator.clone());
                    }
                    _ => {}
                }
                glib::Propagation::Proceed
            },
        );
        text_view_original.add_controller(event_controller_key);
    }

    {
        let event_controller_motion = gtk::EventControllerMotion::new();
        let text_view = text_view_original.clone();
        let hovering_over_link_tag = hovering_over_link_tag.clone();

        event_controller_motion.connect_motion(move |_motion_ctl, x, y| {
            let (tx, ty) =
                text_view.window_to_buffer_coords(gtk::TextWindowType::Widget, x as i32, y as i32);

            set_cursor_if_appropriate(hovering_over_link_tag.clone(), &text_view, tx, ty);
        });
        text_view_original.add_controller(event_controller_motion);
    }

    {
        let gesture_click = gtk::GestureClick::new();
        let text_view = text_view_original.clone();
        let link_activator = link_activator.clone();
        gesture_click.connect_released(move |_gesture_click, _n_press, mut x, mut y| {
            let buf = text_view.buffer();

            //Adjust to the scrolling
            if let Some(vadj) = text_view.vadjustment() {
                y += vadj.value();
            }

            //Adjust to the scrolling
            if let Some(hadj) = text_view.hadjustment() {
                x += hadj.value();
            }

            // we shouldn't follow a link if the user has selected something
            if let Some((start, end)) = buf.selection_bounds()
                && start.offset() != end.offset()
            {
                return;
            }

            let Some(iter) = text_view.iter_at_location(x as i32, y as i32) else {
                return;
            };

            follow_if_link(iter, link_activator.clone());
        });

        text_view_original.add_controller(gesture_click);
    }
}

fn set_cursor_if_appropriate(
    hovering_over_link_tag: Rc<RefCell<Option<gtk::TextTag>>>,
    text_view: &gtk::TextView,
    x: i32,
    y: i32,
) {
    let mut hovering_tag: Option<gtk::TextTag> = None;

    if let Some(iter) = text_view.iter_at_location(x, y) {
        let tags = iter.tags();
        for tag in tags.iter() {
            let val = unsafe {
                let val: Option<std::ptr::NonNull<Value>> = tag.qdata(*TAG_DATA_LINK);
                val
            };

            if val.is_some() {
                hovering_tag = Some(tag.clone());
                break;
            }
        }
    }

    let (change, previous_not_null) = {
        let previous_tag = hovering_over_link_tag.borrow();

        //It works empiricaly
        (!previous_tag.eq(&hovering_tag), previous_tag.is_some())
    };

    if change {
        if let Some(ref hovering_tag) = hovering_tag {
            text_view.set_cursor_from_name(Some("pointer"));

            hovering_tag.set_property(PROP_UNDERLINE, pango::Underline::DoubleLine.to_value());

            reset_hyper_tag(&hovering_over_link_tag, previous_not_null);
        } else {
            text_view.set_cursor_from_name(Some("text"));

            reset_hyper_tag(&hovering_over_link_tag, previous_not_null);
        }

        hovering_over_link_tag.replace(hovering_tag);
    }
}

fn reset_hyper_tag(
    hovering_over_link_tag: &Rc<RefCell<Option<gtk::TextTag>>>,
    previous_not_null: bool,
) {
    if previous_not_null {
        let previous_tag = hovering_over_link_tag.borrow();
        previous_tag
            .as_ref()
            .unwrap()
            .set_property(PROP_UNDERLINE, pango::Underline::SingleLine.to_value())
    }
}

fn follow_if_link(text_iter: gtk::TextIter, link_activator: LinkActivator) {
    let link = retreive_tag_link_value(text_iter);

    if let Some(link) = link {
        link_activator.run(&link);
    }
}

fn retreive_tag_link_value(text_iter: gtk::TextIter) -> Option<String> {
    let tags = text_iter.tags();

    let mut link_value = None;
    for tag in tags.iter() {
        link_value = unsafe {
            let val: Option<std::ptr::NonNull<Value>> = tag.qdata(*TAG_DATA_LINK);
            val.map(|link_value_nonull| link_value_nonull.as_ref())
        };

        if link_value.is_some() {
            break;
        }
    }

    if let Some(link_value) = link_value {
        match link_value.get::<String>() {
            Ok(link) => return Some(link),
            Err(e) => warn!("Link value Error {e:?}"),
        }
    }
    None
}