feat(ui): added sandboxing feature
This commit is contained in:
parent
d058342829
commit
1954f99783
3 changed files with 242 additions and 48 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
@ -44,7 +44,8 @@ version = "1.6.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"cached",
|
"cached 0.43.0",
|
||||||
|
"dns-lookup",
|
||||||
"flate2",
|
"flate2",
|
||||||
"fs_extra",
|
"fs_extra",
|
||||||
"kinda-virtual-fs",
|
"kinda-virtual-fs",
|
||||||
|
@ -67,7 +68,7 @@ version = "3.4.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anime-launcher-sdk",
|
"anime-launcher-sdk",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cached",
|
"cached 0.42.0",
|
||||||
"fluent-templates",
|
"fluent-templates",
|
||||||
"glib-build-tools",
|
"glib-build-tools",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
|
@ -89,7 +90,7 @@ version = "0.5.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anime-game-core",
|
"anime-game-core",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cached",
|
"cached 0.43.0",
|
||||||
"discord-rich-presence",
|
"discord-rich-presence",
|
||||||
"enum-ordinalize",
|
"enum-ordinalize",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
@ -344,6 +345,25 @@ dependencies = [
|
||||||
"tokio",
|
"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]]
|
[[package]]
|
||||||
name = "cached_proc_macro"
|
name = "cached_proc_macro"
|
||||||
version = "0.16.0"
|
version = "0.16.0"
|
||||||
|
@ -629,6 +649,18 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"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]]
|
[[package]]
|
||||||
name = "doc-comment"
|
name = "doc-comment"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
|
|
@ -62,8 +62,8 @@ impl AsyncFactoryComponent for Variable {
|
||||||
pub struct EnvironmentApp {
|
pub struct EnvironmentApp {
|
||||||
variables: AsyncFactoryVecDeque<Variable>,
|
variables: AsyncFactoryVecDeque<Variable>,
|
||||||
|
|
||||||
name: gtk::Entry,
|
name_entry: adw::EntryRow,
|
||||||
value: gtk::Entry
|
value_entry: adw::EntryRow
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -110,20 +110,14 @@ impl SimpleAsyncComponent for EnvironmentApp {
|
||||||
add = &adw::PreferencesGroup {
|
add = &adw::PreferencesGroup {
|
||||||
set_title: &tr("new-variable"),
|
set_title: &tr("new-variable"),
|
||||||
|
|
||||||
gtk::Box {
|
#[local_ref]
|
||||||
set_orientation: gtk::Orientation::Horizontal,
|
name_entry -> adw::EntryRow {
|
||||||
set_spacing: 8,
|
set_title: &tr("name")
|
||||||
|
},
|
||||||
|
|
||||||
#[local_ref]
|
#[local_ref]
|
||||||
name -> gtk::Entry {
|
value_entry -> adw::EntryRow {
|
||||||
set_placeholder_text: Some(&tr("name"))
|
set_title: &tr("value")
|
||||||
},
|
|
||||||
|
|
||||||
#[local_ref]
|
|
||||||
value -> gtk::Entry {
|
|
||||||
set_placeholder_text: Some(&tr("value")),
|
|
||||||
set_hexpand: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
gtk::Button {
|
gtk::Button {
|
||||||
|
@ -152,8 +146,8 @@ impl SimpleAsyncComponent for EnvironmentApp {
|
||||||
let mut model = Self {
|
let mut model = Self {
|
||||||
variables: AsyncFactoryVecDeque::new(adw::PreferencesGroup::new(), sender.input_sender()),
|
variables: AsyncFactoryVecDeque::new(adw::PreferencesGroup::new(), sender.input_sender()),
|
||||||
|
|
||||||
name: gtk::Entry::new(),
|
name_entry: adw::EntryRow::new(),
|
||||||
value: gtk::Entry::new()
|
value_entry: adw::EntryRow::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
for (name, value) in &CONFIG.game.environment {
|
for (name, value) in &CONFIG.game.environment {
|
||||||
|
@ -162,8 +156,8 @@ impl SimpleAsyncComponent for EnvironmentApp {
|
||||||
|
|
||||||
let variables = model.variables.widget();
|
let variables = model.variables.widget();
|
||||||
|
|
||||||
let name = &model.name;
|
let name_entry = &model.name_entry;
|
||||||
let value = &model.value;
|
let value_entry = &model.value_entry;
|
||||||
|
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
@ -174,8 +168,8 @@ impl SimpleAsyncComponent for EnvironmentApp {
|
||||||
match msg {
|
match msg {
|
||||||
EnvironmentMsg::Add => {
|
EnvironmentMsg::Add => {
|
||||||
if let Ok(mut config) = Config::get() {
|
if let Ok(mut config) = Config::get() {
|
||||||
let name = self.name.text().trim().to_string();
|
let name = self.name_entry.text().trim().to_string();
|
||||||
let value = self.value.text().trim().to_string();
|
let value = self.value_entry.text().trim().to_string();
|
||||||
|
|
||||||
if !name.is_empty() && !value.is_empty() {
|
if !name.is_empty() && !value.is_empty() {
|
||||||
config.game.environment.insert(name.clone(), value.clone());
|
config.game.environment.insert(name.clone(), value.clone());
|
||||||
|
|
|
@ -10,12 +10,12 @@ use crate::i18n::tr;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Directory {
|
struct PrivateDirectory {
|
||||||
path: String
|
path: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[relm4::factory(async)]
|
#[relm4::factory(async)]
|
||||||
impl AsyncFactoryComponent for Directory {
|
impl AsyncFactoryComponent for PrivateDirectory {
|
||||||
type Init = String;
|
type Init = String;
|
||||||
type Input = SandboxAppMsg;
|
type Input = SandboxAppMsg;
|
||||||
type Output = SandboxAppMsg;
|
type Output = SandboxAppMsg;
|
||||||
|
@ -33,7 +33,7 @@ impl AsyncFactoryComponent for Directory {
|
||||||
set_valign: gtk::Align::Center,
|
set_valign: gtk::Align::Center,
|
||||||
|
|
||||||
connect_clicked[sender, index] => move |_| {
|
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 {
|
#[derive(Debug)]
|
||||||
directories: AsyncFactoryVecDeque<Directory>,
|
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 = >k::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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum SandboxAppMsg {
|
pub enum SandboxAppMsg {
|
||||||
Add,
|
AddPrivate,
|
||||||
Remove(DynamicIndex)
|
RemovePrivate(DynamicIndex),
|
||||||
|
|
||||||
|
AddShared,
|
||||||
|
RemoveShared(DynamicIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[relm4::component(async, pub)]
|
#[relm4::component(async, pub)]
|
||||||
|
@ -127,15 +187,19 @@ impl SimpleAsyncComponent for SandboxApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
adw::EntryRow {
|
||||||
|
set_title: "Hostname"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
add = &adw::PreferencesGroup {
|
add = &adw::PreferencesGroup {
|
||||||
set_title: "Sandboxed directories",
|
set_title: "Private 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_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]
|
#[local_ref]
|
||||||
sandboxing_path -> adw::EntryRow {
|
private_path_entry -> adw::EntryRow {
|
||||||
set_title: "Path"
|
set_title: "Path"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -146,12 +210,50 @@ impl SimpleAsyncComponent for SandboxApp {
|
||||||
set_margin_top: 8,
|
set_margin_top: 8,
|
||||||
set_halign: gtk::Align::Start,
|
set_halign: gtk::Align::Start,
|
||||||
|
|
||||||
connect_clicked => SandboxAppMsg::Add
|
connect_clicked => SandboxAppMsg::AddPrivate
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
#[local_ref]
|
#[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");
|
tracing::info!("Initializing environment settings");
|
||||||
|
|
||||||
let mut model = Self {
|
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 {
|
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();
|
for (from, to) in &CONFIG.sandbox.mounts.read_only {
|
||||||
let sandboxing_path = &model.sandboxing_path;
|
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!();
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
@ -182,29 +309,70 @@ impl SimpleAsyncComponent for SandboxApp {
|
||||||
|
|
||||||
async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) {
|
async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) {
|
||||||
match msg {
|
match msg {
|
||||||
SandboxAppMsg::Add => {
|
SandboxAppMsg::AddPrivate => {
|
||||||
if let Ok(mut config) = Config::get() {
|
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() {
|
if !path.is_empty() {
|
||||||
config.sandbox.private.push(path.clone());
|
config.sandbox.private.push(path.clone());
|
||||||
|
|
||||||
Config::update(config);
|
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 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.sandbox.private.retain(|item| item != &var.path);
|
||||||
|
|
||||||
Config::update(config);
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue