Additional Packages
This commit is contained in:
parent
5345f8d6b3
commit
3620df450e
20
data/config/recommended_addons.json
Normal file
20
data/config/recommended_addons.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"recommended_addons": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"title": "update-pikaos-title",
|
||||||
|
"subtitle": "update-pikaos-subtitle",
|
||||||
|
"icon": "pika-system-software-update",
|
||||||
|
"checkpkg": "pika-gameutils-meta",
|
||||||
|
"packages": "pika-gameutils-meta pika-codecs-meta"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "install-media-codec-title",
|
||||||
|
"subtitle": "install-media-codec-subtitle",
|
||||||
|
"icon": "pika-media-tape",
|
||||||
|
"checkpkg": "pika-rocm-meta",
|
||||||
|
"packages": "pika-gameutils-meta pika-codecs-meta"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -10,9 +10,11 @@ use std::rc::Rc;
|
|||||||
// stack crates
|
// stack crates
|
||||||
mod welcome_page;
|
mod welcome_page;
|
||||||
mod setup_steps_page;
|
mod setup_steps_page;
|
||||||
|
mod recommended_addons_page;
|
||||||
|
|
||||||
use welcome_page::welcome_page;
|
use welcome_page::welcome_page;
|
||||||
use setup_steps_page::setup_steps_page;
|
use setup_steps_page::setup_steps_page;
|
||||||
|
use recommended_addons_page::recommended_addons_page;
|
||||||
|
|
||||||
use crate::config::{APP_GITHUB, APP_ICON, APP_ID, VERSION};
|
use crate::config::{APP_GITHUB, APP_ICON, APP_ID, VERSION};
|
||||||
|
|
||||||
@ -81,6 +83,7 @@ pub fn welcome_content_page(window: &adw::ApplicationWindow, content_box: >k::
|
|||||||
let welcome_content_page_stack = gtk::Stack::builder()
|
let welcome_content_page_stack = gtk::Stack::builder()
|
||||||
.vexpand(true)
|
.vexpand(true)
|
||||||
.hexpand(true)
|
.hexpand(true)
|
||||||
|
.transition_type(gtk::StackTransitionType::SlideUpDown)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let welcome_content_page_stack_sidebar = gtk::StackSidebar::builder()
|
let welcome_content_page_stack_sidebar = gtk::StackSidebar::builder()
|
||||||
@ -153,4 +156,5 @@ pub fn welcome_content_page(window: &adw::ApplicationWindow, content_box: >k::
|
|||||||
|
|
||||||
welcome_page(&welcome_content_page_stack, &window_banner, &internet_connected);
|
welcome_page(&welcome_content_page_stack, &window_banner, &internet_connected);
|
||||||
setup_steps_page(&welcome_content_page_stack, &window, &internet_connected);
|
setup_steps_page(&welcome_content_page_stack, &window, &internet_connected);
|
||||||
|
recommended_addons_page(&welcome_content_page_stack, &window, &internet_connected);
|
||||||
}
|
}
|
||||||
|
295
src/welcome_content_page/recommended_addons_page/mod.rs
Normal file
295
src/welcome_content_page/recommended_addons_page/mod.rs
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
// GTK crates
|
||||||
|
use std::io::BufReader;
|
||||||
|
use duct::cmd;
|
||||||
|
use std::io::BufRead;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::fs;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::{thread, time};
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use adw::prelude::*;
|
||||||
|
use adw::*;
|
||||||
|
use glib::*;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[derive(PartialEq, Debug, Eq, Hash, Clone, Ord, PartialOrd, Deserialize)]
|
||||||
|
struct recommended_addons_entry {
|
||||||
|
id: i32,
|
||||||
|
title: String,
|
||||||
|
subtitle: String,
|
||||||
|
icon: String,
|
||||||
|
checkpkg: String,
|
||||||
|
packages: String
|
||||||
|
}
|
||||||
|
|
||||||
|
const ADDON_COMMAND_PROG1: &str = r###"
|
||||||
|
#! /bin/bash
|
||||||
|
set -e
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt update -y -o Dpkg::Options::="--force-confnew"
|
||||||
|
|
||||||
|
"###;
|
||||||
|
|
||||||
|
const ADDON_COMMAND_PROG2: &str = r###"
|
||||||
|
|
||||||
|
apt autoremove -y -o Dpkg::Options::="--force-confnew"
|
||||||
|
"###;
|
||||||
|
fn run_addon_command(
|
||||||
|
log_loop_sender: async_channel::Sender<String>,
|
||||||
|
operation: &str,
|
||||||
|
entry_packages: &str,
|
||||||
|
) -> Result<(), std::boxed::Box<dyn Error + Send + Sync>> {
|
||||||
|
let (pipe_reader, pipe_writer) = os_pipe::pipe()?;
|
||||||
|
let child = cmd!("pkexec", "bash", "-c", ADDON_COMMAND_PROG1.to_owned() + "apt " + operation + " " + &entry_packages + r###" -y -o Dpkg::Options::="--force-confnew""### + ADDON_COMMAND_PROG2)
|
||||||
|
.stderr_to_stdout()
|
||||||
|
.stdout_file(pipe_writer)
|
||||||
|
.start()?;
|
||||||
|
for line in BufReader::new(pipe_reader).lines() {
|
||||||
|
log_loop_sender
|
||||||
|
.send_blocking(line?)
|
||||||
|
.expect("Channel needs to be opened.")
|
||||||
|
}
|
||||||
|
child.wait()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recommended_addons_page(
|
||||||
|
recommended_addons_content_page_stack: >k::Stack,
|
||||||
|
window: &adw::ApplicationWindow,
|
||||||
|
internet_connected: &Rc<RefCell<bool>>
|
||||||
|
) {
|
||||||
|
let internet_connected_status = internet_connected.clone();
|
||||||
|
|
||||||
|
let (internet_loop_sender, internet_loop_receiver) = async_channel::unbounded();
|
||||||
|
let internet_loop_sender = internet_loop_sender.clone();
|
||||||
|
// The long running operation runs now in a separate thread
|
||||||
|
gio::spawn_blocking(move || loop {
|
||||||
|
thread::sleep(time::Duration::from_secs(1));
|
||||||
|
internet_loop_sender
|
||||||
|
.send_blocking(true)
|
||||||
|
.expect("The channel needs to be open.");
|
||||||
|
});
|
||||||
|
|
||||||
|
let recommended_addons_page_box = gtk::Box::builder()
|
||||||
|
.vexpand(true)
|
||||||
|
.hexpand(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let recommended_addons_page_listbox = gtk::ListBox::builder()
|
||||||
|
.margin_top(20)
|
||||||
|
.margin_bottom(20)
|
||||||
|
.margin_start(20)
|
||||||
|
.margin_end(20)
|
||||||
|
.vexpand(true)
|
||||||
|
.hexpand(true)
|
||||||
|
.build();
|
||||||
|
recommended_addons_page_listbox.add_css_class("boxed-list");
|
||||||
|
|
||||||
|
let recommended_addons_page_scroll = gtk::ScrolledWindow::builder()
|
||||||
|
// that puts items vertically
|
||||||
|
.hexpand(true)
|
||||||
|
.vexpand(true)
|
||||||
|
.child(&recommended_addons_page_box)
|
||||||
|
.propagate_natural_width(true)
|
||||||
|
.propagate_natural_height(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let internet_loop_context = MainContext::default();
|
||||||
|
// The main loop executes the asynchronous block
|
||||||
|
internet_loop_context.spawn_local(
|
||||||
|
clone!(@strong internet_connected_status, @weak recommended_addons_page_box => async move {
|
||||||
|
while let Ok(_state) = internet_loop_receiver.recv().await {
|
||||||
|
if *internet_connected_status.borrow_mut() == true {
|
||||||
|
recommended_addons_page_box.set_sensitive(true);
|
||||||
|
} else {
|
||||||
|
recommended_addons_page_box.set_sensitive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut json_array: Vec<recommended_addons_entry> = Vec::new();
|
||||||
|
let json_path = "/home/ward/builds/pkg-pika-welcome/data/config/recommended_addons.json";
|
||||||
|
let json_data = fs::read_to_string(json_path).expect("Unable to read json");
|
||||||
|
let json_data: serde_json::Value = serde_json::from_str(&json_data).expect("JSON format invalid");
|
||||||
|
if let serde_json::Value::Array(recommended_addons) = &json_data["recommended_addons"] {
|
||||||
|
for recommended_addons_entry in recommended_addons {
|
||||||
|
let recommended_addons_entry_struct: recommended_addons_entry = serde_json::from_value(recommended_addons_entry.clone()).unwrap();
|
||||||
|
json_array.push(recommended_addons_entry_struct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for recommended_addons_entry in json_array {
|
||||||
|
let (checkpkg_status_loop_sender, checkpkg_status_loop_receiver) = async_channel::unbounded();
|
||||||
|
let checkpkg_status_loop_sender: async_channel::Sender<bool> = checkpkg_status_loop_sender.clone();
|
||||||
|
|
||||||
|
let (log_loop_sender, log_loop_receiver) = async_channel::unbounded();
|
||||||
|
let log_loop_sender: async_channel::Sender<String> = log_loop_sender.clone();
|
||||||
|
|
||||||
|
let (log_status_loop_sender, log_status_loop_receiver) = async_channel::unbounded();
|
||||||
|
let log_status_loop_sender: async_channel::Sender<bool> =
|
||||||
|
log_status_loop_sender.clone();
|
||||||
|
|
||||||
|
let entry_title = recommended_addons_entry.title;
|
||||||
|
let entry_subtitle = recommended_addons_entry.subtitle;
|
||||||
|
let entry_icon = recommended_addons_entry.icon;
|
||||||
|
let entry_checkpkg = recommended_addons_entry.checkpkg;
|
||||||
|
let entry_packages = recommended_addons_entry.packages;
|
||||||
|
|
||||||
|
gio::spawn_blocking(clone!(@strong checkpkg_status_loop_sender, @strong entry_checkpkg => move || {
|
||||||
|
let checkpkg_command = Command::new("dpkg")
|
||||||
|
.arg("-s")
|
||||||
|
.arg(entry_checkpkg)
|
||||||
|
.output()
|
||||||
|
.expect("failed to execute process");
|
||||||
|
if checkpkg_command.status.success() {
|
||||||
|
checkpkg_status_loop_sender.send_blocking(true).expect("The channel needs to be open.");
|
||||||
|
} else {
|
||||||
|
checkpkg_status_loop_sender.send_blocking(false).expect("The channel needs to be open.");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
let entry_row = adw::ActionRow::builder()
|
||||||
|
.title(t!(&entry_title))
|
||||||
|
.subtitle(t!(&entry_subtitle))
|
||||||
|
.vexpand(true)
|
||||||
|
.hexpand(true)
|
||||||
|
.build();
|
||||||
|
let entry_row_icon = gtk::Image::builder()
|
||||||
|
.icon_name(entry_icon)
|
||||||
|
.pixel_size(80)
|
||||||
|
.vexpand(true)
|
||||||
|
.valign(gtk::Align::Center)
|
||||||
|
.build();
|
||||||
|
let entry_row_button = gtk::Button::builder()
|
||||||
|
.vexpand(true)
|
||||||
|
.valign(gtk::Align::Center)
|
||||||
|
.build();
|
||||||
|
entry_row.add_prefix(&entry_row_icon);
|
||||||
|
entry_row.add_suffix(&entry_row_button);
|
||||||
|
|
||||||
|
let recommended_addons_command_log_terminal_buffer = gtk::TextBuffer::builder().build();
|
||||||
|
|
||||||
|
let recommended_addons_command_log_terminal = gtk::TextView::builder()
|
||||||
|
.vexpand(true)
|
||||||
|
.hexpand(true)
|
||||||
|
.editable(false)
|
||||||
|
.buffer(&recommended_addons_command_log_terminal_buffer)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let recommended_addons_command_log_terminal_scroll = gtk::ScrolledWindow::builder()
|
||||||
|
.width_request(400)
|
||||||
|
.height_request(200)
|
||||||
|
.vexpand(true)
|
||||||
|
.hexpand(true)
|
||||||
|
.child(&recommended_addons_command_log_terminal)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let recommended_addons_command_dialog = adw::MessageDialog::builder()
|
||||||
|
.transient_for(window)
|
||||||
|
.hide_on_close(true)
|
||||||
|
.extra_child(&recommended_addons_command_log_terminal_scroll)
|
||||||
|
.width_request(400)
|
||||||
|
.height_request(200)
|
||||||
|
.heading(t!("recommended_addons_command_dialog_heading"))
|
||||||
|
.build();
|
||||||
|
recommended_addons_command_dialog.add_response(
|
||||||
|
"recommended_addons_command_dialog_ok",
|
||||||
|
&t!("recommended_addons_command_dialog_ok_label").to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let checkpkg_status_loop_context = MainContext::default();
|
||||||
|
// The main loop executes the asynchronous block
|
||||||
|
checkpkg_status_loop_context.spawn_local(clone!(@weak entry_row_button, @strong checkpkg_status_loop_receiver => async move {
|
||||||
|
while let Ok(state) = checkpkg_status_loop_receiver.recv().await {
|
||||||
|
if state == false {
|
||||||
|
entry_row_button.remove_css_class("destructive-action");
|
||||||
|
entry_row_button.set_label(&t!("entry_row_button_install").to_string());
|
||||||
|
entry_row_button.add_css_class("suggested-action");
|
||||||
|
entry_row_button.set_widget_name("false")
|
||||||
|
} else {
|
||||||
|
entry_row_button.remove_css_class("suggested-action");
|
||||||
|
entry_row_button.set_label(&t!("entry_row_button_remove").to_string());
|
||||||
|
entry_row_button.add_css_class("destructive-action");
|
||||||
|
entry_row_button.set_widget_name("true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
//
|
||||||
|
let log_loop_context = MainContext::default();
|
||||||
|
// The main loop executes the asynchronous block
|
||||||
|
log_loop_context.spawn_local(clone!(@weak recommended_addons_command_log_terminal_buffer, @weak recommended_addons_command_dialog, @strong log_loop_receiver => async move {
|
||||||
|
while let Ok(state) = log_loop_receiver.recv().await {
|
||||||
|
recommended_addons_command_log_terminal_buffer.insert(&mut recommended_addons_command_log_terminal_buffer.end_iter(), &("\n".to_string() + &state))
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
let log_status_loop_context = MainContext::default();
|
||||||
|
// The main loop executes the asynchronous block
|
||||||
|
log_status_loop_context.spawn_local(clone!(@weak recommended_addons_command_dialog, @strong log_status_loop_receiver => async move {
|
||||||
|
while let Ok(state) = log_status_loop_receiver.recv().await {
|
||||||
|
if state == true {
|
||||||
|
recommended_addons_command_dialog.set_response_enabled("recommended_addons_command_dialog_ok", true);
|
||||||
|
recommended_addons_command_dialog.set_body(&t!("recommended_addons_command_dialog_success_true"));
|
||||||
|
} else {
|
||||||
|
recommended_addons_command_dialog.set_response_enabled("recommended_addons_command_dialog_ok", true);
|
||||||
|
recommended_addons_command_dialog.set_body(&t!("recommended_addons_command_dialog_success_false"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
//
|
||||||
|
recommended_addons_command_log_terminal_buffer.connect_changed(clone!(@weak recommended_addons_command_log_terminal, @weak recommended_addons_command_log_terminal_buffer,@weak recommended_addons_command_log_terminal_scroll => move |_|{
|
||||||
|
if recommended_addons_command_log_terminal_scroll.vadjustment().upper() - recommended_addons_command_log_terminal_scroll.vadjustment().value() > 100.0 {
|
||||||
|
recommended_addons_command_log_terminal_scroll.vadjustment().set_value(recommended_addons_command_log_terminal_scroll.vadjustment().upper())
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
entry_row_button.connect_clicked(clone!(@strong entry_packages, @weak entry_row_button, @weak window => move |_| {
|
||||||
|
recommended_addons_command_log_terminal_buffer.delete(&mut recommended_addons_command_log_terminal_buffer.bounds().0, &mut recommended_addons_command_log_terminal_buffer.bounds().1);
|
||||||
|
recommended_addons_command_dialog.set_response_enabled("recommended_addons_command_dialog_ok", false);
|
||||||
|
recommended_addons_command_dialog.set_body("");
|
||||||
|
recommended_addons_command_dialog.present();
|
||||||
|
if &entry_row_button.widget_name() == "true" {
|
||||||
|
gio::spawn_blocking(clone!(@strong log_loop_sender, @strong log_status_loop_sender, @strong entry_packages => move || {
|
||||||
|
let command = run_addon_command(log_loop_sender, "remove", &entry_packages);
|
||||||
|
match command {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("Status: Addon Command Successful");
|
||||||
|
log_status_loop_sender.send_blocking(true).expect("The channel needs to be open.");
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
println!("Status: Addon Command Failed");
|
||||||
|
log_status_loop_sender.send_blocking(false).expect("The channel needs to be open.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
gio::spawn_blocking(clone!(@strong log_loop_sender, @strong log_status_loop_sender, @strong entry_packages => move || {
|
||||||
|
let command = run_addon_command(log_loop_sender, "install", &entry_packages);
|
||||||
|
match command {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("Status: Addon Command Successful");
|
||||||
|
log_status_loop_sender.send_blocking(true).expect("The channel needs to be open.");
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
println!("Status: Addon Command Failed");
|
||||||
|
log_status_loop_sender.send_blocking(false).expect("The channel needs to be open.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
recommended_addons_page_listbox.append(&entry_row)
|
||||||
|
}
|
||||||
|
|
||||||
|
recommended_addons_page_box.append(&recommended_addons_page_listbox);
|
||||||
|
|
||||||
|
recommended_addons_content_page_stack.add_titled(&recommended_addons_page_scroll, Some("recommended_addons_page"), &t!("recommended_addons_page_title").to_string());
|
||||||
|
}
|
@ -9,10 +9,10 @@ use std::cell::RefCell;
|
|||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use adw::*;
|
use adw::*;
|
||||||
use glib::*;
|
use glib::*;
|
||||||
use std::fs::File;
|
|
||||||
|
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
#[derive(PartialEq, Debug, Eq, Hash, Clone, Ord, PartialOrd, Deserialize)]
|
#[derive(PartialEq, Debug, Eq, Hash, Clone, Ord, PartialOrd, Deserialize)]
|
||||||
struct Setup_Steps_Entry {
|
struct setup_steps_entry {
|
||||||
id: i32,
|
id: i32,
|
||||||
title: String,
|
title: String,
|
||||||
subtitle: String,
|
subtitle: String,
|
||||||
@ -76,13 +76,13 @@ pub fn setup_steps_page(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut json_array: Vec<Setup_Steps_Entry> = Vec::new();
|
let mut json_array: Vec<setup_steps_entry> = Vec::new();
|
||||||
let json_path = "/home/ward/builds/pkg-pika-welcome/data/config/setup_steps.json";
|
let json_path = "/home/ward/builds/pkg-pika-welcome/data/config/setup_steps.json";
|
||||||
let json_data = fs::read_to_string(json_path).expect("Unable to read json");
|
let json_data = fs::read_to_string(json_path).expect("Unable to read json");
|
||||||
let json_data: serde_json::Value = serde_json::from_str(&json_data).expect("JSON format invalid");
|
let json_data: serde_json::Value = serde_json::from_str(&json_data).expect("JSON format invalid");
|
||||||
if let serde_json::Value::Array(setup_steps) = &json_data["setup_steps"] {
|
if let serde_json::Value::Array(setup_steps) = &json_data["setup_steps"] {
|
||||||
for setup_steps_entry in setup_steps {
|
for setup_steps_entry in setup_steps {
|
||||||
let setup_steps_entry_struct: Setup_Steps_Entry = serde_json::from_value(setup_steps_entry.clone()).unwrap();
|
let setup_steps_entry_struct: setup_steps_entry = serde_json::from_value(setup_steps_entry.clone()).unwrap();
|
||||||
json_array.push(setup_steps_entry_struct);
|
json_array.push(setup_steps_entry_struct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user