diff --git a/data/config/credits.json b/data/config/credits.json new file mode 100644 index 0000000..a765ac2 --- /dev/null +++ b/data/config/credits.json @@ -0,0 +1,81 @@ +{ + "developers": [ + { + "id": 0, + "dev": "Welcome App's coding:\nCosmo & GlouriousEggroll" + }, + { + "id": 1, + "dev": "Welcome App's UI:\nPizzaLovingNerd - risiOS BDFL" + }, + { + "id": 2, + "dev": "Webapps manager:\nThe Linux Mint team" + }, + { + "id": 3, + "dev": "Standalone gamescope session:\nThe ChimeraOS Team & KyleGospo" + }, + { + "id": 4, + "dev": "Repositories, and firmware Manager, \nand The tiling in gnome-shell with pop-shell: \nThe Pop!OS Team" + }, + { + "id": 5, + "dev": "Hybrid GPU Controls:\nasus-linux" + }, { + "id": 6, + "dev": "ISO testing VMs:\nAkiToasterUwU" + }, + { + "id": 7, + "dev": "APX package manager:\nThe Vanilla OS Team" + }, { + "id": 8, + "dev": "Wallpapers:\nNeytirix & Colorman" + }, + { + "id": 9, + "dev": "Logo artist:\nnesper8 (willsmanic) & MattsCreative & Piaf_Jaune" + }, + { + "id": 10, + "dev": "Kernel Base:\nCachyOS team (ptr1337)" + }, + { + "id": 11, + "dev": "Themeing:\nVinceliuice & Papirus" + }, + { + "id": 12, + "dev": "Encryption Support:\nSkimmingDeath" + }, + { + "id": 13, + "dev": "Graphics Drivers:\nKisak & Oibaf" + }, + { + "id": 14, + "dev": "pikman package manager, Package Builder Hardware, \nServer Hosting:\nFerreo" + }, { + "id": 15, + "dev": "Website:\nFerreo & BL4Z3" + }, + { + "id": 16, + "dev": "Anti Snap:\nThe Xtradeb team" + }, + { + "id": 17, + "dev": "Technical Advisors:\nFerreo & ptr1337 & Zukureneno" + }, + { + "id": 18, + "dev": "Audio Artist:\n4lk4" + }, + { + "id": 19, + "dev": "Financial Contributors:\nnesper8 (willsmanic) & FlibbyJibbit & Zukureneno" + } + ] +} \ No newline at end of file diff --git a/src/welcome_content_page/community_page/mod.rs b/src/welcome_content_page/community_page/mod.rs new file mode 100644 index 0000000..720fc5e --- /dev/null +++ b/src/welcome_content_page/community_page/mod.rs @@ -0,0 +1,157 @@ +// GTK crates +use duct::cmd; +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::*; + +#[allow(non_camel_case_types)] +#[derive(PartialEq, Debug, Eq, Hash, Clone, Ord, PartialOrd, Deserialize)] +struct setup_steps_entry { + id: i32, + title: String, + subtitle: String, + icon: String, + button: String, + command: String +} + +pub fn setup_steps_page( + setup_steps_content_page_stack: >k::Stack, + window: &adw::ApplicationWindow, + internet_connected: &Rc> +) { + 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 setup_steps_page_box = gtk::Box::builder() + .vexpand(true) + .hexpand(true) + .build(); + + let setup_steps_page_listbox = gtk::ListBox::builder() + .margin_top(20) + .margin_bottom(20) + .margin_start(20) + .margin_end(20) + .vexpand(true) + .hexpand(true) + .build(); + setup_steps_page_listbox.add_css_class("boxed-list"); + + let setup_steps_page_scroll = gtk::ScrolledWindow::builder() + // that puts items vertically + .hexpand(true) + .vexpand(true) + .child(&setup_steps_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 setup_steps_page_box => async move { + while let Ok(_state) = internet_loop_receiver.recv().await { + if *internet_connected_status.borrow_mut() == true { + setup_steps_page_box.set_sensitive(true); + } else { + setup_steps_page_box.set_sensitive(false); + } + } + }), + ); + + let mut json_array: Vec = Vec::new(); + 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: 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"] { + for setup_steps_entry in setup_steps { + let setup_steps_entry_struct: setup_steps_entry = serde_json::from_value(setup_steps_entry.clone()).unwrap(); + json_array.push(setup_steps_entry_struct); + } + } + + for setup_steps_entry in json_array { + let (entry_command_status_loop_sender, entry_command_status_loop_receiver) = async_channel::unbounded(); + let entry_command_status_loop_sender: async_channel::Sender = entry_command_status_loop_sender.clone(); + + let entry_title = setup_steps_entry.title; + let entry_subtitle = setup_steps_entry.subtitle; + let entry_icon = setup_steps_entry.icon; + let entry_button = setup_steps_entry.button; + let entry_command = setup_steps_entry.command; + 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() + .label(t!(&entry_button)) + .vexpand(true) + .valign(gtk::Align::Center) + .build(); + entry_row.add_prefix(&entry_row_icon); + entry_row.add_suffix(&entry_row_button); + + entry_row_button.connect_clicked(clone!(@strong entry_command, @weak window => move |_| { + gio::spawn_blocking(clone!(@strong entry_command_status_loop_sender, @strong entry_command => move || { + if Path::new("/tmp/pika-welcome-exec.sh").exists() { + fs::remove_file("/tmp/pika-welcome-exec.sh").expect("Bad permissions on /tmp/pika-installer-gtk4-target-manual.txt"); + } + fs::write("/tmp/pika-welcome-exec.sh", "#! /bin/bash\nset -e\n".to_owned() + &entry_command).expect("Unable to write file"); + let _ = cmd!("chmod", "+x", "/tmp/pika-welcome-exec.sh").read(); + let command = cmd!("/tmp/pika-welcome-exec.sh").run(); + if command.is_err() { + entry_command_status_loop_sender.send_blocking(false).expect("The channel needs to be open."); + } else { + entry_command_status_loop_sender.send_blocking(true).expect("The channel needs to be open."); + } + })); + })); + + let cmd_err_dialog = adw::MessageDialog::builder() + .body(t!("cmd_err_dialog_body")) + .heading(t!("cmd_err_dialog_heading")) + .transient_for(window) + .build(); + cmd_err_dialog.add_response("cmd_err_dialog_ok", &t!("cmd_err_dialog_ok_label").to_string()); + + let entry_command_status_loop_context = MainContext::default(); + // The main loop executes the asynchronous block + entry_command_status_loop_context.spawn_local(clone!(@weak cmd_err_dialog, @strong entry_command_status_loop_receiver => async move { + while let Ok(state) = entry_command_status_loop_receiver.recv().await { + if state == false { + cmd_err_dialog.present(); + } + } + })); + setup_steps_page_listbox.append(&entry_row) + } + + setup_steps_page_box.append(&setup_steps_page_listbox); + + setup_steps_content_page_stack.add_titled(&setup_steps_page_scroll, Some("setup_steps_page"), &t!("setup_steps_page_title").to_string()); +} \ No newline at end of file diff --git a/src/welcome_content_page/contribute_page/mod.rs b/src/welcome_content_page/contribute_page/mod.rs new file mode 100644 index 0000000..720fc5e --- /dev/null +++ b/src/welcome_content_page/contribute_page/mod.rs @@ -0,0 +1,157 @@ +// GTK crates +use duct::cmd; +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::*; + +#[allow(non_camel_case_types)] +#[derive(PartialEq, Debug, Eq, Hash, Clone, Ord, PartialOrd, Deserialize)] +struct setup_steps_entry { + id: i32, + title: String, + subtitle: String, + icon: String, + button: String, + command: String +} + +pub fn setup_steps_page( + setup_steps_content_page_stack: >k::Stack, + window: &adw::ApplicationWindow, + internet_connected: &Rc> +) { + 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 setup_steps_page_box = gtk::Box::builder() + .vexpand(true) + .hexpand(true) + .build(); + + let setup_steps_page_listbox = gtk::ListBox::builder() + .margin_top(20) + .margin_bottom(20) + .margin_start(20) + .margin_end(20) + .vexpand(true) + .hexpand(true) + .build(); + setup_steps_page_listbox.add_css_class("boxed-list"); + + let setup_steps_page_scroll = gtk::ScrolledWindow::builder() + // that puts items vertically + .hexpand(true) + .vexpand(true) + .child(&setup_steps_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 setup_steps_page_box => async move { + while let Ok(_state) = internet_loop_receiver.recv().await { + if *internet_connected_status.borrow_mut() == true { + setup_steps_page_box.set_sensitive(true); + } else { + setup_steps_page_box.set_sensitive(false); + } + } + }), + ); + + let mut json_array: Vec = Vec::new(); + 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: 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"] { + for setup_steps_entry in setup_steps { + let setup_steps_entry_struct: setup_steps_entry = serde_json::from_value(setup_steps_entry.clone()).unwrap(); + json_array.push(setup_steps_entry_struct); + } + } + + for setup_steps_entry in json_array { + let (entry_command_status_loop_sender, entry_command_status_loop_receiver) = async_channel::unbounded(); + let entry_command_status_loop_sender: async_channel::Sender = entry_command_status_loop_sender.clone(); + + let entry_title = setup_steps_entry.title; + let entry_subtitle = setup_steps_entry.subtitle; + let entry_icon = setup_steps_entry.icon; + let entry_button = setup_steps_entry.button; + let entry_command = setup_steps_entry.command; + 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() + .label(t!(&entry_button)) + .vexpand(true) + .valign(gtk::Align::Center) + .build(); + entry_row.add_prefix(&entry_row_icon); + entry_row.add_suffix(&entry_row_button); + + entry_row_button.connect_clicked(clone!(@strong entry_command, @weak window => move |_| { + gio::spawn_blocking(clone!(@strong entry_command_status_loop_sender, @strong entry_command => move || { + if Path::new("/tmp/pika-welcome-exec.sh").exists() { + fs::remove_file("/tmp/pika-welcome-exec.sh").expect("Bad permissions on /tmp/pika-installer-gtk4-target-manual.txt"); + } + fs::write("/tmp/pika-welcome-exec.sh", "#! /bin/bash\nset -e\n".to_owned() + &entry_command).expect("Unable to write file"); + let _ = cmd!("chmod", "+x", "/tmp/pika-welcome-exec.sh").read(); + let command = cmd!("/tmp/pika-welcome-exec.sh").run(); + if command.is_err() { + entry_command_status_loop_sender.send_blocking(false).expect("The channel needs to be open."); + } else { + entry_command_status_loop_sender.send_blocking(true).expect("The channel needs to be open."); + } + })); + })); + + let cmd_err_dialog = adw::MessageDialog::builder() + .body(t!("cmd_err_dialog_body")) + .heading(t!("cmd_err_dialog_heading")) + .transient_for(window) + .build(); + cmd_err_dialog.add_response("cmd_err_dialog_ok", &t!("cmd_err_dialog_ok_label").to_string()); + + let entry_command_status_loop_context = MainContext::default(); + // The main loop executes the asynchronous block + entry_command_status_loop_context.spawn_local(clone!(@weak cmd_err_dialog, @strong entry_command_status_loop_receiver => async move { + while let Ok(state) = entry_command_status_loop_receiver.recv().await { + if state == false { + cmd_err_dialog.present(); + } + } + })); + setup_steps_page_listbox.append(&entry_row) + } + + setup_steps_page_box.append(&setup_steps_page_listbox); + + setup_steps_content_page_stack.add_titled(&setup_steps_page_scroll, Some("setup_steps_page"), &t!("setup_steps_page_title").to_string()); +} \ No newline at end of file diff --git a/src/welcome_content_page/look_and_feel_page/mod.rs b/src/welcome_content_page/look_and_feel_page/mod.rs new file mode 100644 index 0000000..720fc5e --- /dev/null +++ b/src/welcome_content_page/look_and_feel_page/mod.rs @@ -0,0 +1,157 @@ +// GTK crates +use duct::cmd; +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::*; + +#[allow(non_camel_case_types)] +#[derive(PartialEq, Debug, Eq, Hash, Clone, Ord, PartialOrd, Deserialize)] +struct setup_steps_entry { + id: i32, + title: String, + subtitle: String, + icon: String, + button: String, + command: String +} + +pub fn setup_steps_page( + setup_steps_content_page_stack: >k::Stack, + window: &adw::ApplicationWindow, + internet_connected: &Rc> +) { + 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 setup_steps_page_box = gtk::Box::builder() + .vexpand(true) + .hexpand(true) + .build(); + + let setup_steps_page_listbox = gtk::ListBox::builder() + .margin_top(20) + .margin_bottom(20) + .margin_start(20) + .margin_end(20) + .vexpand(true) + .hexpand(true) + .build(); + setup_steps_page_listbox.add_css_class("boxed-list"); + + let setup_steps_page_scroll = gtk::ScrolledWindow::builder() + // that puts items vertically + .hexpand(true) + .vexpand(true) + .child(&setup_steps_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 setup_steps_page_box => async move { + while let Ok(_state) = internet_loop_receiver.recv().await { + if *internet_connected_status.borrow_mut() == true { + setup_steps_page_box.set_sensitive(true); + } else { + setup_steps_page_box.set_sensitive(false); + } + } + }), + ); + + let mut json_array: Vec = Vec::new(); + 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: 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"] { + for setup_steps_entry in setup_steps { + let setup_steps_entry_struct: setup_steps_entry = serde_json::from_value(setup_steps_entry.clone()).unwrap(); + json_array.push(setup_steps_entry_struct); + } + } + + for setup_steps_entry in json_array { + let (entry_command_status_loop_sender, entry_command_status_loop_receiver) = async_channel::unbounded(); + let entry_command_status_loop_sender: async_channel::Sender = entry_command_status_loop_sender.clone(); + + let entry_title = setup_steps_entry.title; + let entry_subtitle = setup_steps_entry.subtitle; + let entry_icon = setup_steps_entry.icon; + let entry_button = setup_steps_entry.button; + let entry_command = setup_steps_entry.command; + 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() + .label(t!(&entry_button)) + .vexpand(true) + .valign(gtk::Align::Center) + .build(); + entry_row.add_prefix(&entry_row_icon); + entry_row.add_suffix(&entry_row_button); + + entry_row_button.connect_clicked(clone!(@strong entry_command, @weak window => move |_| { + gio::spawn_blocking(clone!(@strong entry_command_status_loop_sender, @strong entry_command => move || { + if Path::new("/tmp/pika-welcome-exec.sh").exists() { + fs::remove_file("/tmp/pika-welcome-exec.sh").expect("Bad permissions on /tmp/pika-installer-gtk4-target-manual.txt"); + } + fs::write("/tmp/pika-welcome-exec.sh", "#! /bin/bash\nset -e\n".to_owned() + &entry_command).expect("Unable to write file"); + let _ = cmd!("chmod", "+x", "/tmp/pika-welcome-exec.sh").read(); + let command = cmd!("/tmp/pika-welcome-exec.sh").run(); + if command.is_err() { + entry_command_status_loop_sender.send_blocking(false).expect("The channel needs to be open."); + } else { + entry_command_status_loop_sender.send_blocking(true).expect("The channel needs to be open."); + } + })); + })); + + let cmd_err_dialog = adw::MessageDialog::builder() + .body(t!("cmd_err_dialog_body")) + .heading(t!("cmd_err_dialog_heading")) + .transient_for(window) + .build(); + cmd_err_dialog.add_response("cmd_err_dialog_ok", &t!("cmd_err_dialog_ok_label").to_string()); + + let entry_command_status_loop_context = MainContext::default(); + // The main loop executes the asynchronous block + entry_command_status_loop_context.spawn_local(clone!(@weak cmd_err_dialog, @strong entry_command_status_loop_receiver => async move { + while let Ok(state) = entry_command_status_loop_receiver.recv().await { + if state == false { + cmd_err_dialog.present(); + } + } + })); + setup_steps_page_listbox.append(&entry_row) + } + + setup_steps_page_box.append(&setup_steps_page_listbox); + + setup_steps_content_page_stack.add_titled(&setup_steps_page_scroll, Some("setup_steps_page"), &t!("setup_steps_page_title").to_string()); +} \ No newline at end of file diff --git a/src/welcome_content_page/mod.rs b/src/welcome_content_page/mod.rs index 84b6edc..9cc32f6 100644 --- a/src/welcome_content_page/mod.rs +++ b/src/welcome_content_page/mod.rs @@ -62,6 +62,16 @@ pub fn welcome_content_page(window: &adw::ApplicationWindow, content_box: >k:: .icon_name("dialog-information-symbolic") .build(); + let mut json_array: Vec = Vec::new(); + let json_path = "/home/ward/builds/pkg-pika-welcome/data/config/credits.json"; + let json_data = std::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(developers) = &json_data["developers"] { + for developer in developers { + json_array.push(developer["dev"].as_str().to_owned().unwrap().into()) + } + } + let credits_window = adw::AboutWindow::builder() .application_icon(APP_ICON) .application_name(t!("app_name")) @@ -69,6 +79,7 @@ pub fn welcome_content_page(window: &adw::ApplicationWindow, content_box: >k:: .version(VERSION) .hide_on_close(true) .developer_name(t!("app_dev")) + .developers(json_array) .issue_url(APP_GITHUB.to_owned() + "/issues") .build(); diff --git a/src/welcome_content_page/troubleshoot_page/mod.rs b/src/welcome_content_page/troubleshoot_page/mod.rs new file mode 100644 index 0000000..720fc5e --- /dev/null +++ b/src/welcome_content_page/troubleshoot_page/mod.rs @@ -0,0 +1,157 @@ +// GTK crates +use duct::cmd; +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::*; + +#[allow(non_camel_case_types)] +#[derive(PartialEq, Debug, Eq, Hash, Clone, Ord, PartialOrd, Deserialize)] +struct setup_steps_entry { + id: i32, + title: String, + subtitle: String, + icon: String, + button: String, + command: String +} + +pub fn setup_steps_page( + setup_steps_content_page_stack: >k::Stack, + window: &adw::ApplicationWindow, + internet_connected: &Rc> +) { + 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 setup_steps_page_box = gtk::Box::builder() + .vexpand(true) + .hexpand(true) + .build(); + + let setup_steps_page_listbox = gtk::ListBox::builder() + .margin_top(20) + .margin_bottom(20) + .margin_start(20) + .margin_end(20) + .vexpand(true) + .hexpand(true) + .build(); + setup_steps_page_listbox.add_css_class("boxed-list"); + + let setup_steps_page_scroll = gtk::ScrolledWindow::builder() + // that puts items vertically + .hexpand(true) + .vexpand(true) + .child(&setup_steps_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 setup_steps_page_box => async move { + while let Ok(_state) = internet_loop_receiver.recv().await { + if *internet_connected_status.borrow_mut() == true { + setup_steps_page_box.set_sensitive(true); + } else { + setup_steps_page_box.set_sensitive(false); + } + } + }), + ); + + let mut json_array: Vec = Vec::new(); + 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: 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"] { + for setup_steps_entry in setup_steps { + let setup_steps_entry_struct: setup_steps_entry = serde_json::from_value(setup_steps_entry.clone()).unwrap(); + json_array.push(setup_steps_entry_struct); + } + } + + for setup_steps_entry in json_array { + let (entry_command_status_loop_sender, entry_command_status_loop_receiver) = async_channel::unbounded(); + let entry_command_status_loop_sender: async_channel::Sender = entry_command_status_loop_sender.clone(); + + let entry_title = setup_steps_entry.title; + let entry_subtitle = setup_steps_entry.subtitle; + let entry_icon = setup_steps_entry.icon; + let entry_button = setup_steps_entry.button; + let entry_command = setup_steps_entry.command; + 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() + .label(t!(&entry_button)) + .vexpand(true) + .valign(gtk::Align::Center) + .build(); + entry_row.add_prefix(&entry_row_icon); + entry_row.add_suffix(&entry_row_button); + + entry_row_button.connect_clicked(clone!(@strong entry_command, @weak window => move |_| { + gio::spawn_blocking(clone!(@strong entry_command_status_loop_sender, @strong entry_command => move || { + if Path::new("/tmp/pika-welcome-exec.sh").exists() { + fs::remove_file("/tmp/pika-welcome-exec.sh").expect("Bad permissions on /tmp/pika-installer-gtk4-target-manual.txt"); + } + fs::write("/tmp/pika-welcome-exec.sh", "#! /bin/bash\nset -e\n".to_owned() + &entry_command).expect("Unable to write file"); + let _ = cmd!("chmod", "+x", "/tmp/pika-welcome-exec.sh").read(); + let command = cmd!("/tmp/pika-welcome-exec.sh").run(); + if command.is_err() { + entry_command_status_loop_sender.send_blocking(false).expect("The channel needs to be open."); + } else { + entry_command_status_loop_sender.send_blocking(true).expect("The channel needs to be open."); + } + })); + })); + + let cmd_err_dialog = adw::MessageDialog::builder() + .body(t!("cmd_err_dialog_body")) + .heading(t!("cmd_err_dialog_heading")) + .transient_for(window) + .build(); + cmd_err_dialog.add_response("cmd_err_dialog_ok", &t!("cmd_err_dialog_ok_label").to_string()); + + let entry_command_status_loop_context = MainContext::default(); + // The main loop executes the asynchronous block + entry_command_status_loop_context.spawn_local(clone!(@weak cmd_err_dialog, @strong entry_command_status_loop_receiver => async move { + while let Ok(state) = entry_command_status_loop_receiver.recv().await { + if state == false { + cmd_err_dialog.present(); + } + } + })); + setup_steps_page_listbox.append(&entry_row) + } + + setup_steps_page_box.append(&setup_steps_page_listbox); + + setup_steps_content_page_stack.add_titled(&setup_steps_page_scroll, Some("setup_steps_page"), &t!("setup_steps_page_title").to_string()); +} \ No newline at end of file