Setup page code
This commit is contained in:
parent
356381a83c
commit
5345f8d6b3
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -854,6 +854,8 @@ dependencies = [
|
|||||||
"os_pipe",
|
"os_pipe",
|
||||||
"regex",
|
"regex",
|
||||||
"rust-i18n",
|
"rust-i18n",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"single-instance",
|
"single-instance",
|
||||||
"user",
|
"user",
|
||||||
"users",
|
"users",
|
||||||
@ -1051,18 +1053,18 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.196"
|
version = "1.0.197"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.196"
|
version = "1.0.197"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -17,3 +17,5 @@ user = "0.1.1"
|
|||||||
users = "0.11.0"
|
users = "0.11.0"
|
||||||
rust-i18n = "3.0.1"
|
rust-i18n = "3.0.1"
|
||||||
single-instance = "0.3.3"
|
single-instance = "0.3.3"
|
||||||
|
serde_json = "1.0.114"
|
||||||
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
20
data/config/setup_steps.json
Normal file
20
data/config/setup_steps.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"setup_steps": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"title": "update-pikaos-title",
|
||||||
|
"subtitle": "update-pikaos-subtitle",
|
||||||
|
"icon": "pika-system-software-update",
|
||||||
|
"button": "update-pikaos-button-label",
|
||||||
|
"command": "echo update"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "install-media-codec-title",
|
||||||
|
"subtitle": "install-media-codec-subtitle",
|
||||||
|
"icon": "pika-media-tape",
|
||||||
|
"button": "install-media-codec-button-label",
|
||||||
|
"command": "echo codec"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
// GTK crates
|
// GTK crates
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use adw::*;
|
use adw::*;
|
||||||
use glib::*;
|
|
||||||
use gtk::Orientation;
|
use gtk::Orientation;
|
||||||
/// Use all gtk4 libraries (gtk4 -> gtk because cargo)
|
/// Use all gtk4 libraries (gtk4 -> gtk because cargo)
|
||||||
/// Use all libadwaita libraries (libadwaita -> adw because cargo)
|
/// Use all libadwaita libraries (libadwaita -> adw because cargo)
|
||||||
|
@ -9,8 +9,6 @@ use gdk::Display;
|
|||||||
/// Use all libadwaita libraries (libadwaita -> adw because cargo)
|
/// Use all libadwaita libraries (libadwaita -> adw because cargo)
|
||||||
use gtk::*;
|
use gtk::*;
|
||||||
use single_instance::SingleInstance;
|
use single_instance::SingleInstance;
|
||||||
use glib::*;
|
|
||||||
use glib::prelude::*;
|
|
||||||
|
|
||||||
use std::boxed::Box;
|
use std::boxed::Box;
|
||||||
use users::*;
|
use users::*;
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
use adw::ffi::AdwApplicationWindow;
|
use adw::{gio,};
|
||||||
use adw::{gio, glib};
|
|
||||||
use adw::prelude::SettingsExt;
|
use adw::prelude::SettingsExt;
|
||||||
use adw::subclass::window;
|
|
||||||
use gtk::prelude::GtkWindowExt;
|
use gtk::prelude::GtkWindowExt;
|
||||||
use glib::*;
|
|
||||||
use glib::prelude::*;
|
|
||||||
pub fn save_window_size(window: &adw::ApplicationWindow, glib_settings: &gio::Settings) {
|
pub fn save_window_size(window: &adw::ApplicationWindow, glib_settings: &gio::Settings) {
|
||||||
let size = window.default_size();
|
let size = window.default_size();
|
||||||
|
|
||||||
|
@ -9,7 +9,10 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
// stack crates
|
// stack crates
|
||||||
mod welcome_page;
|
mod welcome_page;
|
||||||
|
mod setup_steps_page;
|
||||||
|
|
||||||
use welcome_page::welcome_page;
|
use welcome_page::welcome_page;
|
||||||
|
use setup_steps_page::setup_steps_page;
|
||||||
|
|
||||||
use crate::config::{APP_GITHUB, APP_ICON, APP_ID, VERSION};
|
use crate::config::{APP_GITHUB, APP_ICON, APP_ID, VERSION};
|
||||||
|
|
||||||
@ -47,6 +50,10 @@ pub fn welcome_content_page(window: &adw::ApplicationWindow, content_box: >k::
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let window_banner = adw::Banner::builder()
|
||||||
|
.revealed(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
let window_title_bar = gtk::HeaderBar::builder().show_title_buttons(true).build();
|
let window_title_bar = gtk::HeaderBar::builder().show_title_buttons(true).build();
|
||||||
|
|
||||||
let credits_button = gtk::Button::builder()
|
let credits_button = gtk::Button::builder()
|
||||||
@ -65,6 +72,12 @@ pub fn welcome_content_page(window: &adw::ApplicationWindow, content_box: >k::
|
|||||||
|
|
||||||
content_box.append(&window_title_bar);
|
content_box.append(&window_title_bar);
|
||||||
|
|
||||||
|
let welcome_content_page_stack_box = gtk::Box::builder()
|
||||||
|
.vexpand(true)
|
||||||
|
.hexpand(true)
|
||||||
|
.orientation(gtk::Orientation::Vertical)
|
||||||
|
.build();
|
||||||
|
|
||||||
let welcome_content_page_stack = gtk::Stack::builder()
|
let welcome_content_page_stack = gtk::Stack::builder()
|
||||||
.vexpand(true)
|
.vexpand(true)
|
||||||
.hexpand(true)
|
.hexpand(true)
|
||||||
@ -79,7 +92,7 @@ pub fn welcome_content_page(window: &adw::ApplicationWindow, content_box: >k::
|
|||||||
let welcome_content_page_split_view = adw::OverlaySplitView::builder()
|
let welcome_content_page_split_view = adw::OverlaySplitView::builder()
|
||||||
.vexpand(true)
|
.vexpand(true)
|
||||||
.hexpand(true)
|
.hexpand(true)
|
||||||
.content(&welcome_content_page_stack)
|
.content(&welcome_content_page_stack_box)
|
||||||
.sidebar(&welcome_content_page_stack_sidebar)
|
.sidebar(&welcome_content_page_stack_sidebar)
|
||||||
.max_sidebar_width(300.0)
|
.max_sidebar_width(300.0)
|
||||||
.min_sidebar_width(300.0)
|
.min_sidebar_width(300.0)
|
||||||
@ -110,6 +123,8 @@ pub fn welcome_content_page(window: &adw::ApplicationWindow, content_box: >k::
|
|||||||
|
|
||||||
window.add_breakpoint(welcome_content_page_split_view_breakpoint);
|
window.add_breakpoint(welcome_content_page_split_view_breakpoint);
|
||||||
|
|
||||||
|
welcome_content_page_stack_box.append(&window_banner);
|
||||||
|
welcome_content_page_stack_box.append(&welcome_content_page_stack);
|
||||||
window_title_bar.pack_end(&credits_button);
|
window_title_bar.pack_end(&credits_button);
|
||||||
window_title_bar.pack_start(&sidebar_toggle_button);
|
window_title_bar.pack_start(&sidebar_toggle_button);
|
||||||
window_title_bar.pack_start(&startup_switch);
|
window_title_bar.pack_start(&startup_switch);
|
||||||
@ -136,5 +151,6 @@ pub fn welcome_content_page(window: &adw::ApplicationWindow, content_box: >k::
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
welcome_page(&welcome_content_page_stack, &internet_connected);
|
welcome_page(&welcome_content_page_stack, &window_banner, &internet_connected);
|
||||||
|
setup_steps_page(&welcome_content_page_stack, &window, &internet_connected);
|
||||||
}
|
}
|
||||||
|
157
src/welcome_content_page/setup_steps_page/mod.rs
Normal file
157
src/welcome_content_page/setup_steps_page/mod.rs
Normal file
@ -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::*;
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
#[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<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 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<Setup_Steps_Entry> = 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<bool> = 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());
|
||||||
|
}
|
@ -1,17 +1,29 @@
|
|||||||
// GTK crates
|
// GTK crates
|
||||||
|
use std::{thread, time};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use adw::*;
|
use adw::*;
|
||||||
use adw::gio::ffi::GReallocFunc;
|
|
||||||
use glib::*;
|
use glib::*;
|
||||||
use gtk::Orientation;
|
|
||||||
use crate::config::DISTRO_ICON;
|
use crate::config::DISTRO_ICON;
|
||||||
|
|
||||||
pub fn welcome_page(
|
pub fn welcome_page(
|
||||||
welcome_content_page_stack: >k::Stack,
|
welcome_content_page_stack: >k::Stack,
|
||||||
|
window_banner: &adw::Banner,
|
||||||
internet_connected: &Rc<RefCell<bool>>
|
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 welcome_page_text = adw::StatusPage::builder()
|
let welcome_page_text = adw::StatusPage::builder()
|
||||||
.icon_name(DISTRO_ICON)
|
.icon_name(DISTRO_ICON)
|
||||||
.title(t!("welcome_page_text_title"))
|
.title(t!("welcome_page_text_title"))
|
||||||
@ -29,5 +41,20 @@ pub fn welcome_page(
|
|||||||
.propagate_natural_height(true)
|
.propagate_natural_height(true)
|
||||||
.build();
|
.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 window_banner => async move {
|
||||||
|
while let Ok(_state) = internet_loop_receiver.recv().await {
|
||||||
|
if *internet_connected_status.borrow_mut() == true {
|
||||||
|
window_banner.set_revealed(false);
|
||||||
|
} else {
|
||||||
|
window_banner.set_revealed(true);
|
||||||
|
window_banner.set_title(&t!("window_banner_no_internet"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
welcome_content_page_stack.add_titled(&welcome_page_scroll, Some("welcome_page"), &t!("welcome_page_title").to_string());
|
welcome_content_page_stack.add_titled(&welcome_page_scroll, Some("welcome_page"), &t!("welcome_page_title").to_string());
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user