mirror of
https://github.com/an-anime-team/sleepy-launcher.git
synced 2025-02-16 17:59:46 +03:00
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 = [
|
||||
"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"
|
||||
|
|
|
@ -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_entry -> adw::EntryRow {
|
||||
set_title: &tr("name")
|
||||
},
|
||||
|
||||
#[local_ref]
|
||||
name -> gtk::Entry {
|
||||
set_placeholder_text: Some(&tr("name"))
|
||||
},
|
||||
|
||||
#[local_ref]
|
||||
value -> gtk::Entry {
|
||||
set_placeholder_text: Some(&tr("value")),
|
||||
set_hexpand: true
|
||||
}
|
||||
#[local_ref]
|
||||
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());
|
||||
|
|
|
@ -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 = >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)]
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue