diff --git a/Linux/.vscode/settings.json b/Linux/.vscode/settings.json index 786d4d3..f21c8f7 100644 --- a/Linux/.vscode/settings.json +++ b/Linux/.vscode/settings.json @@ -1,3 +1,4 @@ { - "nixEnvSelector.nixFile": "${workspaceFolder}/shell.nix" + "nixEnvSelector.nixFile": "${workspaceFolder}/shell.nix", + "files.trimTrailingWhitespace": true } \ No newline at end of file diff --git a/Linux/src/actions/mod.rs b/Linux/src/actions/mod.rs index 85cdecb..2fdd83a 100644 --- a/Linux/src/actions/mod.rs +++ b/Linux/src/actions/mod.rs @@ -1,17 +1,22 @@ pub mod pipewire; -use crate::registry::MkRegistry; +use crate::registry::Registry; use crate::registry::RegistryItem; use crate::util::unique_id::UniqueId; -pub struct MkAction +pub struct ActionRegistryItem { pub unique_id: UniqueId } -impl RegistryItem for MkAction +pub trait Action +{ +} + + +impl RegistryItem for ActionRegistryItem { fn unique_id(&self) -> UniqueId { @@ -26,7 +31,7 @@ impl RegistryItem for MkAction -pub fn register(registry: &mut MkRegistry) +pub fn register(registry: &mut Registry) { pipewire::register(registry); } \ No newline at end of file diff --git a/Linux/src/actions/pipewire/mod.rs b/Linux/src/actions/pipewire/mod.rs index b59729e..cbef414 100644 --- a/Linux/src/actions/pipewire/mod.rs +++ b/Linux/src/actions/pipewire/mod.rs @@ -1,13 +1,13 @@ -use crate::registry::MkRegistry; +use crate::registry::Registry; use crate::util::unique_id::UniqueId; -use super::MkAction; +use super::ActionRegistryItem; pub mod set_volume; -pub fn register(registry: &mut MkRegistry) +pub fn register(registry: &mut Registry) { - registry.register(MkAction + registry.register(ActionRegistryItem { unique_id: UniqueId::new("pipewire.set_volume") }); diff --git a/Linux/src/config/mod.rs b/Linux/src/config/mod.rs index f9b433b..19793dd 100644 --- a/Linux/src/config/mod.rs +++ b/Linux/src/config/mod.rs @@ -49,15 +49,20 @@ impl ConfigManager pub fn get_writer(&self, name: &ConfigName) -> Result { let path = Path::join(&self.root, name.as_str()); - if !path.exists() + let parent = Path::parent(&path); + + if let Some(parent) = parent { - match std::fs::create_dir_all(path.clone()) + if !parent.exists() { - Ok(_v) => (), - Err(e) => return Err(e) + match std::fs::create_dir_all(parent) + { + Ok(_v) => (), + Err(e) => return Err(e) + } } } - + std::fs::File::create(path) } } diff --git a/Linux/src/devices/emulator/mod.rs b/Linux/src/devices/emulator/mod.rs index 0f39949..e84cb03 100644 --- a/Linux/src/devices/emulator/mod.rs +++ b/Linux/src/devices/emulator/mod.rs @@ -1,24 +1,58 @@ -use crate::registry::MkRegistry; +use emulatorwindow::EmulatorWindow; +use relm4::{component::Connector, ComponentController}; +use relm4::Component; +use relm4::gtk::prelude::*; + +use crate::registry::Registry; use crate::util::unique_id::UniqueId; -use super::MkDevice; +use super::{Device, DeviceRegistryItem}; pub mod emulatorwindow; - - -pub fn register(registry: &mut MkRegistry) +pub struct EmulatorWindowDevice { - registry.register(MkDevice { - unique_id: UniqueId::new("emulator") - }); + window: Option> } -/* - let app = relm4::main_application(); - let builder = EmulatorWindow::builder(); - app.add_window(&builder.root); - builder.launch(()).detach_runtime(); -*/ \ No newline at end of file +impl EmulatorWindowDevice +{ + fn new() -> Self + { + EmulatorWindowDevice + { + window: None + } + } +} + + +impl Device for EmulatorWindowDevice +{ + fn activate(&mut self) + { + if self.window.is_some() { return } + + let builder = EmulatorWindow::builder(); + self.window = Some(builder.launch({})); + } + + + fn deactivate(&mut self) + { + let Some(window) = self.window.take() else { return }; + + window.widget().close(); + } +} + + +pub fn register(registry: &mut Registry) +{ + registry.register(DeviceRegistryItem { + unique_id: UniqueId::new("emulator"), + factory: || Box::new(EmulatorWindowDevice::new()) + }); +} \ No newline at end of file diff --git a/Linux/src/devices/mod.rs b/Linux/src/devices/mod.rs index 0cd95d7..a1a4736 100644 --- a/Linux/src/devices/mod.rs +++ b/Linux/src/devices/mod.rs @@ -2,18 +2,26 @@ pub mod emulator; pub mod serial_min; -use crate::registry::MkRegistry; +use crate::registry::Registry; use crate::registry::RegistryItem; use crate::util::unique_id::UniqueId; -pub struct MkDevice +pub struct DeviceRegistryItem { - pub unique_id: UniqueId + pub unique_id: UniqueId, + pub factory: fn() -> Box } -impl RegistryItem for MkDevice +pub trait Device +{ + fn activate(&mut self); + fn deactivate(&mut self); +} + + +impl RegistryItem for DeviceRegistryItem { fn unique_id(&self) -> UniqueId { @@ -28,7 +36,7 @@ impl RegistryItem for MkDevice -pub fn register(registry: &mut MkRegistry) +pub fn register(registry: &mut Registry) { emulator::register(registry); serial_min::register(registry); diff --git a/Linux/src/devices/serial_min/mod.rs b/Linux/src/devices/serial_min/mod.rs index 61f3eb2..32abf8d 100644 --- a/Linux/src/devices/serial_min/mod.rs +++ b/Linux/src/devices/serial_min/mod.rs @@ -1,11 +1,33 @@ -use crate::registry::MkRegistry; +use crate::registry::Registry; use crate::util::unique_id::UniqueId; -use super::MkDevice; +use super::{Device, DeviceRegistryItem}; -pub fn register(registry: &mut MkRegistry) +pub struct SerialMinDevice { - registry.register(MkDevice { - unique_id: UniqueId::new("serial_min") + +} + + +impl Device for SerialMinDevice +{ + fn activate(&mut self) + { + //todo!() + } + + + fn deactivate(&mut self) + { + //todo!() + } +} + + +pub fn register(registry: &mut Registry) +{ + registry.register(DeviceRegistryItem { + unique_id: UniqueId::new("serial_min"), + factory: || Box::new(SerialMinDevice {}) }); } \ No newline at end of file diff --git a/Linux/src/main.rs b/Linux/src/main.rs index 82339a6..d09e1e1 100644 --- a/Linux/src/main.rs +++ b/Linux/src/main.rs @@ -1,6 +1,7 @@ +use std::cell::RefCell; use std::rc::Rc; use env_logger::Env; -use mainwindow::MainWindowViewModel; +use mainwindow::MainWindowInit; use orchestrator::Orchestrator; use relm4::prelude::*; @@ -27,11 +28,13 @@ fn main() relm4_icons::initialize_icons(); - let orchestrator = Rc::new(Orchestrator::new()); + let orchestrator = Rc::new(RefCell::new(Orchestrator::new())); let app = RelmApp::new("com.github.mvrens.massiveknob"); - app.run::(MainWindowViewModel + app.run::(MainWindowInit { orchestrator: Rc::clone(&orchestrator) }); + + orchestrator.borrow_mut().finalize(); } \ No newline at end of file diff --git a/Linux/src/mainwindow.rs b/Linux/src/mainwindow.rs index 8aadf57..3cdf50e 100644 --- a/Linux/src/mainwindow.rs +++ b/Linux/src/mainwindow.rs @@ -1,25 +1,31 @@ +use std::cell::RefCell; use std::rc::Rc; +use gtk::glib::clone; use gtk::prelude::*; use relm4::prelude::*; -use crate::{devices::MkDevice, orchestrator::Orchestrator, registry::RegistryItem, util::unique_id::UniqueId}; +use crate::orchestrator::Orchestrator; +use crate::registry::RegistryItem; +use crate::util::unique_id::UniqueId; pub struct MainWindow -{ +{ + orchestrator: Rc>, + devices_sorted: Vec } -pub struct MainWindowViewModel +pub struct MainWindowInit { - pub orchestrator: Rc + pub orchestrator: Rc> } -impl std::fmt::Debug for MainWindowViewModel +impl std::fmt::Debug for MainWindowInit { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("MainWindowViewModel") + f.debug_struct("MainWindowInit") // Skip orchestrator .finish() } @@ -29,25 +35,25 @@ impl std::fmt::Debug for MainWindowViewModel #[derive(Debug)] pub enum MainWindowMsg { + DeviceChanged(usize) } pub struct MainWindowWidgets { - device: MainWindowDeviceWidgets + _device: MainWindowDeviceWidgets } pub struct MainWindowDeviceWidgets { - devices_sorted: Vec, - devices_combobox: gtk::ComboBoxText + _devices_combobox: gtk::ComboBoxText } impl SimpleComponent for MainWindow { - type Init = MainWindowViewModel; + type Init = MainWindowInit; type Input = MainWindowMsg; type Output = (); type Root = gtk::Window; @@ -61,67 +67,19 @@ impl SimpleComponent for MainWindow .title(t!("mainwindow.title")) .default_width(500) .default_height(500) - .build() + .build() } - fn init(data: Self::Init, window: Self::Root, _sender: ComponentSender, ) -> ComponentParts + fn init(data: Self::Init, window: Self::Root, sender: ComponentSender) -> ComponentParts { - let orchestrator = data.orchestrator.as_ref(); - - let model = MainWindow {}; - let widgets = Self::init_ui(&window, &orchestrator); - - ComponentParts { model, widgets } - } - - - fn update(&mut self, msg: Self::Input, _sender: ComponentSender) - { - match msg { + let mut init_orchestrator = data.orchestrator.borrow_mut(); + init_orchestrator.initialize(); } - } -} - -impl MainWindow -{ - fn init_ui(window: >k::Window, orchestrator: &Orchestrator) -> MainWindowWidgets - { - let tabs = gtk::Notebook::builder().build(); - window.set_child(Some(&tabs)); - - MainWindowWidgets - { - device: Self::init_device_tab(&tabs, &orchestrator) - //Self::new_box_tab(&tabs, "mainwindow.tab.analoginputs"); - //Self::new_box_tab(&tabs, "mainwindow.tab.digitalinputs"); - //Self::add_box_tab(&tabs, "mainwindow.tab.analogoutputs"); - //Self::add_box_tab(&tabs, "mainwindow.tab.digitaloutputs"); - } - } - - - fn init_device_tab(tabs: >k::Notebook, orchestrator: &Orchestrator) -> MainWindowDeviceWidgets - { - let tab = Self::new_box_tab(&tabs, "mainwindow.tab.device"); - - let label = gtk::Label::builder() - .label(t!("mainwindow.deviceType.label")) - .halign(gtk::Align::Start) - .build(); - - tab.append(&label); - - - - let devices_combobox = gtk::ComboBoxText::builder() - .build(); - - tab.append(&devices_combobox); - + let orchestrator = data.orchestrator.borrow(); let mut devices_sorted: Vec = orchestrator.devices() .map(|device| SortedDevice @@ -133,14 +91,114 @@ impl MainWindow devices_sorted.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); - let current_device_id = orchestrator.current_device_id(); + + let model = MainWindow + { + orchestrator: data.orchestrator.clone(), + devices_sorted + }; + + let widgets = MainWindowBuilder::new(&window, &model, &sender).build(); + + ComponentParts { model, widgets } + } - for (index, device) in devices_sorted.iter().enumerate() + fn update(&mut self, msg: Self::Input, _sender: ComponentSender) + { + match msg + { + MainWindowMsg::DeviceChanged(index) => + { + let mut orchestrator = self.orchestrator.borrow_mut(); + let device = &self.devices_sorted[index]; + + orchestrator.set_current_device_id(device.unique_id.clone()); + } + } + } +} + + +struct MainWindowBuilder<'a> +{ + window: &'a gtk::Window, + model: &'a MainWindow, + sender: &'a ComponentSender +} + + +impl<'a> MainWindowBuilder<'a> +{ + fn new(window: &'a gtk::Window, model: &'a MainWindow, sender: &'a ComponentSender) -> Self + { + Self + { + window, + model, + sender + } + } + + + fn build(&self) -> MainWindowWidgets + { + let tabs = gtk::Notebook::builder().build(); + self.window.set_child(Some(&tabs)); + + MainWindowWidgets + { + _device: self.init_device_tab(&tabs) + //Self::new_box_tab(&tabs, "mainwindow.tab.analoginputs"); + //Self::new_box_tab(&tabs, "mainwindow.tab.digitalinputs"); + //Self::add_box_tab(&tabs, "mainwindow.tab.analogoutputs"); + //Self::add_box_tab(&tabs, "mainwindow.tab.digitaloutputs"); + } + } + + + fn init_device_tab(&self, tabs: >k::Notebook) -> MainWindowDeviceWidgets + { + let sender = self.sender; + let tab = Self::new_box_tab(&tabs, "mainwindow.tab.device"); + + let label = gtk::Label::builder() + .label(t!("mainwindow.deviceType.label")) + .halign(gtk::Align::Start) + .build(); + + tab.append(&label); + + + + let devices_combobox = gtk::ComboBoxText::builder() + .build(); + + let devices_combobox_cloned = devices_combobox.clone(); + + devices_combobox.connect_changed(clone!( + @strong sender => move |_| + { + if let Some(active_index) = devices_combobox_cloned.active() + { + if let Ok(active_index_usize) = usize::try_from(active_index) + { + sender.input(MainWindowMsg::DeviceChanged(active_index_usize)); + } + } + })); + + tab.append(&devices_combobox); + + + let current_device_id = self.model.orchestrator.borrow().current_device_id(); + + + for (index, device) in self.model.devices_sorted.iter().enumerate() { devices_combobox.append_text(device.name.as_str()); - if let Some(device_id) = current_device_id + if let Some(device_id) = current_device_id.clone() { if device_id == device.unique_id { @@ -152,12 +210,11 @@ impl MainWindow MainWindowDeviceWidgets { - devices_sorted, - devices_combobox + _devices_combobox: devices_combobox } } - + fn new_box_tab(notebook: >k::Notebook, title_key: &str) -> gtk::Box { let tab = gtk::Box::builder() diff --git a/Linux/src/orchestrator/mod.rs b/Linux/src/orchestrator/mod.rs index 7c5735a..e3882ea 100644 --- a/Linux/src/orchestrator/mod.rs +++ b/Linux/src/orchestrator/mod.rs @@ -1,10 +1,10 @@ use crate::actions; -use crate::actions::MkAction; +use crate::actions::ActionRegistryItem; use crate::config::json::JsonConfigManager; use crate::config::{ConfigManager, ConfigName}; -use crate::devices; -use crate::devices::MkDevice; -use crate::registry::MkRegistry; +use crate::devices::{self, Device}; +use crate::devices::DeviceRegistryItem; +use crate::registry::Registry; use crate::util::unique_id::UniqueId; @@ -15,11 +15,14 @@ pub struct Orchestrator { config_manager: ConfigManager, - device_registry: MkRegistry, - action_registry: MkRegistry, + device_registry: Registry, + action_registry: Registry, settings_name: ConfigName, - settings: settings::Settings + settings: settings::Settings, + + + current_device_instance: Option> } @@ -36,8 +39,8 @@ impl Orchestrator Some(v) => v }; - let mut device_registry = MkRegistry::new(); - let mut action_registry = MkRegistry::new(); + let mut device_registry = Registry::new(); + let mut action_registry = Registry::new(); devices::register(&mut device_registry); actions::register(&mut action_registry); @@ -50,12 +53,27 @@ impl Orchestrator action_registry, settings_name, - settings + settings, + + + current_device_instance: None } } - pub fn devices(&self) -> impl Iterator + pub fn initialize(&mut self) + { + self.set_current_device(self.current_device_id()); + } + + + pub fn finalize(&mut self) + { + self.set_current_device(None); + } + + + pub fn devices(&self) -> impl Iterator { self.device_registry.iter() } @@ -67,22 +85,23 @@ impl Orchestrator Some(UniqueId::new(device_id.as_str())) } - pub fn current_device(&self) -> Option<&MkDevice> + + pub fn current_device(&self) -> Option<&DeviceRegistryItem> { let Some(device_id) = self.current_device_id() else { return None }; self.device_registry.by_id(device_id) } - pub fn set_current_device_id(&mut self, id: &str) + pub fn set_current_device_id(&mut self, id: UniqueId) { - let new_id = Some(String::from(id)); + let new_id = Some(String::from(id.as_str())); if new_id == self.settings.device_id { return; } self.settings.device_id = new_id; self.store_settings(); - // TODO unload old device, activate new + self.set_current_device(Some(id)); } @@ -93,4 +112,36 @@ impl Orchestrator log::error!("Error writing settings: {e}"); } } + + + fn set_current_device(&mut self, id: Option) + { + let prev_device_instance; + let new_device = match id + { + None => None, + Some(v) => self.device_registry.by_id(v) + }; + + + // Replace the device instance + if let Some(new_device) = new_device + { + let mut new_device_instance = (new_device.factory)(); + new_device_instance.activate(); + + prev_device_instance = self.current_device_instance.replace(new_device_instance); + } + else + { + prev_device_instance = self.current_device_instance.take(); + } + + + // Deactivate the previous instance + if let Some(mut prev_device_instance) = prev_device_instance + { + prev_device_instance.deactivate(); + } + } } \ No newline at end of file diff --git a/Linux/src/registry/mod.rs b/Linux/src/registry/mod.rs index 5d6208a..385683d 100644 --- a/Linux/src/registry/mod.rs +++ b/Linux/src/registry/mod.rs @@ -16,14 +16,14 @@ pub trait RegistryItem -pub struct MkRegistry where T: RegistryItem +pub struct Registry where T: RegistryItem { items: HashMap } -impl MkRegistry where T: RegistryItem +impl Registry where T: RegistryItem { pub fn new() -> Self { @@ -35,8 +35,8 @@ impl MkRegistry where T: RegistryItem pub fn register(&mut self, device: T) - { - let device_id = device.unique_id(); + { + let device_id = device.unique_id(); log::debug!("Registered device: [{}] {}", device_id.as_str(), device.name()); self.items.insert(String::from(device_id.as_str()), device); diff --git a/Linux/src/util/unique_id.rs b/Linux/src/util/unique_id.rs index dfea5ae..e0b820a 100644 --- a/Linux/src/util/unique_id.rs +++ b/Linux/src/util/unique_id.rs @@ -4,8 +4,10 @@ use super::validated_string::{ValidatedString, ValidatedStringPattern}; pub type UniqueId = ValidatedString; +#[derive(PartialEq, Eq)] pub struct UniqueIdPattern; + impl ValidatedStringPattern for UniqueIdPattern { fn pattern() -> &'static str { r"^[a-zA-Z0-9\.\-_]+$" }