From d05834282902aca7e33c0a20ab1ebf649b6c6c1f Mon Sep 17 00:00:00 2001 From: Observer KRypt0n_ Date: Sun, 16 Apr 2023 12:04:53 +0200 Subject: [PATCH] feat: added game sandboxing --- src/ui/preferences/environment.rs | 9 +- src/ui/preferences/main.rs | 7 + src/ui/preferences/mod.rs | 1 + src/ui/preferences/sandbox.rs | 212 ++++++++++++++++++++++++++++++ 4 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 src/ui/preferences/sandbox.rs diff --git a/src/ui/preferences/environment.rs b/src/ui/preferences/environment.rs index dd0cad5..ddc8442 100644 --- a/src/ui/preferences/environment.rs +++ b/src/ui/preferences/environment.rs @@ -128,6 +128,7 @@ impl SimpleAsyncComponent for EnvironmentApp { gtk::Button { set_label: &tr("add"), + add_css_class: "pill", set_margin_top: 8, set_halign: gtk::Align::Start, @@ -176,11 +177,13 @@ impl SimpleAsyncComponent for EnvironmentApp { let name = self.name.text().trim().to_string(); let value = self.value.text().trim().to_string(); - config.game.environment.insert(name.clone(), value.clone()); + if !name.is_empty() && !value.is_empty() { + config.game.environment.insert(name.clone(), value.clone()); - Config::update(config); + Config::update(config); - self.variables.guard().push_back((name, value)); + self.variables.guard().push_back((name, value)); + } } } diff --git a/src/ui/preferences/main.rs b/src/ui/preferences/main.rs index bd9c3f5..545ef80 100644 --- a/src/ui/preferences/main.rs +++ b/src/ui/preferences/main.rs @@ -15,6 +15,7 @@ use crate::i18n::tr; use super::general::*; use super::enhancements::*; +use super::sandbox::*; use super::environment::*; pub static mut PREFERENCES_WINDOW: Option = None; @@ -22,6 +23,7 @@ pub static mut PREFERENCES_WINDOW: Option = None; pub struct PreferencesApp { general: AsyncController, enhancements: AsyncController, + sandbox: AsyncController, environment: AsyncController } @@ -67,6 +69,7 @@ impl SimpleAsyncComponent for PreferencesApp { add = model.general.widget(), add = model.enhancements.widget(), + add = model.sandbox.widget(), add = model.environment.widget(), connect_close_request[sender] => move |_| { @@ -98,6 +101,10 @@ impl SimpleAsyncComponent for PreferencesApp { .launch(()) .detach(), + sandbox: SandboxApp::builder() + .launch(()) + .detach(), + environment: EnvironmentApp::builder() .launch(()) .detach() diff --git a/src/ui/preferences/mod.rs b/src/ui/preferences/mod.rs index 292d031..c066563 100644 --- a/src/ui/preferences/mod.rs +++ b/src/ui/preferences/mod.rs @@ -1,5 +1,6 @@ pub mod main; pub mod general; pub mod enhancements; +pub mod sandbox; pub mod environment; pub mod gamescope; diff --git a/src/ui/preferences/sandbox.rs b/src/ui/preferences/sandbox.rs new file mode 100644 index 0000000..79d4315 --- /dev/null +++ b/src/ui/preferences/sandbox.rs @@ -0,0 +1,212 @@ +use relm4::prelude::*; +use relm4::component::*; +use relm4::factory::*; + +use adw::prelude::*; + +use anime_launcher_sdk::is_available; + +use crate::i18n::tr; +use crate::*; + +#[derive(Debug)] +struct Directory { + path: String +} + +#[relm4::factory(async)] +impl AsyncFactoryComponent for Directory { + type Init = String; + type Input = SandboxAppMsg; + type Output = SandboxAppMsg; + type CommandOutput = (); + type ParentInput = SandboxAppMsg; + type ParentWidget = adw::PreferencesGroup; + + view! { + root = adw::ActionRow { + set_title: &self.path, + + 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::Remove(index.clone())); + } + } + } + } + + fn output_to_parent_input(output: Self::Output) -> Option { + Some(output) + } + + async fn init_model( + init: Self::Init, + _index: &DynamicIndex, + _sender: AsyncFactorySender, + ) -> Self { + Self { + path: init + } + } + + async fn update(&mut self, msg: Self::Input, sender: AsyncFactorySender) { + sender.output(msg); + } +} + +pub struct SandboxApp { + directories: AsyncFactoryVecDeque, + + sandboxing_path: adw::EntryRow +} + +#[derive(Debug, Clone)] +pub enum SandboxAppMsg { + Add, + Remove(DynamicIndex) +} + +#[relm4::component(async, pub)] +impl SimpleAsyncComponent for SandboxApp { + type Init = (); + type Input = SandboxAppMsg; + type Output = (); + + view! { + adw::PreferencesPage { + set_title: "Sandbox", + set_icon_name: Some("folder-symbolic"), + + set_sensitive: is_available("bwrap"), + + add = &adw::PreferencesGroup { + set_title: "Sandbox", + set_description: Some("Run the game in isolated environment, preventing it from accessing your personal data"), + + adw::ActionRow { + set_title: "Enable sandboxing", + set_subtitle: "Run the game in read-only copy of your root filesystem", + + add_suffix = >k::Switch { + set_valign: gtk::Align::Center, + + set_state: CONFIG.sandbox.enabled, + + connect_state_notify => |switch| { + if is_ready() { + if let Ok(mut config) = Config::get() { + config.sandbox.enabled = switch.state(); + + Config::update(config); + } + } + } + } + }, + + adw::ActionRow { + set_title: "Hide home directory", + set_subtitle: "Isolate your /home, /var/home/{username}, and $HOME folders from the game", + + add_suffix = >k::Switch { + set_valign: gtk::Align::Center, + + set_state: CONFIG.sandbox.isolate_home, + + connect_state_notify => |switch| { + if is_ready() { + if let Ok(mut config) = Config::get() { + config.sandbox.isolate_home = switch.state(); + + Config::update(config); + } + } + } + } + } + }, + + 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"), + + #[local_ref] + sandboxing_path -> adw::EntryRow { + set_title: "Path" + }, + + gtk::Button { + set_label: &tr("add"), + add_css_class: "pill", + + set_margin_top: 8, + set_halign: gtk::Align::Start, + + connect_clicked => SandboxAppMsg::Add + } + }, + + #[local_ref] + add = directories -> adw::PreferencesGroup {} + } + } + + async fn init( + _init: Self::Init, + root: Self::Root, + sender: AsyncComponentSender, + ) -> AsyncComponentParts { + tracing::info!("Initializing environment settings"); + + let mut model = Self { + directories: AsyncFactoryVecDeque::new(adw::PreferencesGroup::new(), sender.input_sender()), + + sandboxing_path: adw::EntryRow::new() + }; + + for path in &CONFIG.sandbox.private { + model.directories.guard().push_back(path.trim().to_string()); + } + + let directories = model.directories.widget(); + let sandboxing_path = &model.sandboxing_path; + + let widgets = view_output!(); + + AsyncComponentParts { model, widgets } + } + + async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender) { + match msg { + SandboxAppMsg::Add => { + if let Ok(mut config) = Config::get() { + let path = self.sandboxing_path.text().trim().to_string(); + + if !path.is_empty() { + config.sandbox.private.push(path.clone()); + + Config::update(config); + + self.directories.guard().push_back(path); + } + } + } + + SandboxAppMsg::Remove(index) => { + if let Ok(mut config) = Config::get() { + if let Some(var) = self.directories.guard().get(index.current_index()) { + config.sandbox.private.retain(|item| item != &var.path); + + Config::update(config); + } + + self.directories.guard().remove(index.current_index()); + } + } + } + } +}