use std::sync::LazyLock;
use base::enums::UnitDBusLevel;
use gtk::{
glib::{self, Binding, Quark},
prelude::*,
};
use systemd::{
data::get_custom_property_typed_raw,
enums::UnitType,
errors::SystemdErrors,
socket_unit::SocketUnitInfo,
time_handling::{self, MSEC_PER_SEC},
timestamp_is_set,
};
use tracing::{error, warn};
use crate::{
consts::*,
widget::{
preferences::data::PREFERENCES, unit_list::column::SysdColumn,
unit_properties_selector::data_selection::UnitPropertySelection,
},
};
use crate::{
systemd::{
data::UnitInfo,
enums::{ActiveState, LoadState, Preset, UnitFileStatus},
},
widget::unit_list::UnitListPanel,
};
static BIND_INFO: LazyLock<Quark> = LazyLock::new(|| Quark::from_str("I"));
static BIND_CSS: LazyLock<Quark> = LazyLock::new(|| Quark::from_str("C"));
static BIND_CSS2: LazyLock<Quark> = LazyLock::new(|| Quark::from_str("C2"));
const CSS_CLASSES: &str = "css-classes";
const TEXT: &str = "text";
pub const PROPERTY_NAME: &str = "socket-listen-idx";
macro_rules! downcast_list_item {
($list_item_object:expr) => {{
$list_item_object
.downcast_ref::<gtk::ListItem>()
.expect("item.downcast_ref::<gtk::ListItem>()")
}};
}
fn factory_setup(_factory: >k::SignalListItemFactory, object: &glib::Object) {
let list_item = downcast_list_item!(object);
let inscription = gtk::Inscription::builder()
.wrap_mode(gtk::pango::WrapMode::None)
.build();
list_item.set_child(Some(&inscription));
}
macro_rules! factory_bind_pre {
($list_item_object:expr) => {{
let list_item = downcast_list_item!($list_item_object);
let inscription = list_item
.child()
.and_downcast::<gtk::Inscription>()
.expect("item.downcast_ref::<gtk::Inscription>()");
let unit_binding = list_item
.item()
.and_downcast::<UnitInfo>()
.expect("item.downcast_ref::<gtk::UnitBinding>()");
(inscription, unit_binding)
}};
}
macro_rules! factory_bind {
($item_obj:expr, $func:ident) => {{
let (inscription, unit) = factory_bind_pre!($item_obj);
let text = $func(&unit);
inscription.set_text(Some(&text));
(inscription, unit)
}};
}
macro_rules! factory_bind_enum {
($item_obj:expr, $func:ident) => {{
let (inscription, unit) = factory_bind_pre!($item_obj);
let text = unit.$func().as_str();
inscription.set_text(Some(text));
(inscription, unit)
}};
}
const ACTIVE_STATE: &str = "active_state";
const CSS_GREY: &str = "grey";
macro_rules! display_inactive {
($widget:expr, $unit:expr) => {
let state = $unit.active_state();
if state.is_inactive() {
$widget.set_css_classes(&[CSS_GREY]);
} else {
$widget.set_css_classes(&[]);
}
};
}
macro_rules! factory_connect_unbind {
($factory:expr, $($bind_id:expr), *) => {
$factory.connect_unbind(|_factory, object| {
let list_item = downcast_list_item!(object);
let Some(child) = list_item.child() else {
warn!("No child");
return;
};
$(
unbind(&child, $bind_id);
)*
});
};
}
fn store_binding(object: &impl IsA<gtk::Widget>, key: Quark, binding: Binding) {
unsafe {
object.set_qdata(key, binding);
}
}
fn unbind(child: >k::Widget, key: Quark) {
let binding: Option<Binding> = unsafe { child.steal_qdata(key) };
if let Some(binding) = binding {
binding.unbind();
}
}
fn inactive_display(widget: &impl IsA<gtk::Widget>, unit: &UnitInfo) {
display_inactive!(widget, unit);
let binding = unit
.bind_property(ACTIVE_STATE, widget, CSS_CLASSES)
.transform_to(|_, active_state: ActiveState| {
let css_classes = if active_state.is_inactive() {
[CSS_GREY].to_value()
} else {
[].to_value()
};
Some(css_classes)
})
.build();
store_binding(widget, *BIND_CSS, binding);
}
pub fn fac_unit_name(display_color: bool) -> gtk::SignalListItemFactory {
common_factory(display_color, UnitInfo::display_name)
}
pub fn fac_unit_name_full(display_color: bool) -> gtk::SignalListItemFactory {
common_factory(display_color, UnitInfo::primary)
}
fn common_factory(
display_color: bool,
func: fn(&UnitInfo) -> String,
) -> gtk::SignalListItemFactory {
let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(factory_setup);
if display_color {
factory.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind!(object, func);
inactive_display(&inscription, &unit)
});
factory_connect_unbind!(factory, *BIND_CSS);
} else {
factory.connect_bind(move |_factory, object| {
factory_bind!(object, func);
});
}
factory
}
pub fn fac_unit_type(display_color: bool) -> gtk::SignalListItemFactory {
let fac_unit_type = gtk::SignalListItemFactory::new();
fac_unit_type.connect_setup(factory_setup);
if display_color {
fac_unit_type.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind_enum!(object, unit_type);
inactive_display(&inscription, &unit)
});
factory_connect_unbind!(fac_unit_type, *BIND_CSS);
} else {
fac_unit_type.connect_bind(move |_factory, object| {
factory_bind_enum!(object, unit_type);
});
}
fac_unit_type
}
pub fn fac_bus(display_color: bool) -> gtk::SignalListItemFactory {
let fac_bus = gtk::SignalListItemFactory::new();
fac_bus.connect_setup(factory_setup);
if display_color {
fac_bus.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind_enum!(object, dbus_level);
inactive_display(&inscription, &unit)
});
factory_connect_unbind!(fac_bus, *BIND_CSS);
} else {
fac_bus.connect_bind(move |_factory, object| {
factory_bind_enum!(object, dbus_level);
});
}
fac_bus
}
pub fn fac_active(display_color: bool) -> gtk::SignalListItemFactory {
let fac_active = gtk::SignalListItemFactory::new();
fac_active.connect_setup(|_factory, object| {
let item = downcast_list_item!(object);
let image = gtk::Image::new();
item.set_child(Some(&image));
});
if display_color {
fac_active.connect_bind(|_factory, object| {
let (icon_image, unit) = active_icon(object);
inactive_display(&icon_image, &unit)
});
factory_connect_unbind!(&fac_active, *BIND_INFO, *BIND_CSS);
} else {
fac_active.connect_bind(|_factory, object| {
active_icon(object);
});
factory_connect_unbind!(&fac_active, *BIND_INFO);
}
fac_active
}
fn active_icon(object: &glib::Object) -> (gtk::Image, UnitInfo) {
let list_item: >k::ListItem = downcast_list_item!(object);
let icon_image = list_item.child().and_downcast::<gtk::Image>().unwrap();
let unit = list_item.item().and_downcast::<UnitInfo>().unwrap();
let state = unit.active_state();
let icon_name = state.icon_name();
icon_image.set_icon_name(icon_name);
icon_image.set_tooltip_text(Some(state.as_str()));
let binding = unit
.bind_property("active_state", &icon_image, "icon-name")
.transform_to(|_, state: ActiveState| state.icon_name())
.build();
store_binding(&icon_image, *BIND_INFO, binding);
(icon_image, unit)
}
pub fn fac_sub_state(display_color: bool) -> gtk::SignalListItemFactory {
common_factory(display_color, UnitInfo::sub_state)
}
pub fn fac_descrition(display_color: bool) -> gtk::SignalListItemFactory {
let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(factory_setup);
if display_color {
factory.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind_pre!(object);
let text = UnitInfo::description(&unit);
inscription.set_text(text.as_deref());
inactive_display(&inscription, &unit)
});
factory_connect_unbind!(factory, *BIND_CSS);
} else {
factory.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind_pre!(object);
let text = UnitInfo::description(&unit);
inscription.set_text(text.as_deref());
});
}
factory
}
pub fn setup_factories(
unit_list: &UnitListPanel,
column_view_column_list: &Vec<gtk::ColumnViewColumn>,
current_column_view_column_definition_list: &[UnitPropertySelection],
) {
let display_color = unit_list.display_color();
for column in column_view_column_list {
let Some(id) = column.id() else {
warn!("Column with no id!");
continue;
};
let col_id = current_column_view_column_definition_list
.iter()
.find_map(|prop_selection| {
if prop_selection.id().is_some_and(|s| id == s.as_str()) {
prop_selection.sysd_column()
} else {
None
}
});
if let Some(col_id) = col_id {
let factory = get_factory_by_id(&col_id, display_color);
column.set_factory(factory.as_ref());
}
}
}
pub fn get_factory_by_id(
id: &SysdColumn,
display_color: bool,
) -> Option<gtk::SignalListItemFactory> {
match id {
SysdColumn::Custom(_) => Some(get_custom_factory(id, display_color)),
SysdColumn::Name => Some(fac_unit_name(display_color)),
SysdColumn::FullName => Some(fac_unit_name_full(display_color)),
SysdColumn::Type => Some(fac_unit_type(display_color)),
SysdColumn::Bus => Some(fac_bus(display_color)),
SysdColumn::State => Some(fac_enable_status(display_color)),
SysdColumn::Preset => Some(fac_preset(display_color)),
SysdColumn::Load => Some(fac_load_state(display_color)),
SysdColumn::Active => Some(fac_active(display_color)),
SysdColumn::SubState => Some(fac_sub_state(display_color)),
SysdColumn::Description => Some(fac_descrition(display_color)),
SysdColumn::TimerTimeNextElapseRT => Some(fac_time_next()),
SysdColumn::TimerTimeLeftElapseMono => Some(fac_time_left()),
SysdColumn::TimerTimePassed => Some(fac_time_passed()),
SysdColumn::TimerTimeLast => Some(fac_time_last()),
SysdColumn::SocketListenType => Some(fac_socket_listen_type()),
SysdColumn::SocketListen => Some(fac_socket_listen()),
SysdColumn::PathCondition => Some(fac_path_condition()),
SysdColumn::Path => Some(fac_path_path()),
SysdColumn::AutomountWhat => Some(fac_automount_what()),
SysdColumn::AutomountMounted => Some(fac_automount_mounted()),
SysdColumn::AutomountIdleTimeOut => Some(fac_automount_idle_timeout()),
}
}
const LOAD_STATE: &str = "load_state";
pub fn fac_load_state(display_color: bool) -> gtk::SignalListItemFactory {
let fac_load_state = gtk::SignalListItemFactory::new();
fac_load_state.connect_setup(factory_setup);
if display_color {
fac_load_state.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind_enum!(object, load_state);
load_state_text_binding(&inscription, &unit);
let binding = unit
.bind_property(LOAD_STATE, &inscription, CSS_CLASSES)
.transform_to(|_, load_state: LoadState| {
let css_classes = load_state_css_classes(load_state);
css_classes.map(|css| css.to_value())
})
.build();
store_binding(&inscription, *BIND_CSS, binding);
let binding = unit
.bind_property(ACTIVE_STATE, &inscription, CSS_CLASSES)
.transform_to(|_, active_state: ActiveState| {
let css_classes = if active_state.is_inactive() {
[CSS_GREY].to_value()
} else {
[].to_value()
};
Some(css_classes)
})
.build();
store_binding(&inscription, *BIND_CSS2, binding);
let load_state = unit.load_state();
inscription.set_text(Some(load_state.as_str()));
let css_classes = load_state_css_classes(load_state);
if let Some(css) = css_classes {
inscription.set_css_classes(&css);
} else {
display_inactive!(inscription, unit);
}
});
factory_connect_unbind!(&fac_load_state, *BIND_INFO, *BIND_CSS, *BIND_CSS2);
} else {
fac_load_state.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind_enum!(object, load_state);
load_state_text_binding(&inscription, &unit);
});
factory_connect_unbind!(&fac_load_state, *BIND_INFO);
}
fac_load_state
}
fn load_state_text_binding(inscription: >k::Inscription, unit: &UnitInfo) {
let binding = unit
.bind_property(LOAD_STATE, inscription, TEXT)
.transform_to(|_, load_state: LoadState| Some(load_state.as_str()))
.build();
store_binding(inscription, *BIND_INFO, binding);
}
fn load_state_css_classes<'a>(load_state: LoadState) -> Option<[&'a str; 2]> {
match load_state {
LoadState::NotFound => Some(["yellow", "bold"]),
LoadState::BadSetting | LoadState::Error | LoadState::Masked => Some(["red", "bold"]),
_ => None,
}
}
const ENABLE_STATUS: &str = "enable_status";
pub fn fac_enable_status(display_color: bool) -> gtk::SignalListItemFactory {
let fac_enable_status = gtk::SignalListItemFactory::new();
fac_enable_status.connect_setup(factory_setup);
if display_color {
fac_enable_status.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind_pre!(object);
let status_code = unit.enable_status();
inscription.set_text(Some(status_code.as_str()));
inscription.set_tooltip_markup(status_code.tooltip_info().as_deref());
let binding = unit
.bind_property(ENABLE_STATUS, &inscription, TEXT)
.transform_to(|_, enablement_status: UnitFileStatus| {
Some(enablement_status.as_str())
})
.build();
store_binding(&inscription, *BIND_INFO, binding);
let binding = unit
.bind_property(ENABLE_STATUS, &inscription, CSS_CLASSES)
.transform_to(|_, enablement_status: UnitFileStatus| {
let css_classes = enablement_css_classes(enablement_status);
css_classes.map(|css| css.to_value())
})
.build();
store_binding(&inscription, *BIND_CSS, binding);
let css_classes = enablement_css_classes(status_code);
if let Some(css) = css_classes {
inscription.set_css_classes(&css);
} else {
display_inactive!(inscription, unit);
}
});
factory_connect_unbind!(&fac_enable_status, *BIND_INFO, *BIND_CSS);
} else {
fac_enable_status.connect_bind(move |_factory, object| {
factory_bind_enum!(object, enable_status);
});
}
fac_enable_status
}
fn enablement_css_classes<'a>(enablement_status: UnitFileStatus) -> Option<[&'a str; 2]> {
match enablement_status {
UnitFileStatus::Bad
| UnitFileStatus::Disabled
| UnitFileStatus::Masked
| UnitFileStatus::MaskedRuntime => Some(["red", "bold"]),
UnitFileStatus::Alias | UnitFileStatus::Enabled | UnitFileStatus::EnabledRuntime => {
Some(["green", "bold"])
}
_ => None,
}
}
const PRESET_NUM: &str = "preset";
pub fn fac_preset(display_color: bool) -> gtk::SignalListItemFactory {
let fac_preset = gtk::SignalListItemFactory::new();
fac_preset.connect_setup(factory_setup);
if display_color {
fac_preset.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind_enum!(object, preset);
preset_text_binding(&inscription, &unit);
let binding = unit
.bind_property(PRESET_NUM, &inscription, CSS_CLASSES)
.transform_to(|_s, preset_value: Preset| {
let css_classes = preset_css_classes(preset_value);
css_classes.map(|css| css.to_value())
})
.build();
store_binding(&inscription, *BIND_CSS, binding);
let preset_value = unit.preset();
let css_classes = preset_css_classes(preset_value);
if let Some(css) = css_classes {
inscription.set_css_classes(&css);
} else {
display_inactive!(inscription, unit);
inscription.set_css_classes(&[]);
}
});
factory_connect_unbind!(&fac_preset, *BIND_INFO, *BIND_CSS);
} else {
fac_preset.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind_enum!(object, preset);
preset_text_binding(&inscription, &unit);
});
factory_connect_unbind!(&fac_preset, *BIND_INFO);
}
fac_preset
}
fn preset_text_binding(inscription: >k::Inscription, unit: &UnitInfo) {
let binding = unit
.bind_property(PRESET_NUM, inscription, TEXT)
.transform_to(|_s, preset: Preset| preset.as_str_op())
.build();
store_binding(inscription, *BIND_INFO, binding);
}
fn preset_css_classes(preset_value: Preset) -> Option<[&'static str; 2]> {
match preset_value {
Preset::Disabled => Some(["red", "bold"]),
Preset::Enabled => Some(["green", "bold"]),
Preset::Ignore => Some(["yellow", "bold"]),
_ => None,
}
}
type UnitFunc = fn(&UnitInfo, Quark) -> Option<String>;
type SocketFunc = fn(&SocketUnitInfo, Quark) -> Option<String>;
pub(super) fn get_custom_factory(
sysd_col: &SysdColumn,
display_color: bool,
) -> gtk::SignalListItemFactory {
let factory = gtk::SignalListItemFactory::new();
let key = sysd_col.generate_quark();
factory.connect_setup(factory_setup);
let Some(prop_type) = sysd_col.property_type() else {
error!("NO PROP_TYPE SET for {:?}", sysd_col);
return factory;
};
let (get_value, get_parent_value): (UnitFunc, SocketFunc) = match prop_type.as_str() {
"b" => (
UnitInfo::get_custom_property_to_string::<bool>,
SocketUnitInfo::get_parent_qproperty_to_string::<bool>,
),
"n" => (
UnitInfo::get_custom_property_to_string::<i16>,
SocketUnitInfo::get_parent_qproperty_to_string::<i16>,
),
"q" => (
UnitInfo::get_custom_property_to_string::<u16>,
SocketUnitInfo::get_parent_qproperty_to_string::<u16>,
),
"i" => (
UnitInfo::get_custom_property_to_string::<i32>,
SocketUnitInfo::get_parent_qproperty_to_string::<i32>,
),
"u" => (
UnitInfo::get_custom_property_to_string::<u32>,
SocketUnitInfo::get_parent_qproperty_to_string::<u32>,
),
"x" => (
UnitInfo::get_custom_property_to_string::<i64>,
SocketUnitInfo::get_parent_qproperty_to_string::<i64>,
),
"t" => (
UnitInfo::get_custom_property_to_string::<u64>,
SocketUnitInfo::get_parent_qproperty_to_string::<u64>,
),
"s" => (
UnitInfo::get_custom_property_to_string::<String>,
SocketUnitInfo::get_parent_qproperty_to_string::<String>,
),
"v" => (
UnitInfo::display_custom_property,
SocketUnitInfo::display_custom_property,
),
_ => (
UnitInfo::get_custom_property_to_string::<String>,
SocketUnitInfo::get_parent_qproperty_to_string::<String>,
),
};
factory.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind_pre!(object);
if display_color {
inactive_display(&inscription, &unit);
}
let value = get_value(&unit, key).or_else(|| {
if unit.has_property(PROPERTY_NAME) {
unit.downcast_ref::<SocketUnitInfo>()
.and_then(|us| get_parent_value(us, key))
} else {
None
}
});
inscription.set_text(value.as_deref());
});
factory
}
fn fac_time_next() -> gtk::SignalListItemFactory {
let time_fac = gtk::SignalListItemFactory::new();
time_fac.connect_setup(factory_setup);
let next_elapse_realtime_key = SysdColumn::TimerTimeNextElapseRT.generate_quark();
let next_elapse_monotonic_key = SysdColumn::TimerTimeLeftElapseMono.generate_quark();
let timestamp_style = PREFERENCES.timestamp_style();
time_fac.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind_pre!(object);
inactive_display(&inscription, &unit);
let next_elapse =
calculate_next_elapse(next_elapse_realtime_key, next_elapse_monotonic_key, &unit);
if next_elapse != u64::MAX {
let time_since = time_handling::get_since_time(next_elapse, timestamp_style);
inscription.set_text(Some(&time_since));
} else {
inscription.set_text(None);
}
});
time_fac
}
fn fac_time_left() -> gtk::SignalListItemFactory {
let time_fac = gtk::SignalListItemFactory::new();
time_fac.connect_setup(factory_setup);
let next_elapse_realtime_key = SysdColumn::TimerTimeNextElapseRT.generate_quark();
let next_elapse_monotonic_key = SysdColumn::TimerTimeLeftElapseMono.generate_quark();
time_fac.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind_pre!(object);
inactive_display(&inscription, &unit);
let next_elapse =
calculate_next_elapse(next_elapse_realtime_key, next_elapse_monotonic_key, &unit);
if next_elapse != u64::MAX {
let time_relative = time_handling::format_timestamp_relative_full(next_elapse);
inscription.set_text(Some(&time_relative));
} else {
inscription.set_text(None);
}
});
time_fac
}
pub fn calculate_next_elapse<O>(
next_elapse_realtime_key: Quark,
next_elapse_monotonic_key: Quark,
unit: &O,
) -> u64
where
O: ObjectExt,
{
let next_elapse_realtime =
get_custom_property_typed_raw::<u64, O>(unit, next_elapse_realtime_key).unwrap_or(U64MAX);
let next_elapse_monotonic =
get_custom_property_typed_raw::<u64, O>(unit, next_elapse_monotonic_key).unwrap_or(U64MAX);
time_handling::calc_next_elapse(next_elapse_realtime, next_elapse_monotonic)
}
fn fac_time_last() -> gtk::SignalListItemFactory {
let time_fac = gtk::SignalListItemFactory::new();
time_fac.connect_setup(factory_setup);
let time_last_trigger_key = Quark::from_str(TIME_LAST_TRIGGER_USEC);
let timestamp_style = PREFERENCES.timestamp_style();
time_fac.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind_pre!(object);
inactive_display(&inscription, &unit);
let time_last_trigger =
get_custom_property_typed_raw::<u64, UnitInfo>(&unit, time_last_trigger_key)
.unwrap_or(U64MAX);
if timestamp_is_set!(time_last_trigger) {
let time_since = time_handling::get_since_time(time_last_trigger, timestamp_style);
inscription.set_text(Some(&time_since));
} else {
inscription.set_text(None);
}
});
time_fac
}
fn fac_time_passed() -> gtk::SignalListItemFactory {
let time_fac = gtk::SignalListItemFactory::new();
time_fac.connect_setup(factory_setup);
let time_last_trigger_key = Quark::from_str(TIME_LAST_TRIGGER_USEC);
time_fac.connect_bind(move |_factory, object| {
let (inscription, unit) = factory_bind_pre!(object);
inactive_display(&inscription, &unit);
let time_last_trigger =
get_custom_property_typed_raw::<u64, UnitInfo>(&unit, time_last_trigger_key)
.unwrap_or(U64MAX);
if timestamp_is_set!(time_last_trigger) {
let time_relative = time_handling::format_timestamp_relative_full(time_last_trigger);
inscription.set_text(Some(&time_relative));
} else {
inscription.set_text(None);
}
});
time_fac
}
#[macro_export]
macro_rules! extract_tuple_idx {
($unit: expr, $socket_listen: expr, $param : expr) => {
match $param {
0 => extract_listen!($unit, $socket_listen, 0),
1 => extract_listen!($unit, $socket_listen, 1),
_ => panic!("Not handled"),
}
};
}
#[macro_export]
macro_rules! extract_listen {
($unit: expr, $socket_listen: expr, $param : tt) => {{
if $unit.has_property(PROPERTY_NAME) {
if let Some(unit_socket) = $unit.downcast_ref::<SocketUnitInfo>() {
let idx = unit_socket.socket_listen_idx();
let listens =
unit_socket.get_parent_qproperty::<Vec<(String, String)>>($socket_listen);
listens
.and_then(|v| v.get(idx as usize))
.map(|t| t.$param.as_str())
} else {
None
}
} else {
let listens = $unit.get_custom_property::<Vec<(String, String)>>($socket_listen);
listens.and_then(|v| v.first()).map(|t| t.$param.as_str())
}
}};
}
fn fac_socket_listen_type() -> gtk::SignalListItemFactory {
let socket_fac = gtk::SignalListItemFactory::new();
socket_fac.connect_setup(factory_setup);
let socket_listen = SysdColumn::SocketListen.generate_quark();
socket_fac.connect_bind(move |_, object| {
let (inscription, unit) = factory_bind_pre!(object);
let value = extract_listen!(unit, socket_listen, 0);
inscription.set_text(value);
});
socket_fac
}
fn fac_socket_listen() -> gtk::SignalListItemFactory {
let socket_fac = gtk::SignalListItemFactory::new();
socket_fac.connect_setup(factory_setup);
let socket_listen = SysdColumn::SocketListen.generate_quark();
socket_fac.connect_bind(move |_, object| {
let (inscription, unit) = factory_bind_pre!(object);
let value = extract_listen!(unit, socket_listen, 1);
inscription.set_text(value);
});
socket_fac
}
fn fac_path_condition() -> gtk::SignalListItemFactory {
let socket_fac = gtk::SignalListItemFactory::new();
socket_fac.connect_setup(factory_setup);
let key = SysdColumn::PathCondition.generate_quark();
socket_fac.connect_bind(move |_, object| {
let (inscription, unit) = factory_bind_pre!(object);
let value = extract_listen!(unit, key, 0);
inscription.set_text(value);
});
socket_fac
}
fn fac_path_path() -> gtk::SignalListItemFactory {
let socket_fac = gtk::SignalListItemFactory::new();
socket_fac.connect_setup(factory_setup);
let key = SysdColumn::Path.generate_quark();
socket_fac.connect_bind(move |_, object| {
let (inscription, unit) = factory_bind_pre!(object);
let value = extract_listen!(unit, key, 1);
inscription.set_text(value);
});
socket_fac
}
fn fac_automount_mounted() -> gtk::SignalListItemFactory {
let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(factory_setup);
let where_key = SysdColumn::AutomountMounted.generate_quark();
factory.connect_bind(move |_, object| {
let (inscription, unit) = factory_bind_pre!(object);
let time = unit.get_custom_property::<String>(where_key);
let mounted =
if let Some(unit_name) = time.and_then(|s| unit_name_from_path(s.as_str(), ".mount")) {
fetch_property(unit.dbus_level(), &unit_name, UnitType::Unit, "ActiveState")
.inspect_err(|err| warn!("{err:?}"))
.map(|v| {
let state: ActiveState = v.into();
if state.is_inactive() { "no" } else { "yes" }
})
.ok()
} else {
None
};
inscription.set_text(mounted);
});
factory
}
fn fac_automount_what() -> gtk::SignalListItemFactory {
let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(factory_setup);
let where_key = SysdColumn::AutomountWhat.generate_quark();
factory.connect_bind(move |_, object| {
let (inscription, unit) = factory_bind_pre!(object);
let time = unit.get_custom_property::<String>(where_key);
let what =
if let Some(unit_name) = time.and_then(|s| unit_name_from_path(s.as_str(), ".mount")) {
fetch_property(unit.dbus_level(), &unit_name, UnitType::Mount, "What")
.inspect_err(|err| warn!("{err:?}"))
.ok()
} else {
None
};
inscription.set_text(what.as_deref());
});
factory
}
fn fetch_property(
level: UnitDBusLevel,
name: &str,
utype: UnitType,
prop: &str,
) -> Result<String, SystemdErrors> {
let value = systemd::fetch_unit_property_blocking(level, name, utype, prop)?;
let v = value.downcast_ref::<String>()?;
Ok(v)
}
fn fac_automount_idle_timeout() -> gtk::SignalListItemFactory {
let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(factory_setup);
let timeout_idle_key = SysdColumn::AutomountIdleTimeOut.generate_quark();
factory.connect_bind(move |_, object| {
let (inscription, unit) = factory_bind_pre!(object);
let time = unit.get_custom_property::<u64>(timeout_idle_key);
let time_str = if let Some(time) = time
&& *time != 0
{
let time_str = time_handling::format_timespan(*time, MSEC_PER_SEC);
Some(time_str)
} else {
None
};
inscription.set_text(time_str.as_deref());
});
factory
}
fn unit_name_from_path(path: &str, suffix: &str) -> Option<String> {
let mut out = String::with_capacity(path.len());
for t in path.split('/') {
match t {
".." => return None,
"." => continue,
_ => {}
}
if !out.is_empty() {
out.push('-')
}
out.push_str(t);
}
if out.is_empty() {
out.push('-');
} else if let Some(c) = out.chars().last()
&& c == '-'
{
out.pop();
}
out.push_str(suffix);
Some(out)
}
#[cfg(test)]
mod tests {
#[test]
fn test_unit_name_from_path() {
test_unit_name_from_path_one("/waldo", ".mount", Some("waldo.mount"));
test_unit_name_from_path_one("/waldo/quuix", ".mount", Some("waldo-quuix.mount"));
test_unit_name_from_path_one("/waldo/quuix/", ".mount", Some("waldo-quuix.mount"));
test_unit_name_from_path_one("", ".mount", Some("-.mount"));
test_unit_name_from_path_one("/", ".mount", Some("-.mount"));
test_unit_name_from_path_one("///", ".mount", Some("-.mount"));
test_unit_name_from_path_one("/foo/../bar", ".mount", None);
test_unit_name_from_path_one("/foo/./bar", ".mount", Some("foo-bar.mount"));
}
fn test_unit_name_from_path_one(path: &str, suffix: &str, expected: Option<&str>) {
let s = super::unit_name_from_path(path, suffix);
assert_eq!(s.as_deref(), expected);
}
}