diff --git a/Cargo.lock b/Cargo.lock index aed281b..915d3c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -546,6 +546,28 @@ version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +[[package]] +name = "git-version" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b0decc02f4636b9ccad390dcbe77b722a77efedfa393caf8379a51d5c61899" +dependencies = [ + "git-version-macro", + "proc-macro-hack", +] + +[[package]] +name = "git-version-macro" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe69f1cbdb6e28af2bac214e943b99ce8a0a06b447d15d3e61161b0423139f3f" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.99", +] + [[package]] name = "glob" version = "0.3.0" @@ -661,6 +683,7 @@ dependencies = [ "cursive", "env_logger 0.10.0", "futures", + "git-version", "lazy_static", "log", "notify 6.0.1", @@ -1058,6 +1081,12 @@ dependencies = [ "time", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "1.0.66" diff --git a/launchk/Cargo.toml b/launchk/Cargo.toml index 06add4b..055d0fb 100644 --- a/launchk/Cargo.toml +++ b/launchk/Cargo.toml @@ -19,4 +19,5 @@ log = "0.4.20" env_logger = "0.10.0" notify-debouncer-mini = { version = "*", default-features = false } sudo = "0.6.0" -clearscreen = "2.0.1" \ No newline at end of file +clearscreen = "2.0.1" +git-version = "0.3.5" \ No newline at end of file diff --git a/launchk/src/launchd/entry_status.rs b/launchk/src/launchd/entry_status.rs index 7ca1d08..17cfee0 100644 --- a/launchk/src/launchd/entry_status.rs +++ b/launchk/src/launchd/entry_status.rs @@ -16,7 +16,7 @@ lazy_static! { Mutex::new(HashMap::new()); } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct LaunchdEntryStatus { pub plist: Option, pub limit_load_to_session_type: SessionType, diff --git a/launchk/src/launchd/job_type_filter.rs b/launchk/src/launchd/job_type_filter.rs index c51fb95..9b6a781 100644 --- a/launchk/src/launchd/job_type_filter.rs +++ b/launchk/src/launchd/job_type_filter.rs @@ -2,7 +2,7 @@ use std::fmt; use std::fmt::Formatter; bitflags! { - #[derive(Clone, Copy, Default, Eq, PartialEq)] + #[derive(Clone, Copy, Default, Eq, PartialEq, Hash)] /// Bitmask for filtering on the job type, which is a mix /// of scope (where it's located), and kind (agent v. daemon) pub struct JobTypeFilter: u32 { diff --git a/launchk/src/launchd/plist.rs b/launchk/src/launchd/plist.rs index aee2889..784498a 100644 --- a/launchk/src/launchd/plist.rs +++ b/launchk/src/launchd/plist.rs @@ -32,7 +32,7 @@ od -xc binary.plist */ static PLIST_MAGIC: &str = "bplist00"; -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum LaunchdEntryType { /// Runs on behalf of currently logged in user Agent, @@ -46,7 +46,7 @@ impl fmt::Display for LaunchdEntryType { } } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum LaunchdEntryLocation { /// macOS system provided agent or daemon System, @@ -64,7 +64,7 @@ impl fmt::Display for LaunchdEntryLocation { } } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct LaunchdPlist { pub entry_type: LaunchdEntryType, pub entry_location: LaunchdEntryLocation, diff --git a/launchk/src/main.rs b/launchk/src/main.rs index bba6046..00e6cdd 100644 --- a/launchk/src/main.rs +++ b/launchk/src/main.rs @@ -11,6 +11,7 @@ extern crate plist; use cursive::view::Resizable; use cursive::views::{NamedView, Panel}; +use git_version::git_version; use std::process::exit; use crate::launchd::plist::{init_plist_map, PLIST_MAP_INIT}; @@ -38,7 +39,7 @@ fn main() { let root_layout = NamedView::new("root_layout", root_layout); let panel = Panel::new(root_layout) - .title("launchk") + .title(format!("launchk ({})", git_version!())) .full_width() .full_height(); diff --git a/launchk/src/tui/dialog.rs b/launchk/src/tui/dialog.rs index 7b5c7cd..a266c46 100644 --- a/launchk/src/tui/dialog.rs +++ b/launchk/src/tui/dialog.rs @@ -160,7 +160,7 @@ pub fn show_csr_info() -> CbSinkMessage { pub fn show_help() -> CbSinkMessage { let commands = OMNIBOX_COMMANDS .iter() - .map(|(cmd, desc, _)| format!("{}: {}", cmd, desc)) + .map(|(cmd, desc, _)| format!("{:<15}: {}", cmd, desc.chars().filter(|c| c.is_ascii()).collect::())) .collect::>(); Box::new(move |siv| { diff --git a/launchk/src/tui/omnibox/command.rs b/launchk/src/tui/omnibox/command.rs index 4885ab7..a4221e9 100644 --- a/launchk/src/tui/omnibox/command.rs +++ b/launchk/src/tui/omnibox/command.rs @@ -89,5 +89,5 @@ pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 12] = [ OmniboxCommand::ProcInfo, ), ("help", "🤔 Show all commands", OmniboxCommand::Help), - ("exit", "🚪 see ya!", OmniboxCommand::Quit), + ("exit", "🚪 see ya!", OmniboxCommand::Quit), ]; diff --git a/launchk/src/tui/pager.rs b/launchk/src/tui/pager.rs index 009e822..a8ad915 100644 --- a/launchk/src/tui/pager.rs +++ b/launchk/src/tui/pager.rs @@ -5,6 +5,7 @@ use std::sync::mpsc::Sender; use super::root::CbSinkMessage; use cursive::Cursive; +use clearscreen; lazy_static! { static ref PAGER: String = env::var("PAGER").unwrap_or("less".to_string()); @@ -12,9 +13,7 @@ lazy_static! { /// Show $PAGER (or less), write buf, and clear Cursive after exiting pub fn show_pager(cbsink: &Sender, buf: &[u8]) -> Result<(), String> { - cbsink - .send(Box::new(Cursive::clear)) - .expect("Must clear before"); + clearscreen::clear().expect("Must clear screen"); let mut pager = Command::new(&*PAGER) .stdin(Stdio::piped()) diff --git a/launchk/src/tui/service_list/list_item.rs b/launchk/src/tui/service_list/list_item.rs index 1de8b35..b3be5e7 100644 --- a/launchk/src/tui/service_list/list_item.rs +++ b/launchk/src/tui/service_list/list_item.rs @@ -4,7 +4,7 @@ use crate::launchd::entry_status::LaunchdEntryStatus; use crate::launchd::job_type_filter::JobTypeFilter; use crate::tui::table::table_list_view::TableListItem; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct ServiceListItem { pub name: String, pub status: LaunchdEntryStatus, diff --git a/launchk/src/tui/table/table_list_view.rs b/launchk/src/tui/table/table_list_view.rs index cf901c5..1b21046 100644 --- a/launchk/src/tui/table/table_list_view.rs +++ b/launchk/src/tui/table/table_list_view.rs @@ -1,7 +1,10 @@ +use std::cell::RefCell; +use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use std::rc::Rc; use std::sync::Arc; +use std::collections::hash_map::DefaultHasher; use cursive::event::{Event, EventResult}; use cursive::traits::{Resizable, Scrollable}; @@ -23,6 +26,7 @@ pub struct TableListView { linear_layout: LinearLayout, // LinearLayout swallows T from , but we still need it inner: PhantomData, + last_hash: RefCell } impl TableListView { @@ -36,6 +40,7 @@ impl TableListView { .into_iter() .map(|(n, _)| n.as_ref().to_string()); let column_sizer = ColumnSizer::new(columns); + let last_hash = RefCell::new(0u64); let mut linear_layout = LinearLayout::vertical(); linear_layout.add_child( @@ -49,16 +54,19 @@ impl TableListView { .full_height() .scrollable(), ); + Self { linear_layout, column_sizer, inner: PhantomData::default(), + last_hash } } pub fn replace_and_preserve_selection(&mut self, items: I) where I: IntoIterator, + T: Hash { let rows: Vec<(String, T)> = items .into_iter() @@ -80,9 +88,17 @@ impl TableListView { }) .collect(); + let mut row_hasher = DefaultHasher::new(); + rows.hash(&mut row_hasher); + let hash = row_hasher.finish(); + + if *self.last_hash.borrow() == hash { return } + log::trace!("Replaced listview items -- new hash {}", hash); + *self.last_hash.borrow_mut() = hash; + let sv = self.get_mut_selectview(); let current_selection = sv.selected_id().unwrap_or(0); - + sv.clear(); sv.add_all(rows); sv.set_selection(current_selection); diff --git a/xpc-sys/src/enums.rs b/xpc-sys/src/enums.rs index 46e6573..74d48eb 100644 --- a/xpc-sys/src/enums.rs +++ b/xpc-sys/src/enums.rs @@ -10,7 +10,7 @@ use crate::traits::xpc_value::TryXPCValue; /// LimitLoadToSessionType key in XPC response /// https://developer.apple.com/library/archive/technotes/tn2083/_index.html -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum SessionType { Aqua = 0, StandardIO, @@ -70,7 +70,7 @@ impl TryFrom> for SessionType { } // Huge thanks to: https://saelo.github.io/presentations/bits_of_launchd.pdf -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum DomainType { System = 1, User = 2, diff --git a/xpc-sys/src/lib.rs b/xpc-sys/src/lib.rs index 877003a..60d5e5c 100644 --- a/xpc-sys/src/lib.rs +++ b/xpc-sys/src/lib.rs @@ -23,10 +23,10 @@ pub mod traits; pub type xpc_pipe_t = *mut c_void; -/// Some extra private API definitions. Thanks: -/// -/// https://developer.apple.com/documentation/kernel/mach -/// https://chromium.googlesource.com/chromium/src.git/+/47.0.2507.2/sandbox/mac/xpc_private_stubs.sig +// Some extra private API definitions. Thanks: +// +// https://developer.apple.com/documentation/kernel/mach +// https://chromium.googlesource.com/chromium/src.git/+/47.0.2507.2/sandbox/mac/xpc_private_stubs.sig extern "C" { // Can decode i64 returned in "errors" for XPC responses pub fn xpc_strerror(err: c_int) -> *const c_char;