mirror of
https://gitlab.com/gabmus/envision.git
synced 2025-04-20 11:35:48 +00:00
feat: can enable and disable plugins
This commit is contained in:
parent
2f8caafac2
commit
781e2f4fd2
5 changed files with 178 additions and 29 deletions
|
@ -7,15 +7,36 @@ use crate::{
|
|||
lighthouse::lighthouse_profile, openhmd::openhmd_profile, simulated::simulated_profile,
|
||||
survive::survive_profile, wivrn::wivrn_profile, wmr::wmr_profile,
|
||||
},
|
||||
ui::plugins::Plugin,
|
||||
util::file_utils::get_writer,
|
||||
};
|
||||
use serde::{de::Error, Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::File,
|
||||
io::BufReader,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PluginConfig {
|
||||
pub appid: String,
|
||||
pub version: String,
|
||||
pub enabled: bool,
|
||||
pub exec_path: PathBuf,
|
||||
}
|
||||
|
||||
impl From<&Plugin> for PluginConfig {
|
||||
fn from(p: &Plugin) -> Self {
|
||||
Self {
|
||||
appid: p.appid.clone(),
|
||||
version: p.version.clone(),
|
||||
enabled: true,
|
||||
exec_path: p.exec_path(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_WIN_SIZE: [i32; 2] = [360, 400];
|
||||
|
||||
const fn default_win_size() -> [i32; 2] {
|
||||
|
@ -29,6 +50,8 @@ pub struct Config {
|
|||
pub user_profiles: Vec<Profile>,
|
||||
#[serde(default = "default_win_size")]
|
||||
pub win_size: [i32; 2],
|
||||
#[serde(default)]
|
||||
pub plugins: HashMap<String, PluginConfig>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
|
@ -37,8 +60,9 @@ impl Default for Config {
|
|||
// TODO: using an empty string here is ugly
|
||||
selected_profile_uuid: "".to_string(),
|
||||
debug_view_enabled: false,
|
||||
user_profiles: vec![],
|
||||
user_profiles: Vec::default(),
|
||||
win_size: DEFAULT_WIN_SIZE,
|
||||
plugins: HashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use super::{
|
|||
},
|
||||
libsurvive_setup_window::{LibsurviveSetupMsg, LibsurviveSetupWindow},
|
||||
main_view::{MainView, MainViewInit, MainViewMsg, MainViewOutMsg},
|
||||
plugins::store::{PluginStore, PluginStoreMsg},
|
||||
plugins::store::{PluginStore, PluginStoreInit, PluginStoreMsg, PluginStoreOutMsg},
|
||||
util::{copiable_code_snippet, copy_text, open_with_default_handler},
|
||||
wivrn_conf_editor::{WivrnConfEditor, WivrnConfEditorInit, WivrnConfEditorMsg},
|
||||
};
|
||||
|
@ -22,7 +22,7 @@ use crate::{
|
|||
build_opencomposite::get_build_opencomposite_jobs, build_openhmd::get_build_openhmd_jobs,
|
||||
build_wivrn::get_build_wivrn_jobs,
|
||||
},
|
||||
config::Config,
|
||||
config::{Config, PluginConfig},
|
||||
constants::APP_NAME,
|
||||
depcheck::common::dep_pkexec,
|
||||
file_builders::{
|
||||
|
@ -57,7 +57,11 @@ use relm4::{
|
|||
new_action_group, new_stateful_action, new_stateless_action,
|
||||
prelude::*,
|
||||
};
|
||||
use std::{collections::VecDeque, fs::remove_file, time::Duration};
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
fs::remove_file,
|
||||
time::Duration,
|
||||
};
|
||||
use tracing::error;
|
||||
|
||||
pub struct App {
|
||||
|
@ -123,6 +127,7 @@ pub enum Msg {
|
|||
OnProberExit(bool),
|
||||
WivrnCheckPairMode,
|
||||
OpenPluginStore,
|
||||
UpdateConfigPlugins(HashMap<String, PluginConfig>),
|
||||
NoOp,
|
||||
}
|
||||
|
||||
|
@ -795,10 +800,20 @@ impl AsyncComponent for App {
|
|||
}
|
||||
}
|
||||
Msg::OpenPluginStore => {
|
||||
let pluginstore = PluginStore::builder().launch(()).detach();
|
||||
let pluginstore = PluginStore::builder()
|
||||
.launch(PluginStoreInit {
|
||||
config_plugins: self.config.plugins.clone(),
|
||||
})
|
||||
.forward(sender.input_sender(), move |msg| match msg {
|
||||
PluginStoreOutMsg::UpdateConfigPlugins(cp) => Msg::UpdateConfigPlugins(cp),
|
||||
});
|
||||
pluginstore.sender().emit(PluginStoreMsg::Present);
|
||||
self.pluginstore = Some(pluginstore);
|
||||
}
|
||||
Msg::UpdateConfigPlugins(cp) => {
|
||||
self.config.plugins = cp;
|
||||
self.config.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,14 @@ use super::{
|
|||
store_row_factory::{StoreRowModel, StoreRowModelInit, StoreRowModelMsg, StoreRowModelOutMsg},
|
||||
Plugin,
|
||||
};
|
||||
use crate::{downloader::download_file_async, ui::alert::alert};
|
||||
use crate::{
|
||||
config::PluginConfig,
|
||||
downloader::download_file_async,
|
||||
ui::{alert::alert, SENDER_IO_ERR_MSG},
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use relm4::{factory::AsyncFactoryVecDeque, prelude::*};
|
||||
use std::fs::remove_file;
|
||||
use std::{collections::HashMap, fs::remove_file};
|
||||
|
||||
#[tracker::track]
|
||||
pub struct PluginStore {
|
||||
|
@ -18,6 +22,8 @@ pub struct PluginStore {
|
|||
details: AsyncController<StoreDetail>,
|
||||
#[tracker::do_not_track]
|
||||
main_stack: Option<gtk::Stack>,
|
||||
#[tracker::do_not_track]
|
||||
config_plugins: HashMap<String, PluginConfig>,
|
||||
refreshing: bool,
|
||||
locked: bool,
|
||||
plugins: Vec<Plugin>,
|
||||
|
@ -32,18 +38,24 @@ pub enum PluginStoreMsg {
|
|||
InstallDownload(Plugin, relm4::Sender<StoreRowModelMsg>),
|
||||
RemoveFromDetails(Plugin),
|
||||
Remove(Plugin, relm4::Sender<StoreRowModelMsg>),
|
||||
Enable(String),
|
||||
Disable(String),
|
||||
SetEnabled(Plugin, bool),
|
||||
ShowDetails(usize),
|
||||
ShowPluginList,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PluginStoreOutMsg {}
|
||||
pub struct PluginStoreInit {
|
||||
pub config_plugins: HashMap<String, PluginConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PluginStoreOutMsg {
|
||||
UpdateConfigPlugins(HashMap<String, PluginConfig>),
|
||||
}
|
||||
|
||||
#[relm4::component(pub async)]
|
||||
impl AsyncComponent for PluginStore {
|
||||
type Init = ();
|
||||
type Init = PluginStoreInit;
|
||||
type Input = PluginStoreMsg;
|
||||
type Output = PluginStoreOutMsg;
|
||||
type CommandOutput = ();
|
||||
|
@ -181,6 +193,10 @@ impl AsyncComponent for PluginStore {
|
|||
self.plugins.iter().for_each(|plugin| {
|
||||
guard.push_back(StoreRowModelInit {
|
||||
plugin: plugin.clone(),
|
||||
enabled: self
|
||||
.config_plugins
|
||||
.get(&plugin.appid)
|
||||
.is_some_and(|cp| cp.enabled),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -231,9 +247,17 @@ impl AsyncComponent for PluginStore {
|
|||
)),
|
||||
Some(&self.win.as_ref().unwrap().clone().upcast::<gtk::Window>()),
|
||||
);
|
||||
} else {
|
||||
self.config_plugins
|
||||
.insert(plugin.appid.clone(), PluginConfig::from(&plugin));
|
||||
sender
|
||||
.output(Self::Output::UpdateConfigPlugins(
|
||||
self.config_plugins.clone(),
|
||||
))
|
||||
.expect(SENDER_IO_ERR_MSG);
|
||||
}
|
||||
row_sender.emit(StoreRowModelMsg::Refresh);
|
||||
self.details.emit(StoreDetailMsg::Refresh);
|
||||
row_sender.emit(StoreRowModelMsg::Refresh(true));
|
||||
self.details.emit(StoreDetailMsg::Refresh(true));
|
||||
self.set_locked(false);
|
||||
}
|
||||
Self::Input::Remove(plugin, row_sender) => {
|
||||
|
@ -249,21 +273,58 @@ impl AsyncComponent for PluginStore {
|
|||
)),
|
||||
Some(&self.win.as_ref().unwrap().clone().upcast::<gtk::Window>()),
|
||||
);
|
||||
} else {
|
||||
self.config_plugins.remove(&plugin.appid);
|
||||
sender
|
||||
.output(Self::Output::UpdateConfigPlugins(
|
||||
self.config_plugins.clone(),
|
||||
))
|
||||
.expect(SENDER_IO_ERR_MSG);
|
||||
}
|
||||
}
|
||||
row_sender.emit(StoreRowModelMsg::Refresh);
|
||||
self.details.emit(StoreDetailMsg::Refresh);
|
||||
row_sender.emit(StoreRowModelMsg::Refresh(false));
|
||||
self.details.emit(StoreDetailMsg::Refresh(false));
|
||||
self.set_locked(false);
|
||||
}
|
||||
Self::Input::Enable(_) => todo!(),
|
||||
Self::Input::Disable(_) => todo!(),
|
||||
Self::Input::SetEnabled(plugin, enabled) => {
|
||||
if let Some(cp) = self.config_plugins.get_mut(&plugin.appid) {
|
||||
cp.enabled = enabled;
|
||||
if let Some(row) = self
|
||||
.plugin_rows
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.guard()
|
||||
.iter()
|
||||
.find(|row| row.is_some_and(|row| row.plugin.appid == plugin.appid))
|
||||
.flatten()
|
||||
{
|
||||
row.input_sender.emit(StoreRowModelMsg::Refresh(enabled));
|
||||
} else {
|
||||
eprintln!("could not find corresponding listbox row!")
|
||||
}
|
||||
self.details.emit(StoreDetailMsg::Refresh(enabled));
|
||||
} else {
|
||||
eprintln!(
|
||||
"failed to set plugin {} enabled: could not find in hashmap",
|
||||
plugin.appid
|
||||
)
|
||||
}
|
||||
sender
|
||||
.output(Self::Output::UpdateConfigPlugins(
|
||||
self.config_plugins.clone(),
|
||||
))
|
||||
.expect(SENDER_IO_ERR_MSG);
|
||||
}
|
||||
// we use index here because it's the listbox not the row that can
|
||||
// send this signal, so I don't directly have the plugin object
|
||||
Self::Input::ShowDetails(index) => {
|
||||
if let Some(plugin) = self.plugins.get(index) {
|
||||
self.details
|
||||
.sender()
|
||||
.emit(StoreDetailMsg::SetPlugin(plugin.clone()));
|
||||
self.details.sender().emit(StoreDetailMsg::SetPlugin(
|
||||
plugin.clone(),
|
||||
self.config_plugins
|
||||
.get(&plugin.appid)
|
||||
.is_some_and(|cp| cp.enabled),
|
||||
));
|
||||
self.main_stack
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
|
@ -282,7 +343,7 @@ impl AsyncComponent for PluginStore {
|
|||
}
|
||||
|
||||
async fn init(
|
||||
_init: Self::Init,
|
||||
init: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: AsyncComponentSender<Self>,
|
||||
) -> AsyncComponentParts<Self> {
|
||||
|
@ -299,7 +360,11 @@ impl AsyncComponent for PluginStore {
|
|||
StoreDetailOutMsg::GoBack => Self::Input::ShowPluginList,
|
||||
StoreDetailOutMsg::Install(plugin) => Self::Input::InstallFromDetails(plugin),
|
||||
StoreDetailOutMsg::Remove(plugin) => Self::Input::RemoveFromDetails(plugin),
|
||||
StoreDetailOutMsg::SetEnabled(plugin, enabled) => {
|
||||
Self::Input::SetEnabled(plugin, enabled)
|
||||
}
|
||||
}),
|
||||
config_plugins: init.config_plugins,
|
||||
main_stack: None,
|
||||
};
|
||||
|
||||
|
@ -318,6 +383,9 @@ impl AsyncComponent for PluginStore {
|
|||
StoreRowModelOutMsg::Remove(appid, row_sender) => {
|
||||
Self::Input::Remove(appid, row_sender)
|
||||
}
|
||||
StoreRowModelOutMsg::SetEnabled(plugin, enabled) => {
|
||||
Self::Input::SetEnabled(plugin, enabled)
|
||||
}
|
||||
}),
|
||||
);
|
||||
model.main_stack = Some(widgets.main_stack.clone());
|
||||
|
|
|
@ -6,6 +6,7 @@ use relm4::prelude::*;
|
|||
#[tracker::track]
|
||||
pub struct StoreDetail {
|
||||
plugin: Option<Plugin>,
|
||||
enabled: bool,
|
||||
#[tracker::do_not_track]
|
||||
carousel: Option<adw::Carousel>,
|
||||
#[tracker::do_not_track]
|
||||
|
@ -15,12 +16,13 @@ pub struct StoreDetail {
|
|||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug)]
|
||||
pub enum StoreDetailMsg {
|
||||
SetPlugin(Plugin),
|
||||
SetPlugin(Plugin, bool),
|
||||
SetIcon,
|
||||
SetScreenshots,
|
||||
Refresh,
|
||||
Refresh(bool),
|
||||
Install,
|
||||
Remove,
|
||||
SetEnabled(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -28,6 +30,7 @@ pub enum StoreDetailOutMsg {
|
|||
Install(Plugin),
|
||||
Remove(Plugin),
|
||||
GoBack,
|
||||
SetEnabled(Plugin, bool),
|
||||
}
|
||||
|
||||
#[relm4::component(pub async)]
|
||||
|
@ -128,11 +131,18 @@ impl AsyncComponent for StoreDetail {
|
|||
}
|
||||
},
|
||||
gtk::Switch {
|
||||
#[track = "self.changed(Self::plugin())"]
|
||||
#[track = "model.changed(Self::plugin())"]
|
||||
set_visible: model.plugin.as_ref()
|
||||
.is_some_and(|p| p.is_installed()),
|
||||
#[track = "model.changed(Self::enabled())"]
|
||||
set_active: model.enabled,
|
||||
set_tooltip_text: Some("Plugin enabled"),
|
||||
set_valign: gtk::Align::Center,
|
||||
set_halign: gtk::Align::Center,
|
||||
connect_state_set[sender] => move |_, state| {
|
||||
sender.input(Self::Input::SetEnabled(state));
|
||||
gtk::glib::Propagation::Proceed
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -184,8 +194,9 @@ impl AsyncComponent for StoreDetail {
|
|||
self.reset();
|
||||
|
||||
match message {
|
||||
Self::Input::SetPlugin(p) => {
|
||||
Self::Input::SetPlugin(p, enabled) => {
|
||||
self.set_plugin(Some(p));
|
||||
self.set_enabled(enabled);
|
||||
sender.input(Self::Input::SetIcon);
|
||||
sender.input(Self::Input::SetScreenshots);
|
||||
}
|
||||
|
@ -233,8 +244,9 @@ impl AsyncComponent for StoreDetail {
|
|||
}
|
||||
}
|
||||
}
|
||||
Self::Input::Refresh => {
|
||||
Self::Input::Refresh(enabled) => {
|
||||
self.mark_all_changed();
|
||||
self.set_enabled(enabled);
|
||||
}
|
||||
Self::Input::Install => {
|
||||
if let Some(plugin) = self.plugin.as_ref() {
|
||||
|
@ -250,6 +262,14 @@ impl AsyncComponent for StoreDetail {
|
|||
.expect(SENDER_IO_ERR_MSG);
|
||||
}
|
||||
}
|
||||
Self::Input::SetEnabled(enabled) => {
|
||||
self.set_enabled(enabled);
|
||||
if let Some(plugin) = self.plugin.as_ref() {
|
||||
sender
|
||||
.output(Self::Output::SetEnabled(plugin.clone(), enabled))
|
||||
.expect(SENDER_IO_ERR_MSG);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,6 +281,7 @@ impl AsyncComponent for StoreDetail {
|
|||
let mut model = Self {
|
||||
tracker: 0,
|
||||
plugin: None,
|
||||
enabled: false,
|
||||
carousel: None,
|
||||
icon: None,
|
||||
};
|
||||
|
|
|
@ -14,23 +14,27 @@ pub struct StoreRowModel {
|
|||
icon: Option<gtk::Image>,
|
||||
#[tracker::do_not_track]
|
||||
pub input_sender: relm4::Sender<StoreRowModelMsg>,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StoreRowModelInit {
|
||||
pub plugin: Plugin,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StoreRowModelMsg {
|
||||
LoadIcon,
|
||||
Refresh,
|
||||
Refresh(bool),
|
||||
SetEnabled(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StoreRowModelOutMsg {
|
||||
Install(Plugin, relm4::Sender<StoreRowModelMsg>),
|
||||
Remove(Plugin, relm4::Sender<StoreRowModelMsg>),
|
||||
SetEnabled(Plugin, bool),
|
||||
}
|
||||
|
||||
#[relm4::factory(async pub)]
|
||||
|
@ -121,15 +125,22 @@ impl AsyncFactoryComponent for StoreRowModel {
|
|||
gtk::Switch {
|
||||
#[track = "self.changed(StoreRowModel::plugin())"]
|
||||
set_visible: self.plugin.is_installed(),
|
||||
#[track = "self.changed(StoreRowModel::enabled())"]
|
||||
set_active: self.enabled,
|
||||
set_valign: gtk::Align::Center,
|
||||
set_halign: gtk::Align::Center,
|
||||
set_tooltip_text: Some("Plugin enabled"),
|
||||
connect_state_set[sender] => move |_, state| {
|
||||
sender.input(Self::Input::SetEnabled(state));
|
||||
gtk::glib::Propagation::Proceed
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn update(&mut self, message: Self::Input, _sender: AsyncFactorySender<Self>) {
|
||||
async fn update(&mut self, message: Self::Input, sender: AsyncFactorySender<Self>) {
|
||||
self.reset();
|
||||
|
||||
match message {
|
||||
|
@ -145,7 +156,16 @@ impl AsyncFactoryComponent for StoreRowModel {
|
|||
};
|
||||
}
|
||||
}
|
||||
Self::Input::Refresh => self.mark_all_changed(),
|
||||
Self::Input::SetEnabled(state) => {
|
||||
self.set_enabled(state);
|
||||
sender
|
||||
.output(Self::Output::SetEnabled(self.plugin.clone(), state))
|
||||
.expect(SENDER_IO_ERR_MSG);
|
||||
}
|
||||
Self::Input::Refresh(enabled) => {
|
||||
self.mark_all_changed();
|
||||
self.set_enabled(enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,6 +177,7 @@ impl AsyncFactoryComponent for StoreRowModel {
|
|||
Self {
|
||||
tracker: 0,
|
||||
plugin: init.plugin,
|
||||
enabled: init.enabled,
|
||||
icon: None,
|
||||
input_sender: sender.input_sender().clone(),
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue