Added max_entries to limit amount of total entries

This commit is contained in:
Kirottu
2023-05-03 10:53:41 +03:00
parent b4b13ffdae
commit c4e0940ce5

View File

@@ -25,6 +25,7 @@ struct Config {
hide_plugin_info: bool, hide_plugin_info: bool,
ignore_exclusive_zones: bool, ignore_exclusive_zones: bool,
close_on_click: bool, close_on_click: bool,
max_entries: Option<usize>,
layer: Layer, layer: Layer,
} }
@@ -44,6 +45,7 @@ impl Default for Config {
hide_plugin_info: false, hide_plugin_info: false,
ignore_exclusive_zones: false, ignore_exclusive_zones: false,
close_on_click: false, close_on_click: false,
max_entries: None,
layer: Layer::Overlay, layer: Layer::Overlay,
} }
} }
@@ -74,7 +76,6 @@ struct PluginView {
struct Args { struct Args {
override_plugins: Option<Vec<String>>, override_plugins: Option<Vec<String>>,
config_dir: Option<String>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -96,6 +97,10 @@ struct RuntimeData {
exclusive: Option<PluginView>, exclusive: Option<PluginView>,
plugins: Vec<PluginView>, plugins: Vec<PluginView>,
post_run_action: PostRunAction, post_run_action: PostRunAction,
config: Config,
/// Used for displaying errors later on
error_label: String,
config_dir: String,
} }
/// The naming scheme for CSS styling /// The naming scheme for CSS styling
@@ -148,14 +153,49 @@ fn main() {
let override_plugins = dict.lookup::<Vec<String>>("override-plugins").unwrap(); let override_plugins = dict.lookup::<Vec<String>>("override-plugins").unwrap();
let config_dir = dict.lookup::<String>("config-dir").unwrap(); let config_dir = dict.lookup::<String>("config-dir").unwrap();
// Figure out the config dir
let user_dir = format!(
"{}/.config/anyrun",
env::var("HOME").expect("Could not determine home directory! Is $HOME set?")
);
let config_dir = config_dir.unwrap_or_else(|| {
if PathBuf::from(&user_dir).exists() {
user_dir
} else {
DEFAULT_CONFIG_DIR.to_string()
}
});
// Load config, if unable to then read default config. If an error occurs the message will be displayed.
let (config, error_label) = match fs::read_to_string(format!("{}/config.ron", config_dir)) {
Ok(content) => ron::from_str(&content)
.map(|config| (config, String::new()))
.unwrap_or_else(|why| {
(
Config::default(),
format!(
"Failed to parse Anyrun config file, using default config: {}",
why
),
)
}),
Err(why) => (
Config::default(),
format!(
"Failed to read Anyrun config file, using default config: {}",
why
),
),
};
*runtime_data_clone.borrow_mut() = Some(RuntimeData { *runtime_data_clone.borrow_mut() = Some(RuntimeData {
args: Args { args: Args { override_plugins },
override_plugins,
config_dir,
},
exclusive: None, exclusive: None,
plugins: Vec::new(), plugins: Vec::new(),
post_run_action: PostRunAction::None, post_run_action: PostRunAction::None,
error_label,
config,
config_dir,
}); });
-1 // Magic GTK number to continue running -1 // Magic GTK number to continue running
}); });
@@ -193,42 +233,6 @@ fn main() {
} }
fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>>>) { fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>>>) {
// Figure out the config dir
let user_dir = format!(
"{}/.config/anyrun",
env::var("HOME").expect("Could not determine home directory! Is $HOME set?")
);
let config_dir =
if let Some(config_dir) = &runtime_data.borrow().as_ref().unwrap().args.config_dir {
config_dir.clone()
} else if PathBuf::from(&user_dir).exists() {
user_dir
} else {
DEFAULT_CONFIG_DIR.to_string()
};
// Load config, if unable to then read default config. If an error occurs the message will be displayed.
let (config, error_label) = match fs::read_to_string(format!("{}/config.ron", config_dir)) {
Ok(content) => ron::from_str(&content)
.map(|config| (config, None))
.unwrap_or_else(|why| {
(
Config::default(),
Some(format!(
"Failed to parse Anyrun config file, using default config: {}",
why
)),
)
}),
Err(why) => (
Config::default(),
Some(format!(
"Failed to read Anyrun config file, using default config: {}",
why
)),
),
};
// Create the main window // Create the main window
let window = gtk::ApplicationWindow::builder() let window = gtk::ApplicationWindow::builder()
.application(app) .application(app)
@@ -246,13 +250,19 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
gtk_layer_shell::set_namespace(&window, "anyrun"); gtk_layer_shell::set_namespace(&window, "anyrun");
if config.ignore_exclusive_zones { if runtime_data
.borrow()
.as_ref()
.unwrap()
.config
.ignore_exclusive_zones
{
gtk_layer_shell::set_exclusive_zone(&window, -1); gtk_layer_shell::set_exclusive_zone(&window, -1);
} }
gtk_layer_shell::set_keyboard_mode(&window, gtk_layer_shell::KeyboardMode::Exclusive); gtk_layer_shell::set_keyboard_mode(&window, gtk_layer_shell::KeyboardMode::Exclusive);
match config.layer { match runtime_data.borrow().as_ref().unwrap().config.layer {
Layer::Background => { Layer::Background => {
gtk_layer_shell::set_layer(&window, gtk_layer_shell::Layer::Background) gtk_layer_shell::set_layer(&window, gtk_layer_shell::Layer::Background)
} }
@@ -263,7 +273,10 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
// Try to load custom CSS, if it fails load the default CSS // Try to load custom CSS, if it fails load the default CSS
let provider = gtk::CssProvider::new(); let provider = gtk::CssProvider::new();
if let Err(why) = provider.load_from_path(&format!("{}/style.css", config_dir)) { if let Err(why) = provider.load_from_path(&format!(
"{}/style.css",
runtime_data.borrow().as_ref().unwrap().config_dir
)) {
eprintln!("Failed to load custom CSS: {}", why); eprintln!("Failed to load custom CSS: {}", why);
provider provider
.load_from_data(include_bytes!("../res/style.css")) .load_from_data(include_bytes!("../res/style.css"))
@@ -284,7 +297,13 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
.override_plugins .override_plugins
{ {
Some(plugins) => plugins.iter().map(PathBuf::from).collect(), Some(plugins) => plugins.iter().map(PathBuf::from).collect(),
None => config.plugins, None => runtime_data
.borrow()
.as_ref()
.unwrap()
.config
.plugins
.clone(),
}; };
// Make sure at least one plugin is specified // Make sure at least one plugin is specified
@@ -306,7 +325,11 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
}; };
plugin_paths.append(&mut vec![ plugin_paths.append(&mut vec![
format!("{}/plugins", config_dir).into(), format!(
"{}/plugins",
runtime_data.borrow().as_ref().unwrap().config_dir
)
.into(),
format!("{}/plugins", DEFAULT_CONFIG_DIR).into(), format!("{}/plugins", DEFAULT_CONFIG_DIR).into(),
]); ]);
@@ -315,7 +338,10 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
.iter() .iter()
.map(|plugin_path| { .map(|plugin_path| {
// Load the plugin's dynamic library. // Load the plugin's dynamic library.
let mut user_path = PathBuf::from(&format!("{}/plugins", config_dir)); let mut user_path = PathBuf::from(&format!(
"{}/plugins",
runtime_data.borrow().as_ref().unwrap().config_dir
));
let mut global_path = PathBuf::from("/etc/anyrun/plugins"); let mut global_path = PathBuf::from("/etc/anyrun/plugins");
user_path.extend(plugin_path.iter()); user_path.extend(plugin_path.iter());
global_path.extend(plugin_path.iter()); global_path.extend(plugin_path.iter());
@@ -341,15 +367,32 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
.expect("Failed to load plugin"); .expect("Failed to load plugin");
// Run the plugin's init code to init static resources etc. // Run the plugin's init code to init static resources etc.
plugin.init()(config_dir.clone().into()); plugin.init()(
runtime_data
.borrow()
.as_ref()
.unwrap()
.config_dir
.clone()
.into(),
);
let plugin_box = gtk::Box::builder() let plugin_box = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal) .orientation(gtk::Orientation::Horizontal)
.spacing(10) .spacing(10)
.name(style_names::PLUGIN) .name(style_names::PLUGIN)
.build(); .build();
if !config.hide_plugin_info { if !runtime_data
plugin_box.add(&create_info_box(&plugin.info()(), config.hide_icons)); .borrow()
.as_ref()
.unwrap()
.config
.hide_plugin_info
{
plugin_box.add(&create_info_box(
&plugin.info()(),
runtime_data.borrow().as_ref().unwrap().config.hide_icons,
));
plugin_box.add( plugin_box.add(
&gtk::Separator::builder() &gtk::Separator::builder()
.orientation(gtk::Orientation::Horizontal) .orientation(gtk::Orientation::Horizontal)
@@ -409,15 +452,13 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
// Refresh the matches when text input changes // Refresh the matches when text input changes
let runtime_data_clone = runtime_data.clone(); let runtime_data_clone = runtime_data.clone();
entry.connect_changed(move |entry| { entry.connect_changed(move |entry| {
refresh_matches( refresh_matches(entry.text().to_string(), runtime_data_clone.clone())
entry.text().to_string(),
runtime_data_clone.clone(),
config.hide_icons,
)
}); });
// Handle other key presses for selection control and all other things that may be needed // Handle other key presses for selection control and all other things that may be needed
let entry_clone = entry.clone(); let entry_clone = entry.clone();
let runtime_data_clone = runtime_data.clone();
window.connect_key_press_event(move |window, event| { window.connect_key_press_event(move |window, event| {
use gdk::keys::constants; use gdk::keys::constants;
match event.keyval() { match event.keyval() {
@@ -429,7 +470,7 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
// Handle selections // Handle selections
constants::Down | constants::Tab | constants::Up => { constants::Down | constants::Tab | constants::Up => {
// Combine all of the matches into a `Vec` to allow for easier handling of the selection // Combine all of the matches into a `Vec` to allow for easier handling of the selection
let combined_matches = runtime_data let combined_matches = runtime_data_clone
.borrow() .borrow()
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -447,7 +488,7 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
.collect::<Vec<(gtk::ListBoxRow, gtk::ListBox)>>(); .collect::<Vec<(gtk::ListBoxRow, gtk::ListBox)>>();
// Get the selected match // Get the selected match
let (selected_match, selected_list) = match runtime_data let (selected_match, selected_list) = match runtime_data_clone
.borrow() .borrow()
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -515,9 +556,9 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
} }
// Handle when the selected match is "activated" // Handle when the selected match is "activated"
constants::Return => { constants::Return => {
let mut _runtime_data = runtime_data.borrow_mut(); let mut _runtime_data_clone = runtime_data_clone.borrow_mut();
let (selected_match, plugin_view) = match _runtime_data let (selected_match, plugin_view) = match _runtime_data_clone
.as_ref() .as_ref()
.unwrap() .unwrap()
.plugins .plugins
@@ -540,20 +581,17 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
} }
HandleResult::Refresh(exclusive) => { HandleResult::Refresh(exclusive) => {
if exclusive { if exclusive {
_runtime_data.as_mut().unwrap().exclusive = Some(plugin_view.clone()); _runtime_data_clone.as_mut().unwrap().exclusive =
Some(plugin_view.clone());
} else { } else {
_runtime_data.as_mut().unwrap().exclusive = None; _runtime_data_clone.as_mut().unwrap().exclusive = None;
} }
mem::drop(_runtime_data); // Drop the mutable borrow mem::drop(_runtime_data_clone); // Drop the mutable borrow
refresh_matches( refresh_matches(entry_clone.text().into(), runtime_data_clone.clone());
entry_clone.text().into(),
runtime_data.clone(),
config.hide_icons,
);
Inhibit(false) Inhibit(false)
} }
HandleResult::Copy(bytes) => { HandleResult::Copy(bytes) => {
_runtime_data.as_mut().unwrap().post_run_action = _runtime_data_clone.as_mut().unwrap().post_run_action =
PostRunAction::Copy(bytes.into()); PostRunAction::Copy(bytes.into());
window.close(); window.close();
Inhibit(true) Inhibit(true)
@@ -573,7 +611,13 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
// If the option is enabled, close the window when any click is received // If the option is enabled, close the window when any click is received
// that is outside the bounds of the main box // that is outside the bounds of the main box
if config.close_on_click { if runtime_data
.borrow()
.as_ref()
.unwrap()
.config
.close_on_click
{
window.connect_button_press_event(move |window, event| { window.connect_button_press_event(move |window, event| {
if event.window() == window.window() { if event.window() == window.window() {
window.close(); window.close();
@@ -586,7 +630,7 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
// Create widgets here for proper positioning // Create widgets here for proper positioning
window.connect_configure_event(move |window, event| { window.connect_configure_event(move |window, event| {
let width = match config.width { let width = match runtime_data.borrow().as_ref().unwrap().config.width {
RelativeNum::Absolute(width) => width, RelativeNum::Absolute(width) => width,
RelativeNum::Fraction(fraction) => (event.size().0 as f32 * fraction) as i32, RelativeNum::Fraction(fraction) => (event.size().0 as f32 * fraction) as i32,
}; };
@@ -602,16 +646,31 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
main_vbox.add(&entry); main_vbox.add(&entry);
// Display the error message // Display the error message
if let Some(error_label) = &error_label { if !runtime_data
.borrow()
.as_ref()
.unwrap()
.error_label
.is_empty()
{
main_vbox.add( main_vbox.add(
&gtk::Label::builder() &gtk::Label::builder()
.label(&format!(r#"<span foreground="red">{}</span>"#, error_label)) .label(&format!(
r#"<span foreground="red">{}</span>"#,
runtime_data.borrow().as_ref().unwrap().error_label
))
.use_markup(true) .use_markup(true)
.build(), .build(),
); );
} }
let vertical_offset = match config.vertical_offset { let vertical_offset = match runtime_data
.borrow()
.as_ref()
.unwrap()
.config
.vertical_offset
{
RelativeNum::Absolute(offset) => offset, RelativeNum::Absolute(offset) => offset,
RelativeNum::Fraction(fraction) => (event.size().1 as f32 * fraction) as i32, RelativeNum::Fraction(fraction) => (event.size().1 as f32 * fraction) as i32,
}; };
@@ -619,7 +678,7 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
fixed.put( fixed.put(
&main_vbox, &main_vbox,
(event.size().0 as i32 - width) / 2, (event.size().0 as i32 - width) / 2,
match config.position { match runtime_data.borrow().as_ref().unwrap().config.position {
Position::Top => vertical_offset, Position::Top => vertical_offset,
Position::Center => { Position::Center => {
(event.size().1 as i32 - entry.allocated_height()) / 2 + vertical_offset (event.size().1 as i32 - entry.allocated_height()) / 2 + vertical_offset
@@ -640,12 +699,7 @@ fn activate(app: &gtk::Application, runtime_data: Rc<RefCell<Option<RuntimeData>
window.show_all(); window.show_all();
} }
fn handle_matches( fn handle_matches(plugin_view: PluginView, runtime_data: &RuntimeData, matches: RVec<Match>) {
plugin_view: PluginView,
runtime_data: Rc<RefCell<Option<RuntimeData>>>,
matches: RVec<Match>,
hide_icons: bool,
) {
// Clear out the old matches from the list // Clear out the old matches from the list
for widget in plugin_view.list.children() { for widget in plugin_view.list.children() {
plugin_view.list.remove(&widget); plugin_view.list.remove(&widget);
@@ -664,7 +718,7 @@ fn handle_matches(
.name(style_names::MATCH) .name(style_names::MATCH)
.hexpand(true) .hexpand(true)
.build(); .build();
if !hide_icons { if !runtime_data.config.hide_icons {
if let ROption::RSome(icon) = &_match.icon { if let ROption::RSome(icon) = &_match.icon {
let mut builder = gtk::Image::builder() let mut builder = gtk::Image::builder()
.name(style_names::MATCH) .name(style_names::MATCH)
@@ -742,23 +796,32 @@ fn handle_matches(
plugin_view.row.show_all(); plugin_view.row.show_all();
let combined_matches = runtime_data let combined_matches = runtime_data
.borrow()
.as_ref()
.unwrap()
.plugins .plugins
.iter() .iter()
.flat_map(|view| { .flat_map(|view| {
view.list.children().into_iter().map(|child| { view.list
( .children()
child.dynamic_cast::<gtk::ListBoxRow>().unwrap(), .into_iter()
view.list.clone(), .map(move |child| (child.dynamic_cast::<gtk::ListBoxRow>().unwrap(), view))
)
}) })
}) .collect::<Vec<(gtk::ListBoxRow, &PluginView)>>();
.collect::<Vec<(gtk::ListBoxRow, gtk::ListBox)>>();
if let Some((row, list)) = combined_matches.get(0) { // If `max_entries` is set, truncate the amount of entries
list.select_row(Some(row)); if let Some(max_matches) = runtime_data.config.max_entries {
for (row, view) in combined_matches.iter().skip(max_matches) {
view.list.remove(row);
}
}
// Hide the plugins that no longer have any entries
for (_, view) in &combined_matches {
if view.list.children().is_empty() {
view.row.hide();
}
}
if let Some((row, view)) = combined_matches.get(0) {
view.list.select_row(Some(row));
} }
} }
@@ -808,45 +871,34 @@ fn create_info_box(info: &PluginInfo, hide_icons: bool) -> gtk::Box {
} }
/// Refresh the matches from the plugins /// Refresh the matches from the plugins
fn refresh_matches( fn refresh_matches(input: String, runtime_data: Rc<RefCell<Option<RuntimeData>>>) {
input: String,
runtime_data: Rc<RefCell<Option<RuntimeData>>>,
hide_icons: bool,
) {
for plugin_view in runtime_data.borrow().as_ref().unwrap().plugins.iter() { for plugin_view in runtime_data.borrow().as_ref().unwrap().plugins.iter() {
let id = plugin_view.plugin.get_matches()(input.clone().into()); let id = plugin_view.plugin.get_matches()(input.clone().into());
let plugin_view = plugin_view.clone(); let plugin_view = plugin_view.clone();
let runtime_data_clone = runtime_data.clone(); let runtime_data_clone = runtime_data.clone();
// If the input is empty, skip getting matches and just clear everything out. // If the input is empty, skip getting matches and just clear everything out.
if input.is_empty() { if input.is_empty() {
handle_matches(plugin_view, runtime_data_clone, RVec::new(), hide_icons); handle_matches(
plugin_view,
runtime_data.borrow().as_ref().unwrap(),
RVec::new(),
);
// If a plugin has requested exclusivity, respect it // If a plugin has requested exclusivity, respect it
} else if let Some(exclusive) = &runtime_data.borrow().as_ref().unwrap().exclusive { } else if let Some(exclusive) = &runtime_data.borrow().as_ref().unwrap().exclusive {
if plugin_view.plugin.info() == exclusive.plugin.info() { if plugin_view.plugin.info() == exclusive.plugin.info() {
glib::timeout_add_local(Duration::from_micros(1000), move || { glib::timeout_add_local(Duration::from_micros(1000), move || {
async_match( async_match(plugin_view.clone(), runtime_data_clone.clone(), id)
plugin_view.clone(),
runtime_data_clone.clone(),
id,
hide_icons,
)
}); });
} else { } else {
handle_matches( handle_matches(
plugin_view.clone(), plugin_view.clone(),
runtime_data_clone, runtime_data.borrow().as_ref().unwrap(),
RVec::new(), RVec::new(),
hide_icons,
); );
} }
} else { } else {
glib::timeout_add_local(Duration::from_micros(1000), move || { glib::timeout_add_local(Duration::from_micros(1000), move || {
async_match( async_match(plugin_view.clone(), runtime_data_clone.clone(), id)
plugin_view.clone(),
runtime_data_clone.clone(),
id,
hide_icons,
)
}); });
} }
} }
@@ -857,11 +909,14 @@ fn async_match(
plugin_view: PluginView, plugin_view: PluginView,
runtime_data: Rc<RefCell<Option<RuntimeData>>>, runtime_data: Rc<RefCell<Option<RuntimeData>>>,
id: u64, id: u64,
hide_icons: bool,
) -> glib::Continue { ) -> glib::Continue {
match plugin_view.plugin.poll_matches()(id) { match plugin_view.plugin.poll_matches()(id) {
PollResult::Ready(matches) => { PollResult::Ready(matches) => {
handle_matches(plugin_view, runtime_data, matches, hide_icons); handle_matches(
plugin_view,
runtime_data.borrow().as_ref().unwrap(),
matches,
);
glib::Continue(false) glib::Continue(false)
} }
PollResult::Pending => glib::Continue(true), PollResult::Pending => glib::Continue(true),