feat(ui): added sandboxing feature

This commit is contained in:
Observer KRypt0n_ 2023-04-16 14:12:25 +02:00
parent d058342829
commit 1954f99783
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
3 changed files with 242 additions and 48 deletions

38
Cargo.lock generated
View file

@ -44,7 +44,8 @@ version = "1.6.2"
dependencies = [
"anyhow",
"bzip2",
"cached",
"cached 0.43.0",
"dns-lookup",
"flate2",
"fs_extra",
"kinda-virtual-fs",
@ -67,7 +68,7 @@ version = "3.4.1"
dependencies = [
"anime-launcher-sdk",
"anyhow",
"cached",
"cached 0.42.0",
"fluent-templates",
"glib-build-tools",
"gtk4",
@ -89,7 +90,7 @@ version = "0.5.17"
dependencies = [
"anime-game-core",
"anyhow",
"cached",
"cached 0.43.0",
"discord-rich-presence",
"enum-ordinalize",
"lazy_static",
@ -344,6 +345,25 @@ dependencies = [
"tokio",
]
[[package]]
name = "cached"
version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc2fafddf188d13788e7099295a59b99e99b2148ab2195cae454e754cc099925"
dependencies = [
"async-trait",
"async_once",
"cached_proc_macro",
"cached_proc_macro_types",
"futures",
"hashbrown 0.13.2",
"instant",
"lazy_static",
"once_cell",
"thiserror",
"tokio",
]
[[package]]
name = "cached_proc_macro"
version = "0.16.0"
@ -629,6 +649,18 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "dns-lookup"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53ecafc952c4528d9b51a458d1a8904b81783feff9fde08ab6ed2545ff396872"
dependencies = [
"cfg-if",
"libc",
"socket2",
"winapi",
]
[[package]]
name = "doc-comment"
version = "0.3.3"

View file

@ -62,8 +62,8 @@ impl AsyncFactoryComponent for Variable {
pub struct EnvironmentApp {
variables: AsyncFactoryVecDeque<Variable>,
name: gtk::Entry,
value: gtk::Entry
name_entry: adw::EntryRow,
value_entry: adw::EntryRow
}
#[derive(Debug, Clone)]
@ -110,20 +110,14 @@ impl SimpleAsyncComponent for EnvironmentApp {
add = &adw::PreferencesGroup {
set_title: &tr("new-variable"),
gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
set_spacing: 8,
#[local_ref]
name -> gtk::Entry {
set_placeholder_text: Some(&tr("name"))
name_entry -> adw::EntryRow {
set_title: &tr("name")
},
#[local_ref]
value -> gtk::Entry {
set_placeholder_text: Some(&tr("value")),
set_hexpand: true
}
value_entry -> adw::EntryRow {
set_title: &tr("value")
},
gtk::Button {
@ -152,8 +146,8 @@ impl SimpleAsyncComponent for EnvironmentApp {
let mut model = Self {
variables: AsyncFactoryVecDeque::new(adw::PreferencesGroup::new(), sender.input_sender()),
name: gtk::Entry::new(),
value: gtk::Entry::new()
name_entry: adw::EntryRow::new(),
value_entry: adw::EntryRow::new()
};
for (name, value) in &CONFIG.game.environment {
@ -162,8 +156,8 @@ impl SimpleAsyncComponent for EnvironmentApp {
let variables = model.variables.widget();
let name = &model.name;
let value = &model.value;
let name_entry = &model.name_entry;
let value_entry = &model.value_entry;
let widgets = view_output!();
@ -174,8 +168,8 @@ impl SimpleAsyncComponent for EnvironmentApp {
match msg {
EnvironmentMsg::Add => {
if let Ok(mut config) = Config::get() {
let name = self.name.text().trim().to_string();
let value = self.value.text().trim().to_string();
let name = self.name_entry.text().trim().to_string();
let value = self.value_entry.text().trim().to_string();
if !name.is_empty() && !value.is_empty() {
config.game.environment.insert(name.clone(), value.clone());

View file

@ -10,12 +10,12 @@ use crate::i18n::tr;
use crate::*;
#[derive(Debug)]
struct Directory {
struct PrivateDirectory {
path: String
}
#[relm4::factory(async)]
impl AsyncFactoryComponent for Directory {
impl AsyncFactoryComponent for PrivateDirectory {
type Init = String;
type Input = SandboxAppMsg;
type Output = SandboxAppMsg;
@ -33,7 +33,7 @@ impl AsyncFactoryComponent for Directory {
set_valign: gtk::Align::Center,
connect_clicked[sender, index] => move |_| {
sender.input(SandboxAppMsg::Remove(index.clone()));
sender.input(SandboxAppMsg::RemovePrivate(index.clone()));
}
}
}
@ -58,16 +58,76 @@ impl AsyncFactoryComponent for Directory {
}
}
pub struct SandboxApp {
directories: AsyncFactoryVecDeque<Directory>,
#[derive(Debug)]
struct SharedDirectory {
mount_from: String,
mount_to: String
}
sandboxing_path: adw::EntryRow
#[relm4::factory(async)]
impl AsyncFactoryComponent for SharedDirectory {
type Init = (String, String);
type Input = SandboxAppMsg;
type Output = SandboxAppMsg;
type CommandOutput = ();
type ParentInput = SandboxAppMsg;
type ParentWidget = adw::PreferencesGroup;
view! {
root = adw::ActionRow {
set_title: &self.mount_to,
set_subtitle: &self.mount_from,
add_suffix = &gtk::Button {
set_icon_name: "user-trash-symbolic",
add_css_class: "flat",
set_valign: gtk::Align::Center,
connect_clicked[sender, index] => move |_| {
sender.input(SandboxAppMsg::RemoveShared(index.clone()));
}
}
}
}
fn output_to_parent_input(output: Self::Output) -> Option<Self::ParentInput> {
Some(output)
}
async fn init_model(
init: Self::Init,
_index: &DynamicIndex,
_sender: AsyncFactorySender<Self>,
) -> Self {
Self {
mount_from: init.0,
mount_to: init.1
}
}
async fn update(&mut self, msg: Self::Input, sender: AsyncFactorySender<Self>) {
sender.output(msg);
}
}
pub struct SandboxApp {
private_paths: AsyncFactoryVecDeque<PrivateDirectory>,
shared_paths: AsyncFactoryVecDeque<SharedDirectory>,
private_path_entry: adw::EntryRow,
shared_path_from_entry: adw::EntryRow,
shared_path_to_entry: adw::EntryRow,
read_only_switch: gtk::Switch
}
#[derive(Debug, Clone)]
pub enum SandboxAppMsg {
Add,
Remove(DynamicIndex)
AddPrivate,
RemovePrivate(DynamicIndex),
AddShared,
RemoveShared(DynamicIndex)
}
#[relm4::component(async, pub)]
@ -127,15 +187,19 @@ impl SimpleAsyncComponent for SandboxApp {
}
}
}
},
adw::EntryRow {
set_title: "Hostname"
}
},
add = &adw::PreferencesGroup {
set_title: "Sandboxed directories",
set_description: Some("These folders will be replaced by in-memory filesystem (tmpfs), and their original content will not be available to sandboxed game"),
set_title: "Private directories",
set_description: Some("These folders will be replaced by an empty virtual filesystem (tmpfs), and their original content will not be available to sandboxed game"),
#[local_ref]
sandboxing_path -> adw::EntryRow {
private_path_entry -> adw::EntryRow {
set_title: "Path"
},
@ -146,12 +210,50 @@ impl SimpleAsyncComponent for SandboxApp {
set_margin_top: 8,
set_halign: gtk::Align::Start,
connect_clicked => SandboxAppMsg::Add
connect_clicked => SandboxAppMsg::AddPrivate
}
},
#[local_ref]
add = directories -> adw::PreferencesGroup {}
add = private_paths -> adw::PreferencesGroup {},
add = &adw::PreferencesGroup {
set_title: "Shared directories",
set_description: Some("These directories will be symlinked to directories in your host system"),
#[local_ref]
shared_path_from_entry -> adw::EntryRow {
set_title: "Original path"
},
#[local_ref]
shared_path_to_entry -> adw::EntryRow {
set_title: "New path"
},
adw::ActionRow {
set_title: "Read-only",
set_subtitle: "Forbid game to write any data to this directory",
#[local_ref]
add_suffix = read_only_switch -> gtk::Switch {
set_valign: gtk::Align::Center
}
},
gtk::Button {
set_label: &tr("add"),
add_css_class: "pill",
set_margin_top: 8,
set_halign: gtk::Align::Start,
connect_clicked => SandboxAppMsg::AddShared
}
},
#[local_ref]
add = shared_paths -> adw::PreferencesGroup {}
}
}
@ -163,17 +265,42 @@ impl SimpleAsyncComponent for SandboxApp {
tracing::info!("Initializing environment settings");
let mut model = Self {
directories: AsyncFactoryVecDeque::new(adw::PreferencesGroup::new(), sender.input_sender()),
private_paths: AsyncFactoryVecDeque::new(adw::PreferencesGroup::new(), sender.input_sender()),
shared_paths: AsyncFactoryVecDeque::new(adw::PreferencesGroup::new(), sender.input_sender()),
sandboxing_path: adw::EntryRow::new()
private_path_entry: adw::EntryRow::new(),
shared_path_from_entry: adw::EntryRow::new(),
shared_path_to_entry: adw::EntryRow::new(),
read_only_switch: gtk::Switch::new()
};
for path in &CONFIG.sandbox.private {
model.directories.guard().push_back(path.trim().to_string());
model.private_paths.guard().push_back(path.trim().to_string());
}
let directories = model.directories.widget();
let sandboxing_path = &model.sandboxing_path;
for (from, to) in &CONFIG.sandbox.mounts.read_only {
model.shared_paths.guard().push_back((
from.trim().to_string(),
format!("[read-only] {}", to.trim())
));
}
for (from, to) in &CONFIG.sandbox.mounts.bind {
model.shared_paths.guard().push_back((
from.trim().to_string(),
to.trim().to_string()
));
}
let private_paths = model.private_paths.widget();
let shared_paths = model.shared_paths.widget();
let private_path_entry = &model.private_path_entry;
let shared_path_from_entry = &model.shared_path_from_entry;
let shared_path_to_entry = &model.shared_path_to_entry;
let read_only_switch = &model.read_only_switch;
let widgets = view_output!();
@ -182,29 +309,70 @@ impl SimpleAsyncComponent for SandboxApp {
async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) {
match msg {
SandboxAppMsg::Add => {
SandboxAppMsg::AddPrivate => {
if let Ok(mut config) = Config::get() {
let path = self.sandboxing_path.text().trim().to_string();
let path = self.private_path_entry.text().trim().to_string();
if !path.is_empty() {
config.sandbox.private.push(path.clone());
Config::update(config);
self.directories.guard().push_back(path);
self.private_paths.guard().push_back(path);
}
}
}
SandboxAppMsg::Remove(index) => {
SandboxAppMsg::RemovePrivate(index) => {
if let Ok(mut config) = Config::get() {
if let Some(var) = self.directories.guard().get(index.current_index()) {
if let Some(var) = self.private_paths.guard().get(index.current_index()) {
config.sandbox.private.retain(|item| item != &var.path);
Config::update(config);
}
self.directories.guard().remove(index.current_index());
self.private_paths.guard().remove(index.current_index());
}
},
SandboxAppMsg::AddShared => {
if let Ok(mut config) = Config::get() {
let from = self.shared_path_from_entry.text().trim().to_string();
let to = self.shared_path_to_entry.text().trim().to_string();
let read_only = self.read_only_switch.state();
if !from.is_empty() && !to.is_empty() {
if read_only {
config.sandbox.mounts.read_only.insert(from.clone(), to.clone());
} else {
config.sandbox.mounts.bind.insert(from.clone(), to.clone());
}
Config::update(config);
self.shared_paths.guard().push_back((
from,
if read_only {
format!("[read-only] {}", to)
} else {
to
}
));
}
}
}
SandboxAppMsg::RemoveShared(index) => {
if let Ok(mut config) = Config::get() {
if let Some(var) = self.shared_paths.guard().get(index.current_index()) {
config.sandbox.mounts.read_only.remove(&var.mount_from);
config.sandbox.mounts.bind.remove(&var.mount_from);
Config::update(config);
}
self.shared_paths.guard().remove(index.current_index());
}
}
}