Added documentation and minor code optimisation
This commit is contained in:
parent
3ddff9f710
commit
8ebff77089
6 changed files with 334 additions and 264 deletions
6
CHANGELOG.md
Normal file
6
CHANGELOG.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
## 8/12/24
|
||||||
|
- Removed `on_shutdown` function from `src/main.rs`, as it was uselessly dropping memory that was automatically dropped right after the function ended.
|
||||||
|
- Finished the documentation for `src/main.rs`, `src/packages/installed.rs`, `src/packages/browse.rs`, and `src/packages/mod.rs`
|
||||||
|
- Renamed `show_installed_package` to `show_package`, and merged it into `src/packages/mod.rs` instead of having copies in two files.
|
||||||
|
- Added this changelog.
|
||||||
|
- Updated `inst.sh` so that it no longer copies `style.css` to the `/usr/share/oreon-system-manager` directory.
|
1
inst.sh
1
inst.sh
|
@ -1,2 +1 @@
|
||||||
sudo mkdir -p /usr/share/oreon/oreon-system-manager && sudo cp src/style.css /usr/share/oreon/oreon-system-manager
|
|
||||||
cargo build --release && sudo cp target/release/oreon-system-manager /usr/bin
|
cargo build --release && sudo cp target/release/oreon-system-manager /usr/bin
|
||||||
|
|
66
src/main.rs
66
src/main.rs
|
@ -10,46 +10,81 @@ pub mod packages;
|
||||||
|
|
||||||
const APP_ID: &str = "org.oreonproject.SystemManager";
|
const APP_ID: &str = "org.oreonproject.SystemManager";
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initializes the application.
|
||||||
|
|
||||||
|
## Returns
|
||||||
|
Returns a `glib::ExitCode`, as given by the `app.run()` function.
|
||||||
|
*/
|
||||||
fn main() -> glib::ExitCode {
|
fn main() -> glib::ExitCode {
|
||||||
let app = Application::builder().application_id(APP_ID).build();
|
let app = Application::builder().application_id(APP_ID).build();
|
||||||
|
|
||||||
app.connect_startup(on_startup);
|
app.connect_startup(on_startup);
|
||||||
app.connect_activate(on_activate);
|
app.connect_activate(on_activate);
|
||||||
app.connect_shutdown(on_shutdown);
|
|
||||||
|
|
||||||
app.run()
|
app.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The function called when the application really starts.
|
||||||
|
|
||||||
|
## Params
|
||||||
|
`app`: A reference to the application built in `main`
|
||||||
|
*/
|
||||||
fn on_activate(app: &Application) {
|
fn on_activate(app: &Application) {
|
||||||
|
// Create a new window with the title "System Manager"
|
||||||
let main_window = ApplicationWindow::new(app);
|
let main_window = ApplicationWindow::new(app);
|
||||||
main_window.set_title(Some("System Manager"));
|
main_window.set_title(Some("System Manager"));
|
||||||
main_window.set_default_size(800, 600);
|
main_window.set_default_size(800, 600);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Create a stack, which is a container that can have
|
||||||
|
several different "pages", each being its own box.
|
||||||
|
*/
|
||||||
let main_stack = Stack::builder().css_classes(["main-stack"]).build();
|
let main_stack = Stack::builder().css_classes(["main-stack"]).build();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Create a top bar for switching the page of the
|
||||||
|
`main_stack` object.
|
||||||
|
*/
|
||||||
let switcher = StackSwitcher::builder()
|
let switcher = StackSwitcher::builder()
|
||||||
.orientation(Horizontal)
|
.orientation(Horizontal)
|
||||||
.stack(&main_stack)
|
.stack(&main_stack)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
// A box with the stack and switcher as children.
|
||||||
let main_box = gtk::Box::builder()
|
let main_box = gtk::Box::builder()
|
||||||
.orientation(Vertical)
|
.orientation(Vertical)
|
||||||
.css_classes(["main-box"])
|
.css_classes(["main-box"])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add all of the pages to the stack, each defined
|
||||||
|
in it's own module.
|
||||||
|
*/
|
||||||
main_stack.add_titled(&packages::packages(), Some("packages"), "Packages");
|
main_stack.add_titled(&packages::packages(), Some("packages"), "Packages");
|
||||||
main_stack.add_titled(&drivers(), Some("drivers"), "Drivers");
|
main_stack.add_titled(&drivers(), Some("drivers"), "Drivers");
|
||||||
main_stack.add_titled(&containers(), Some("containers"), "Containers");
|
main_stack.add_titled(&containers(), Some("containers"), "Containers");
|
||||||
main_stack.add_titled(&virtualization(), Some("virtualization"), "Virtualization");
|
main_stack.add_titled(&virtualization(), Some("virtualization"), "Virtualization");
|
||||||
|
|
||||||
|
// Append the switcher and stack to the main box
|
||||||
main_box.append(&switcher);
|
main_box.append(&switcher);
|
||||||
main_box.append(&main_stack);
|
main_box.append(&main_stack);
|
||||||
|
|
||||||
|
// Set main box as the window's child, and display it.
|
||||||
main_window.set_child(Some(&main_box));
|
main_window.set_child(Some(&main_box));
|
||||||
main_window.present();
|
main_window.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Function for the drivers page of the stack.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
- Move to it's own file/folder
|
||||||
|
- Implement functionality for driver management
|
||||||
|
*/
|
||||||
fn drivers() -> gtk::Box {
|
fn drivers() -> gtk::Box {
|
||||||
|
// Create the box as a page for the stack.
|
||||||
let drivers_box = gtk::Box::builder()
|
let drivers_box = gtk::Box::builder()
|
||||||
.orientation(Vertical)
|
.orientation(Vertical)
|
||||||
.css_classes(["packages-box"])
|
.css_classes(["packages-box"])
|
||||||
|
@ -58,6 +93,7 @@ fn drivers() -> gtk::Box {
|
||||||
.hexpand(true)
|
.hexpand(true)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
// A search bar for the drivers.
|
||||||
let drivers_search = SearchBar::builder()
|
let drivers_search = SearchBar::builder()
|
||||||
.halign(Align::Fill)
|
.halign(Align::Fill)
|
||||||
.valign(Align::Start)
|
.valign(Align::Start)
|
||||||
|
@ -76,33 +112,52 @@ fn drivers() -> gtk::Box {
|
||||||
|
|
||||||
drivers_search.connect_entry(&drivers_entry);
|
drivers_search.connect_entry(&drivers_entry);
|
||||||
|
|
||||||
|
// Append the search bar to the box and return it.
|
||||||
drivers_box.append(&drivers_search);
|
drivers_box.append(&drivers_search);
|
||||||
drivers_box.append(&drivers_entry);
|
drivers_box.append(&drivers_entry);
|
||||||
drivers_box
|
drivers_box
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Page for managing Docker containers and images.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
- Move to it's own file/folder
|
||||||
|
- Add the following pages: "Containers", "Images", "Browse"
|
||||||
|
*/
|
||||||
fn containers() -> gtk::Box {
|
fn containers() -> gtk::Box {
|
||||||
|
// Initialize the box that will be used as a page.
|
||||||
let containers_box = gtk::Box::builder()
|
let containers_box = gtk::Box::builder()
|
||||||
.orientation(Horizontal)
|
.orientation(Horizontal)
|
||||||
.css_classes(["containers-box"])
|
.css_classes(["containers-box"])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
// Append a new label as a title and return the box.
|
||||||
containers_box.append(>k::Label::new(Some("Containers")));
|
containers_box.append(>k::Label::new(Some("Containers")));
|
||||||
|
|
||||||
containers_box
|
containers_box
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
For managing virtual machines.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
- Move to its own file
|
||||||
|
- Possibly merge with the "Containers" page
|
||||||
|
*/
|
||||||
fn virtualization() -> gtk::Box {
|
fn virtualization() -> gtk::Box {
|
||||||
|
// Initialize the box to be used as a page
|
||||||
let virtualization_box = gtk::Box::builder()
|
let virtualization_box = gtk::Box::builder()
|
||||||
.orientation(Horizontal)
|
.orientation(Horizontal)
|
||||||
.css_classes(["virtualization-box"])
|
.css_classes(["virtualization-box"])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
// Append the title label and return the box.
|
||||||
virtualization_box.append(>k::Label::new(Some("Virtualization")));
|
virtualization_box.append(>k::Label::new(Some("Virtualization")));
|
||||||
|
|
||||||
virtualization_box
|
virtualization_box
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Function to link `style.css` to the application.
|
||||||
fn on_startup(_: &Application) {
|
fn on_startup(_: &Application) {
|
||||||
let css_provider = gtk::CssProvider::new();
|
let css_provider = gtk::CssProvider::new();
|
||||||
css_provider.load_from_data(include_str!("style.css"));
|
css_provider.load_from_data(include_str!("style.css"));
|
||||||
|
@ -112,10 +167,3 @@ fn on_startup(_: &Application) {
|
||||||
gtk::STYLE_PROVIDER_PRIORITY_USER,
|
gtk::STYLE_PROVIDER_PRIORITY_USER,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_shutdown(_: &Application) {
|
|
||||||
unsafe {
|
|
||||||
std::mem::drop(packages::browse::PACKAGES_LIST.take());
|
|
||||||
std::mem::drop(packages::installed::PACKAGES_LIST.take());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use gtk::{
|
use gtk::{
|
||||||
prelude::{BoxExt, ButtonExt, EditableExt, GtkWindowExt},
|
prelude::{BoxExt, ButtonExt, EditableExt},
|
||||||
Button, Label, PositionType, Window,
|
Button, PositionType,
|
||||||
};
|
};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ pub static mut PKG_LIST_INDEX: usize = 0;
|
||||||
pub static mut PKG_LISTBOX: Option<ScrolledWindow> = None;
|
pub static mut PKG_LISTBOX: Option<ScrolledWindow> = None;
|
||||||
|
|
||||||
pub fn browse() -> gtk::Box {
|
pub fn browse() -> gtk::Box {
|
||||||
|
// The main box that encompasses everything else
|
||||||
let packages_box = gtk::Box::builder()
|
let packages_box = gtk::Box::builder()
|
||||||
.orientation(gtk::Orientation::Vertical)
|
.orientation(gtk::Orientation::Vertical)
|
||||||
.css_classes(["packages-box"])
|
.css_classes(["packages-box"])
|
||||||
|
@ -20,6 +21,7 @@ pub fn browse() -> gtk::Box {
|
||||||
.hexpand(true)
|
.hexpand(true)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
// Search bar
|
||||||
let pkgs_search = SearchBar::builder()
|
let pkgs_search = SearchBar::builder()
|
||||||
.halign(Align::Fill)
|
.halign(Align::Fill)
|
||||||
.valign(Align::Start)
|
.valign(Align::Start)
|
||||||
|
@ -36,8 +38,16 @@ pub fn browse() -> gtk::Box {
|
||||||
.hexpand(true)
|
.hexpand(true)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
/*
|
||||||
|
List of names to skip when searching, this is to remove the
|
||||||
|
labels "Available Packages", "Last refreshed at ..." and "Installed Packages"
|
||||||
|
*/
|
||||||
let skip_list = vec!["available", "installed", "last"];
|
let skip_list = vec!["available", "installed", "last"];
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initialize the packages list, needs to be a static
|
||||||
|
mutable variable to allow usage with GTK functions.
|
||||||
|
*/
|
||||||
unsafe {
|
unsafe {
|
||||||
PACKAGES_LIST = Some(
|
PACKAGES_LIST = Some(
|
||||||
ListBox::builder()
|
ListBox::builder()
|
||||||
|
@ -60,6 +70,12 @@ pub fn browse() -> gtk::Box {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
/*
|
||||||
|
Gets the list of packages, which also needs to be static
|
||||||
|
mutable for the same reason as PACKAGES_LIST, and remove
|
||||||
|
the description and architecture details, and also filter
|
||||||
|
out the lines that aren't actually packages.
|
||||||
|
*/
|
||||||
PKG_LIST = pkg_list
|
PKG_LIST = pkg_list
|
||||||
.lines()
|
.lines()
|
||||||
.map(|x| {
|
.map(|x| {
|
||||||
|
@ -80,6 +96,12 @@ pub fn browse() -> gtk::Box {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add buttons for the first 50 packages in the list, to
|
||||||
|
keep the RAM usage to a minimum and not have every single
|
||||||
|
package and a button for it in the memory at the same time,
|
||||||
|
which has caused as much as 1.4GB of RAM usage during tests.
|
||||||
|
*/
|
||||||
for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].to_vec() {
|
for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].to_vec() {
|
||||||
PKG_LIST_INDEX += 1;
|
PKG_LIST_INDEX += 1;
|
||||||
let pkg_btn = Button::builder()
|
let pkg_btn = Button::builder()
|
||||||
|
@ -88,13 +110,15 @@ pub fn browse() -> gtk::Box {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
pkg_btn.connect_clicked(|x| {
|
pkg_btn.connect_clicked(|x| {
|
||||||
show_installed_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
|
super::show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
|
||||||
});
|
});
|
||||||
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn)
|
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
/*
|
||||||
|
Create a scrollable area for the package list so that it
|
||||||
|
doesn't just go off the screen and hide all of the list.
|
||||||
|
*/
|
||||||
PKG_LISTBOX = Some(
|
PKG_LISTBOX = Some(
|
||||||
ScrolledWindow::builder()
|
ScrolledWindow::builder()
|
||||||
.css_classes(["package-list-scrollable"])
|
.css_classes(["package-list-scrollable"])
|
||||||
|
@ -105,6 +129,10 @@ pub fn browse() -> gtk::Box {
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Make it so that on reaching the end of the list it adds
|
||||||
|
buttons for 50 more packages.
|
||||||
|
*/
|
||||||
PKG_LISTBOX
|
PKG_LISTBOX
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -117,7 +145,7 @@ pub fn browse() -> gtk::Box {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
pkg_btn.connect_clicked(|x| {
|
pkg_btn.connect_clicked(|x| {
|
||||||
show_installed_package(
|
super::show_package(
|
||||||
x.label().map(|x| x.to_string()).unwrap_or("".to_owned()),
|
x.label().map(|x| x.to_string()).unwrap_or("".to_owned()),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -129,15 +157,19 @@ pub fn browse() -> gtk::Box {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Run the `search` function when the search is changed.
|
||||||
|
TODO: Make this asynchronous.
|
||||||
|
*/
|
||||||
pkgs_entry.connect_search_changed(move |x| {
|
pkgs_entry.connect_search_changed(move |x| {
|
||||||
let f = x.clone();
|
let f = x.clone();
|
||||||
|
|
||||||
// TODO: Possibly refactor to use `dnf search` instead of a filter function.
|
|
||||||
unsafe { search(&f, &skip_list) }
|
unsafe { search(&f, &skip_list) }
|
||||||
});
|
});
|
||||||
|
|
||||||
pkgs_search.connect_entry(&pkgs_entry);
|
pkgs_search.connect_entry(&pkgs_entry);
|
||||||
|
|
||||||
|
// Add all of the parts to the box and return it.
|
||||||
packages_box.append(&pkgs_search);
|
packages_box.append(&pkgs_search);
|
||||||
packages_box.append(&pkgs_entry);
|
packages_box.append(&pkgs_entry);
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -151,104 +183,16 @@ pub fn browse() -> gtk::Box {
|
||||||
packages_box
|
packages_box
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_installed_package(pkg: String) {
|
/**
|
||||||
if pkg.is_empty() {
|
Function that takes a search entry and updates
|
||||||
return;
|
the package lists accordingly, skipping the values
|
||||||
}
|
stated in `skip_list`
|
||||||
|
*/
|
||||||
let pkg_window = Window::builder().title(&pkg).build();
|
|
||||||
let pkg_box = gtk::Box::builder()
|
|
||||||
.orientation(gtk::Orientation::Vertical)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let info = String::from_utf8(
|
|
||||||
Command::new("dnf")
|
|
||||||
.args(["info", pkg.as_str()])
|
|
||||||
.output()
|
|
||||||
.unwrap()
|
|
||||||
.stdout,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let info = if info.contains("Installed Packages") {
|
|
||||||
let a = info.split_once("Installed Packages").unwrap().1.trim();
|
|
||||||
a.split_once("Available Packages")
|
|
||||||
.unwrap_or((a, ""))
|
|
||||||
.0
|
|
||||||
.trim()
|
|
||||||
} else {
|
|
||||||
info.split_once("Available Packages").unwrap().0.trim()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut version = "1.0.0";
|
|
||||||
let mut size = "Unknown";
|
|
||||||
let mut license = "Unknown";
|
|
||||||
|
|
||||||
for line in info.lines() {
|
|
||||||
if let Some(e) = line.trim().strip_prefix("Version") {
|
|
||||||
version = e.split_once(":").unwrap().1.trim();
|
|
||||||
} else if let Some(e) = line.trim().strip_prefix("Size") {
|
|
||||||
size = e.split_once(":").unwrap().1.trim();
|
|
||||||
} else if let Some(e) = line.trim().strip_prefix("License") {
|
|
||||||
license = e.split_once(":").unwrap().1.trim();
|
|
||||||
}
|
|
||||||
if line.trim().starts_with("Description") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let description = info
|
|
||||||
.split_once("Description")
|
|
||||||
.unwrap()
|
|
||||||
.1
|
|
||||||
.split_once(":")
|
|
||||||
.unwrap()
|
|
||||||
.1
|
|
||||||
.trim()
|
|
||||||
.lines()
|
|
||||||
.map(|x| x.split_once(":").unwrap_or(("", x)).1.trim().to_owned() + "\n")
|
|
||||||
.collect::<String>();
|
|
||||||
|
|
||||||
pkg_box.append(
|
|
||||||
&Label::builder()
|
|
||||||
.label("Description: ".to_owned() + description.as_str())
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
pkg_box.append(
|
|
||||||
&Label::builder()
|
|
||||||
.label("Version: ".to_owned() + version)
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
pkg_box.append(&Label::builder().label("Size: ".to_owned() + size).build());
|
|
||||||
pkg_box.append(
|
|
||||||
&Label::builder()
|
|
||||||
.label("License: ".to_owned() + license)
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let buttons_box = gtk::Box::builder()
|
|
||||||
.orientation(gtk::Orientation::Horizontal)
|
|
||||||
.css_classes(["buttons-box"])
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let install_btn = Button::builder().label("Install").build();
|
|
||||||
|
|
||||||
install_btn.connect_clicked(move |_| {
|
|
||||||
Command::new("pkexec")
|
|
||||||
.args(["dnf", "install", pkg.as_str()])
|
|
||||||
.spawn()
|
|
||||||
.unwrap();
|
|
||||||
});
|
|
||||||
|
|
||||||
buttons_box.append(&install_btn);
|
|
||||||
|
|
||||||
pkg_box.append(&buttons_box);
|
|
||||||
|
|
||||||
pkg_window.set_child(Some(&pkg_box));
|
|
||||||
pkg_window.present();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
|
unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
|
||||||
|
/*
|
||||||
|
Get a list of installed packages from DNF
|
||||||
|
TODO: Make this asynchronous
|
||||||
|
*/
|
||||||
let pkg_list = String::from_utf8(
|
let pkg_list = String::from_utf8(
|
||||||
Command::new("dnf")
|
Command::new("dnf")
|
||||||
.args(["search", f.text().to_string().as_str()])
|
.args(["search", f.text().to_string().as_str()])
|
||||||
|
@ -258,6 +202,12 @@ unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reinitialize PKG_LIST with an extra predicate
|
||||||
|
in the filter closure to ensure that the list
|
||||||
|
of packages contains only ones that match the
|
||||||
|
query
|
||||||
|
*/
|
||||||
PKG_LIST = pkg_list
|
PKG_LIST = pkg_list
|
||||||
.lines()
|
.lines()
|
||||||
.map(|x| {
|
.map(|x| {
|
||||||
|
@ -278,8 +228,17 @@ unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Drop the inner value of PACKAGES_LIST to prevent it
|
||||||
|
from persisting, since it has a static lifetime.
|
||||||
|
|
||||||
|
TODO: Check if this is necessary
|
||||||
|
*/
|
||||||
std::mem::drop(PACKAGES_LIST.take());
|
std::mem::drop(PACKAGES_LIST.take());
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reinitialize PACKAGES_LIST as a new ListBox.
|
||||||
|
*/
|
||||||
PACKAGES_LIST = Some(
|
PACKAGES_LIST = Some(
|
||||||
ListBox::builder()
|
ListBox::builder()
|
||||||
.css_classes(["packages-list"])
|
.css_classes(["packages-list"])
|
||||||
|
@ -289,8 +248,14 @@ unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Reset the list index so that it starts at 0 in the package list
|
||||||
PKG_LIST_INDEX = 0;
|
PKG_LIST_INDEX = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Loop through the package list from 0 until
|
||||||
|
either 50 or the length of the list, whichever
|
||||||
|
is less, and make buttons in the list for each
|
||||||
|
*/
|
||||||
for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].to_vec() {
|
for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].to_vec() {
|
||||||
PKG_LIST_INDEX += 1;
|
PKG_LIST_INDEX += 1;
|
||||||
let pkg_btn = Button::builder()
|
let pkg_btn = Button::builder()
|
||||||
|
@ -299,11 +264,15 @@ unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
pkg_btn.connect_clicked(|x| {
|
pkg_btn.connect_clicked(|x| {
|
||||||
show_installed_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
|
super::show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
|
||||||
});
|
});
|
||||||
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn)
|
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reinitialise PKG_LISTBOX since the value
|
||||||
|
it points to has now been changed.
|
||||||
|
*/
|
||||||
PKG_LISTBOX
|
PKG_LISTBOX
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
use gtk::{
|
use gtk::{
|
||||||
prelude::{BoxExt, ButtonExt, EditableExt, GtkWindowExt},
|
prelude::{BoxExt, ButtonExt, EditableExt},
|
||||||
Button, Label, PositionType, Window,
|
Button, PositionType,
|
||||||
};
|
};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry};
|
use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry};
|
||||||
|
|
||||||
|
use super::show_package;
|
||||||
|
|
||||||
pub static mut PACKAGES_LIST: Option<ListBox> = None;
|
pub static mut PACKAGES_LIST: Option<ListBox> = None;
|
||||||
pub static mut PKG_LIST: Vec<String> = vec![];
|
pub static mut PKG_LIST: Vec<String> = vec![];
|
||||||
pub static mut PKG_LIST_INDEX: usize = 0;
|
pub static mut PKG_LIST_INDEX: usize = 0;
|
||||||
pub static mut PKG_LISTBOX: Option<ScrolledWindow> = None;
|
pub static mut PKG_LISTBOX: Option<ScrolledWindow> = None;
|
||||||
|
|
||||||
/// Function to initialise a box that contains a
|
/**
|
||||||
/// list of installed packages.
|
Function to initialise a box that contains a
|
||||||
|
list of installed packages.
|
||||||
|
*/
|
||||||
pub fn installed() -> gtk::Box {
|
pub fn installed() -> gtk::Box {
|
||||||
// The main box that encompasses everything else
|
// The main box that encompasses everything else
|
||||||
let packages_box = gtk::Box::builder()
|
let packages_box = gtk::Box::builder()
|
||||||
|
@ -112,7 +116,7 @@ pub fn installed() -> gtk::Box {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
pkg_btn.connect_clicked(|x| {
|
pkg_btn.connect_clicked(|x| {
|
||||||
show_installed_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
|
show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
|
||||||
});
|
});
|
||||||
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn)
|
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn)
|
||||||
}
|
}
|
||||||
|
@ -147,9 +151,7 @@ pub fn installed() -> gtk::Box {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
pkg_btn.connect_clicked(|x| {
|
pkg_btn.connect_clicked(|x| {
|
||||||
show_installed_package(
|
show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
|
||||||
x.label().map(|x| x.to_string()).unwrap_or("".to_owned()),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn);
|
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn);
|
||||||
|
|
||||||
|
@ -186,147 +188,15 @@ pub fn installed() -> gtk::Box {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Function called when a package is clicked on in the list,
|
Function that takes a search entry and updates
|
||||||
creates a new window over the top that displays the details
|
the package lists accordingly, skipping the values
|
||||||
of the package clicked on.
|
stated in `skip_list`
|
||||||
|
|
||||||
# Params
|
|
||||||
`pkg`: The name of the package that was clicked on.
|
|
||||||
*/
|
*/
|
||||||
fn show_installed_package(pkg: String) {
|
|
||||||
/*
|
|
||||||
Prevent creating empty windows and having failed
|
|
||||||
`dnf` commands.
|
|
||||||
*/
|
|
||||||
if pkg.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a window for the package details.
|
|
||||||
let pkg_window = Window::builder().title(&pkg).build();
|
|
||||||
let pkg_box = gtk::Box::builder()
|
|
||||||
.orientation(gtk::Orientation::Vertical)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// Use `dnf` to get information about the package.
|
|
||||||
let info = String::from_utf8(
|
|
||||||
Command::new("dnf")
|
|
||||||
.args(["info", pkg.as_str()])
|
|
||||||
.output()
|
|
||||||
.unwrap()
|
|
||||||
.stdout,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
/*
|
|
||||||
Get the correct info for whether the package
|
|
||||||
is installed or not and use the data from
|
|
||||||
the installed one where available to be more
|
|
||||||
accurate.
|
|
||||||
|
|
||||||
TODO: Make this differentiate between them
|
|
||||||
and add a remove button instead of an
|
|
||||||
install button on this menu.
|
|
||||||
*/
|
|
||||||
let info = if info.contains("Installed Packages") {
|
|
||||||
let a = info.split_once("Installed Packages").unwrap().1.trim();
|
|
||||||
a.split_once("Available Packages")
|
|
||||||
.unwrap_or((a, ""))
|
|
||||||
.0
|
|
||||||
.trim()
|
|
||||||
} else {
|
|
||||||
info.split_once("Available Packages").unwrap().0.trim()
|
|
||||||
};
|
|
||||||
|
|
||||||
// The details of the package, with default values just in case.
|
|
||||||
let mut version = "1.0.0";
|
|
||||||
let mut size = "Unknown";
|
|
||||||
let mut license = "Unknown";
|
|
||||||
|
|
||||||
/*
|
|
||||||
Sort through each of the lines and add the
|
|
||||||
values where necessary, breaking upon reaching
|
|
||||||
the description, since that can be a multi-line
|
|
||||||
value.
|
|
||||||
*/
|
|
||||||
for line in info.lines() {
|
|
||||||
if let Some(e) = line.trim().strip_prefix("Version") {
|
|
||||||
version = e.split_once(":").unwrap().1.trim();
|
|
||||||
} else if let Some(e) = line.trim().strip_prefix("Size") {
|
|
||||||
size = e.split_once(":").unwrap().1.trim();
|
|
||||||
} else if let Some(e) = line.trim().strip_prefix("License") {
|
|
||||||
license = e.split_once(":").unwrap().1.trim();
|
|
||||||
}
|
|
||||||
if line.trim().starts_with("Description") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Get the description of the package, removing
|
|
||||||
the colons and whitespace at the start of each
|
|
||||||
line.
|
|
||||||
*/
|
|
||||||
let description = info
|
|
||||||
.split_once("Description")
|
|
||||||
.unwrap()
|
|
||||||
.1
|
|
||||||
.split_once(":")
|
|
||||||
.unwrap()
|
|
||||||
.1
|
|
||||||
.trim()
|
|
||||||
.lines()
|
|
||||||
.map(|x| x.split_once(":").unwrap_or(("", x)).1.trim().to_owned() + "\n")
|
|
||||||
.collect::<String>();
|
|
||||||
|
|
||||||
/*
|
|
||||||
Append all of the parts to the box.
|
|
||||||
*/
|
|
||||||
pkg_box.append(
|
|
||||||
&Label::builder()
|
|
||||||
.label("Description: ".to_owned() + description.as_str())
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
pkg_box.append(
|
|
||||||
&Label::builder()
|
|
||||||
.label("Version: ".to_owned() + version)
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
pkg_box.append(&Label::builder().label("Size: ".to_owned() + size).build());
|
|
||||||
pkg_box.append(
|
|
||||||
&Label::builder()
|
|
||||||
.label("License: ".to_owned() + license)
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let buttons_box = gtk::Box::builder()
|
|
||||||
.orientation(gtk::Orientation::Horizontal)
|
|
||||||
.css_classes(["buttons-box"])
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let install_btn = Button::builder().label("Install").build();
|
|
||||||
|
|
||||||
/*
|
|
||||||
Make it so that when the install button
|
|
||||||
is clicked the package is installed.
|
|
||||||
*/
|
|
||||||
install_btn.connect_clicked(move |_| {
|
|
||||||
Command::new("pkexec")
|
|
||||||
.args(["dnf", "install", pkg.as_str()])
|
|
||||||
.spawn()
|
|
||||||
.unwrap();
|
|
||||||
});
|
|
||||||
|
|
||||||
buttons_box.append(&install_btn);
|
|
||||||
|
|
||||||
pkg_box.append(&buttons_box);
|
|
||||||
|
|
||||||
// Append the box to the window and show it.
|
|
||||||
pkg_window.set_child(Some(&pkg_box));
|
|
||||||
pkg_window.present();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
|
unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
|
||||||
|
/*
|
||||||
|
Get a list of installed packages from DNF
|
||||||
|
TODO: Make this asynchronous
|
||||||
|
*/
|
||||||
let pkg_list = String::from_utf8(
|
let pkg_list = String::from_utf8(
|
||||||
Command::new("dnf")
|
Command::new("dnf")
|
||||||
.args(["list", "--installed"])
|
.args(["list", "--installed"])
|
||||||
|
@ -336,6 +206,12 @@ unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reinitialize PKG_LIST with an extra predicate
|
||||||
|
in the filter closure to ensure that the list
|
||||||
|
of packages contains only ones that match the
|
||||||
|
query
|
||||||
|
*/
|
||||||
PKG_LIST = pkg_list
|
PKG_LIST = pkg_list
|
||||||
.lines()
|
.lines()
|
||||||
.map(|x| {
|
.map(|x| {
|
||||||
|
@ -358,8 +234,17 @@ unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Drop the inner value of PACKAGES_LIST to prevent it
|
||||||
|
from persisting, since it has a static lifetime.
|
||||||
|
|
||||||
|
TODO: Check if this is necessary
|
||||||
|
*/
|
||||||
std::mem::drop(PACKAGES_LIST.take());
|
std::mem::drop(PACKAGES_LIST.take());
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reinitialize PACKAGES_LIST as a new ListBox.
|
||||||
|
*/
|
||||||
PACKAGES_LIST = Some(
|
PACKAGES_LIST = Some(
|
||||||
ListBox::builder()
|
ListBox::builder()
|
||||||
.css_classes(["packages-list"])
|
.css_classes(["packages-list"])
|
||||||
|
@ -369,8 +254,14 @@ unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Reset the list index so that it starts at 0 in the package list
|
||||||
PKG_LIST_INDEX = 0;
|
PKG_LIST_INDEX = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Loop through the package list from 0 until
|
||||||
|
either 50 or the length of the list, whichever
|
||||||
|
is less, and make buttons in the list for each
|
||||||
|
*/
|
||||||
for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].to_vec() {
|
for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].to_vec() {
|
||||||
PKG_LIST_INDEX += 1;
|
PKG_LIST_INDEX += 1;
|
||||||
let pkg_btn = Button::builder()
|
let pkg_btn = Button::builder()
|
||||||
|
@ -379,11 +270,15 @@ unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
pkg_btn.connect_clicked(|x| {
|
pkg_btn.connect_clicked(|x| {
|
||||||
show_installed_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
|
show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
|
||||||
});
|
});
|
||||||
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn)
|
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reinitialise PKG_LISTBOX since the value
|
||||||
|
it points to has now been changed.
|
||||||
|
*/
|
||||||
PKG_LISTBOX
|
PKG_LISTBOX
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -1,9 +1,20 @@
|
||||||
use gtk::{prelude::BoxExt, Align, Stack, StackSidebar};
|
use std::process::Command;
|
||||||
|
|
||||||
|
use gtk::{
|
||||||
|
prelude::{BoxExt, ButtonExt, GtkWindowExt},
|
||||||
|
Align, Button, Label, Stack, StackSidebar, Window,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod browse;
|
pub mod browse;
|
||||||
pub mod installed;
|
pub mod installed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Provides a box with a stack showing the installed,
|
||||||
|
available, and updatable packages
|
||||||
|
*/
|
||||||
pub fn packages() -> gtk::Box {
|
pub fn packages() -> gtk::Box {
|
||||||
|
// Stack to allow switching between "Installed",
|
||||||
|
// "Available", and "Updates"
|
||||||
let packages_stack = Stack::builder()
|
let packages_stack = Stack::builder()
|
||||||
.css_classes(["packages-stack"])
|
.css_classes(["packages-stack"])
|
||||||
.valign(Align::Fill)
|
.valign(Align::Fill)
|
||||||
|
@ -19,6 +30,7 @@ pub fn packages() -> gtk::Box {
|
||||||
|
|
||||||
packages_stack.add_titled(&browse::browse(), Some("browse"), "Browse");
|
packages_stack.add_titled(&browse::browse(), Some("browse"), "Browse");
|
||||||
packages_stack.add_titled(&installed::installed(), Some("installed"), "Installed");
|
packages_stack.add_titled(&installed::installed(), Some("installed"), "Installed");
|
||||||
|
// TODO: Add "Updates" section
|
||||||
|
|
||||||
let packages_box = gtk::Box::builder()
|
let packages_box = gtk::Box::builder()
|
||||||
.orientation(gtk::Orientation::Horizontal)
|
.orientation(gtk::Orientation::Horizontal)
|
||||||
|
@ -28,3 +40,144 @@ pub fn packages() -> gtk::Box {
|
||||||
packages_box.append(&packages_stack);
|
packages_box.append(&packages_stack);
|
||||||
packages_box
|
packages_box
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Function called when a package is clicked on in the list,
|
||||||
|
creates a new window over the top that displays the details
|
||||||
|
of the package clicked on.
|
||||||
|
|
||||||
|
# Params
|
||||||
|
`pkg`: The name of the package that was clicked on.
|
||||||
|
*/
|
||||||
|
fn show_package(pkg: String) {
|
||||||
|
/*
|
||||||
|
Prevent creating empty windows and having failed
|
||||||
|
`dnf` commands.
|
||||||
|
*/
|
||||||
|
if pkg.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a window for the package details.
|
||||||
|
let pkg_window = Window::builder().title(&pkg).build();
|
||||||
|
let pkg_box = gtk::Box::builder()
|
||||||
|
.orientation(gtk::Orientation::Vertical)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Use `dnf` to get information about the package.
|
||||||
|
let info = String::from_utf8(
|
||||||
|
Command::new("dnf")
|
||||||
|
.args(["info", pkg.as_str()])
|
||||||
|
.output()
|
||||||
|
.unwrap()
|
||||||
|
.stdout,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the correct info for whether the package
|
||||||
|
is installed or not and use the data from
|
||||||
|
the installed one where available to be more
|
||||||
|
accurate.
|
||||||
|
|
||||||
|
TODO: Make this differentiate between them
|
||||||
|
and add a remove button instead of an
|
||||||
|
install button on this menu.
|
||||||
|
*/
|
||||||
|
let info = if info.contains("Installed Packages") {
|
||||||
|
let a = info.split_once("Installed Packages").unwrap().1.trim();
|
||||||
|
a.split_once("Available Packages")
|
||||||
|
.unwrap_or((a, ""))
|
||||||
|
.0
|
||||||
|
.trim()
|
||||||
|
} else {
|
||||||
|
info.split_once("Available Packages").unwrap().0.trim()
|
||||||
|
};
|
||||||
|
|
||||||
|
// The details of the package, with default values just in case.
|
||||||
|
let mut version = "1.0.0";
|
||||||
|
let mut size = "Unknown";
|
||||||
|
let mut license = "Unknown";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sort through each of the lines and add the
|
||||||
|
values where necessary, breaking upon reaching
|
||||||
|
the description, since that can be a multi-line
|
||||||
|
value.
|
||||||
|
*/
|
||||||
|
for line in info.lines() {
|
||||||
|
if let Some(e) = line.trim().strip_prefix("Version") {
|
||||||
|
version = e.split_once(":").unwrap().1.trim();
|
||||||
|
} else if let Some(e) = line.trim().strip_prefix("Size") {
|
||||||
|
size = e.split_once(":").unwrap().1.trim();
|
||||||
|
} else if let Some(e) = line.trim().strip_prefix("License") {
|
||||||
|
license = e.split_once(":").unwrap().1.trim();
|
||||||
|
}
|
||||||
|
if line.trim().starts_with("Description") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the description of the package, removing
|
||||||
|
the colons and whitespace at the start of each
|
||||||
|
line.
|
||||||
|
*/
|
||||||
|
let description = info
|
||||||
|
.split_once("Description")
|
||||||
|
.unwrap()
|
||||||
|
.1
|
||||||
|
.split_once(":")
|
||||||
|
.unwrap()
|
||||||
|
.1
|
||||||
|
.trim()
|
||||||
|
.lines()
|
||||||
|
.map(|x| x.split_once(":").unwrap_or(("", x)).1.trim().to_owned() + "\n")
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Append all of the parts to the box.
|
||||||
|
*/
|
||||||
|
pkg_box.append(
|
||||||
|
&Label::builder()
|
||||||
|
.label("Description: ".to_owned() + description.as_str())
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
pkg_box.append(
|
||||||
|
&Label::builder()
|
||||||
|
.label("Version: ".to_owned() + version)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
pkg_box.append(&Label::builder().label("Size: ".to_owned() + size).build());
|
||||||
|
pkg_box.append(
|
||||||
|
&Label::builder()
|
||||||
|
.label("License: ".to_owned() + license)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let buttons_box = gtk::Box::builder()
|
||||||
|
.orientation(gtk::Orientation::Horizontal)
|
||||||
|
.css_classes(["buttons-box"])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let install_btn = Button::builder().label("Install").build();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Make it so that when the install button
|
||||||
|
is clicked the package is installed.
|
||||||
|
*/
|
||||||
|
install_btn.connect_clicked(move |_| {
|
||||||
|
Command::new("pkexec")
|
||||||
|
.args(["dnf", "install", pkg.as_str()])
|
||||||
|
.spawn()
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
buttons_box.append(&install_btn);
|
||||||
|
|
||||||
|
pkg_box.append(&buttons_box);
|
||||||
|
|
||||||
|
// Append the box to the window and show it.
|
||||||
|
pkg_window.set_child(Some(&pkg_box));
|
||||||
|
pkg_window.present();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue